diff options
| author | Vincent Schippefilt <vsc@odoo.com> | 2019-03-06 14:02:32 +0100 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2019-03-06 20:07:17 +0100 |
| commit | ab56e637b7f97ddcdbcc9baea99ae097e342409c (patch) | |
| tree | 4cb47da9f7ef89b19d18429220a7507470ec12a9 /addons/web/static | |
| parent | eeed894fd50e477135e1ac9a82f6e98ce063ab90 (diff) | |
[REF] web: adapt code after jQuery update
Part of task 1896658
Co-authored-by: Aaron Bohy <aab@odoo.com>
Co-authored-by: Christophe Matthieu <chm@odoo.com>
Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com>
Co-authored-by: David Monjoie <dmo@odoo.com>
Co-authored-by: Martin Geubelle <mge@odoo.com>
Co-authored-by: svs-odoo <svs@odoo.com>
Co-authored-by: Vincent Schippefilt <vsc@odoo.com>
Diffstat (limited to 'addons/web/static')
155 files changed, 11213 insertions, 10624 deletions
diff --git a/addons/web/static/src/js/_deprecated/data.js b/addons/web/static/src/js/_deprecated/data.js index 984f0bbaddb..acac959fcc8 100644 --- a/addons/web/static/src/js/_deprecated/data.js +++ b/addons/web/static/src/js/_deprecated/data.js @@ -101,7 +101,7 @@ var Query = Class.extend({ * Fetches the first record matching the query, or null * * @param {Object} [options] additional options for the rpc() method - * @returns {jQuery.Deferred<Object|null>} + * @returns {Promise<Object|null>} */ first: function (options) { var self = this; @@ -115,7 +115,7 @@ var Query = Class.extend({ * Fetches all records matching the query * * @param {Object} [options] additional options for the rpc() method - * @returns {jQuery.Deferred<Array<>>} + * @returns {Promise<Array<>>} */ all: function (options) { return this._execute(options); @@ -123,10 +123,10 @@ var Query = Class.extend({ /** * Fetches the number of records matching the query in the database * - * @returns {jQuery.Deferred<Number>} + * @returns {Promise<Number>} */ count: function () { - if (this._count !== undefined) { return $.when(this._count); } + if (this._count !== undefined) { return Promise.resolve(this._count); } return this._model.call( 'search_count', [this._filter], { context: this._model.context(this._context)}); @@ -367,11 +367,12 @@ var DataSet = Class.extend(mixins.PropertiesMixin, { * @param {Array} ids identifiers of the records to read * @param {Array} [fields] fields to read and return, by default all fields are returned * @param {Object} [options] - * @returns {$.Deferred} + * @returns {Promise} */ read_ids: function (ids, fields, options) { - if (_.isEmpty(ids)) - return $.Deferred().resolve([]); + if (_.isEmpty(ids)) { + return Promise.resolve([]); + } options = options || {}; var method = 'read'; @@ -405,7 +406,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, { * @params {Object} [options] * @param {Number} [options.offset=0] The index from which selected records should be returned * @param {Number} [options.limit=null] The maximum number of records to return - * @returns {$.Deferred} + * @returns {Promise} */ read_slice: function (fields, options) { var self = this; @@ -414,21 +415,23 @@ var DataSet = Class.extend(mixins.PropertiesMixin, { .limit(options.limit || false) .offset(options.offset || 0) .all(); - return this.orderer.add(query).done(function (records) { + var prom = this.orderer.add(query); + prom.then(function (records) { self.ids = _(records).pluck('id'); }); + return prom; }, /** * Reads the current dataset record (from its index) * * @params {Array} [fields] fields to read and return, by default all fields are returned * @param {Object} [options.context] context data to add to the request payload, on top of the DataSet's own context - * @returns {$.Deferred} + * @returns {Promise} */ read_index: function (fields, options) { options = options || {}; return this.read_ids([this.ids[this.index]], fields, options).then(function (records) { - if (_.isEmpty(records)) { return $.Deferred().reject().promise(); } + if (_.isEmpty(records)) { return Promise.reject(); } return records[0]; }); }, @@ -437,7 +440,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, { * * @param {Array} [fields] fields to get default values for, by default all defaults are read * @param {Object} [options.context] context data to add to the request payload, on top of the DataSet's own context - * @returns {$.Deferred} + * @returns {Promise} */ default_get: function (fields, options) { options = options || {}; @@ -451,15 +454,17 @@ var DataSet = Class.extend(mixins.PropertiesMixin, { * @param {Object} options Dictionary that can contain the following keys: * - readonly_fields: Values from readonly fields that were updated by * on_changes. Only used by the BufferedDataSet to make the o2m work correctly. - * @returns {$.Deferred} + * @returns {Promise} */ create: function (data, options) { var self = this; - return this._model.call('create', [data], { + var prom = this._model.call('create', [data], { context: this.get_context() - }).done(function () { + }); + prom.then(function () { self.trigger('dataset_changed', data, options); }); + return prom; }, /** * Saves the provided data in an existing db record @@ -470,16 +475,18 @@ var DataSet = Class.extend(mixins.PropertiesMixin, { * - context: The context to use in the server-side call. * - readonly_fields: Values from readonly fields that were updated by * on_changes. Only used by the BufferedDataSet to make the o2m work correctly. - * @returns {$.Deferred} + * @returns {Promise} */ write: function (id, data, options) { options = options || {}; var self = this; - return this._model.call('write', [[id], data], { + var prom = this._model.call('write', [[id], data], { context: this.get_context(options.context) - }).done(function () { + }); + prom.then(function () { self.trigger('dataset_changed', id, data, options); }); + return prom; }, /** * Deletes an existing record from the database @@ -488,11 +495,13 @@ var DataSet = Class.extend(mixins.PropertiesMixin, { */ unlink: function (ids) { var self = this; - return this._model.call('unlink', [ids], { + var prom = this._model.call('unlink', [ids], { context: this.get_context() - }).done(function () { + }); + prom.then(function () { self.trigger('dataset_changed', ids); }); + return prom; }, /** * Calls an arbitrary RPC method @@ -501,7 +510,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, { * @param {Array} [args] arguments to pass to the method * @param {Function} callback * @param {Function} error_callback - * @returns {$.Deferred} + * @returns {Promise} */ call: function (method, args) { return this._model.call(method, args); @@ -511,7 +520,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, { * * @param {String} method * @param {Array} [args] - * @returns {$.Deferred} + * @returns {Promise} */ call_button: function (method, args) { return this._model.call_button(method, args); @@ -520,7 +529,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, { * Fetches the "readable name" for records, based on intrinsic rules * * @param {Array} ids - * @returns {$.Deferred} + * @returns {Promise} */ name_get: function (ids) { return this._model.call('name_get', [ids], {context: this.get_context()}); @@ -532,7 +541,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, { * @param {String} [operator='ilike'] matching operator to use with the provided name value * @param {Number} [limit=0] maximum number of matches to return * @param {Function} callback function to call with name_search result - * @returns {$.Deferred} + * @returns {Promise} */ name_search: function (name, domain, operator, limit) { return this._model.call('name_search', { @@ -603,7 +612,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, { * Resequence records. * * @param {Array} ids identifiers of the records to resequence - * @returns {$.Deferred} + * @returns {Promise} */ resequence: function (ids, options) { options = options || {}; @@ -643,7 +652,7 @@ var DataSetStatic = DataSet.extend({ unlink: function (ids) { this.set_ids(_.without.apply(null, [this.ids].concat(ids))); this.trigger('unlink', ids); - return $.Deferred().resolve({result: true}); + return Promise.resolve({result: true}); }, }); @@ -674,7 +683,7 @@ var DataSetSearch = DataSet.extend({ * @param {Array} [options.domain] domain data to add to the request payload, ANDed with the dataset's domain * @param {Number} [options.offset=0] The index from which selected records should be returned * @param {Number} [options.limit=null] The maximum number of records to return - * @returns {$.Deferred} + * @returns {Promise} */ read_slice: function (fields, options) { options = options || {}; @@ -686,11 +695,13 @@ var DataSetSearch = DataSet.extend({ .limit(options.limit || false); q = q.order_by.apply(q, this._sort); - return this.orderer.add(q.all()).done(function (records) { + var prom = this.orderer.add(q.all()); + prom.then(function (records) { // FIXME: not sure about that one, *could* have discarded count - q.count().done(function (count) { self._length = count; }); + q.count().then(function (count) { self._length = count; }); self.ids = _(records).pluck('id'); }); + return prom; }, get_domain: function (other_domain) { return this._model.domain(other_domain); @@ -717,10 +728,12 @@ var DataSetSearch = DataSet.extend({ }, unlink: function (ids, callback, error_callback) { var self = this; - return this._super(ids).done(function () { + var prom = this._super(ids); + prom.then(function () { self.remove_ids( ids); self.trigger("dataset_changed", ids, callback, error_callback); }); + return prom; }, size: function () { if (this._length !== null) { @@ -801,7 +814,7 @@ var Model = Class.extend({ * @param {Array} [args] positipyEvalonal arguments * @param {Object} [kwargs] keyword arguments * @param {Object} [options] additional options for the rpc() method - * @returns {jQuery.Deferred<>} call result + * @returns {Promise<>} call result */ call: function (method, args, kwargs, options) { args = args || []; diff --git a/addons/web/static/src/js/apps.js b/addons/web/static/src/js/apps.js index aadcc700984..739317337ba 100644 --- a/addons/web/static/src/js/apps.js +++ b/addons/web/static/src/js/apps.js @@ -22,20 +22,21 @@ var Apps = AbstractAction.extend({ }, get_client: function() { - // return the client via a deferred, resolved or rejected depending if + // return the client via a promise, resolved or rejected depending if // the remote host is available or not. var check_client_available = function(client) { - var d = $.Deferred(); var i = new Image(); - i.onerror = function() { - d.reject(client); - }; - i.onload = function() { - d.resolve(client); - }; + var def = new Promise(function (resolve, reject) { + i.onerror = function() { + reject(client); + }; + i.onload = function() { + resolve(client); + }; + }); var ts = new Date().getTime(); i.src = _.str.sprintf('%s/web/static/src/img/sep-a.gif?%s', client.origin, ts); - return d.promise(); + return def; }; if (apps_client) { return check_client_available(apps_client); @@ -105,58 +106,56 @@ var Apps = AbstractAction.extend({ start: function() { var self = this; - var def = $.Deferred(); - self.get_client().then(function(client) { - self.client = client; - - var qs = {db: client.dbname}; - if (session.debug) { - qs.debug = session.debug; - } - var u = $.param.querystring(client.origin + "/apps/embed/client", qs); - var css = {width: '100%', height: '750px'}; - self.$ifr = $('<iframe>').attr('src', u); - - self.uniq = _.uniqueId('apps'); - $(window).on("message." + self.uniq, self.proxy('_on_message')); - - self.on('message:ready', self, function(m) { - var w = this.$ifr[0].contentWindow; - var act = { - type: 'ir.actions.client', - tag: this.remote_action_tag, - params: _.extend({}, this.params, { - db: session.db, - origin: session.origin, - }) - }; - w.postMessage({type:'action', action: act}, client.origin); - }); + return new Promise(function (resolve, reject) { + self.get_client().then(function (client) { + self.client = client; + + var qs = {db: client.dbname}; + if (session.debug) { + qs.debug = session.debug; + } + var u = $.param.querystring(client.origin + "/apps/embed/client", qs); + var css = {width: '100%', height: '750px'}; + self.$ifr = $('<iframe>').attr('src', u); + + self.uniq = _.uniqueId('apps'); + $(window).on("message." + self.uniq, self.proxy('_on_message')); + + self.on('message:ready', self, function(m) { + var w = this.$ifr[0].contentWindow; + var act = { + type: 'ir.actions.client', + tag: this.remote_action_tag, + params: _.extend({}, this.params, { + db: session.db, + origin: session.origin, + }) + }; + w.postMessage({type:'action', action: act}, client.origin); + }); - self.on('message:set_height', self, function(m) { - this.$ifr.height(m.height); - }); + self.on('message:set_height', self, function(m) { + this.$ifr.height(m.height); + }); - self.on('message:blockUI', self, function() { framework.blockUI(); }); - self.on('message:unblockUI', self, function() { framework.unblockUI(); }); - self.on('message:warn', self, function(m) {self.do_warn(m.title, m.message, m.sticky); }); - - self.$ifr.appendTo(self.$('.o_content')).css(css).addClass('apps-client'); - - def.resolve(); - }, function() { - self.do_warn(_t('Odoo Apps will be available soon'), _t('Showing locally available modules'), true); - return self._rpc({ - route: '/web/action/load', - params: {action_id: self.failback_action_id}, - }).then(function(action) { - return self.do_action(action); - }).always(function () { - def.reject(); + self.on('message:blockUI', self, function() { framework.blockUI(); }); + self.on('message:unblockUI', self, function() { framework.unblockUI(); }); + self.on('message:warn', self, function(m) {self.do_warn(m.title, m.message, m.sticky); }); + + self.$ifr.appendTo(self.$('.o_content')).css(css).addClass('apps-client'); + + resolve(); + }, function() { + self.do_warn(_t('Odoo Apps will be available soon'), _t('Showing locally available modules'), true); + return self._rpc({ + route: '/web/action/load', + params: {action_id: self.failback_action_id}, + }).then(function(action) { + return self.do_action(action); + }).then(reject, reject); }); }); - return def; - }, + } }); var AppsUpdates = Apps.extend({ diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js index 994b945b1d3..cf1cbaba180 100644 --- a/addons/web/static/src/js/boot.js +++ b/addons/web/static/src/js/boot.js @@ -6,8 +6,8 @@ * @name openerp * @namespace openerp * - * Each module can return a deferred. In that case, the module is marked as loaded - * only when the deferred is resolved, and its value is equal to the resolved value. + * Each module can return a promise. In that case, the module is marked as loaded + * only when the promise is resolved, and its value is equal to the resolved value. * The module can be rejected (unloaded). This will be logged in the console as info. * * logs: @@ -17,7 +17,7 @@ * Failed modules: * A javascript error is detected * Rejected modules: - * The module returns a rejected deferred. It (and its dependent modules) + * The module returns a rejected promise. It (and its dependent modules) * is not loaded. * Rejected linked modules: * Modules who depend on a rejected module @@ -27,14 +27,14 @@ * Non loaded or failed module informations for debugging */ -(function() { +(function () { "use strict"; var jobs = []; var factories = Object.create(null); var job_names = []; var job_deps = []; - var job_deferred = []; + var job_promise = []; var services = Object.create({}); @@ -45,15 +45,20 @@ var debug = ($.deparam($.param.querystring()).debug !== undefined); var odoo = window.odoo = window.odoo || {}; + var didLogInfoResolve; + var didLogInfoPromise = new Promise(function (resolve) { + didLogInfoResolve = resolve; + }); + _.extend(odoo, { testing: typeof QUnit === "object", debug: debug, remaining_jobs: jobs, __DEBUG__: { - didLogInfo: $.Deferred(), + didLogInfo: didLogInfoPromise, get_dependencies: function (name, transitive) { - var deps = name instanceof Array ? name: [name], + var deps = name instanceof Array ? name : [name], changed; do { changed = false; @@ -67,26 +72,26 @@ return deps; }, get_dependents: function (name) { - return _.pluck(_.where(job_deps, {from: name}), 'to'); + return _.pluck(_.where(job_deps, { from: name }), 'to'); }, get_waited_jobs: function () { - return _.uniq(_.map(jobs, function(job) {return job.name;})); + return _.uniq(_.map(jobs, function (job) { return job.name; })); }, get_missing_jobs: function () { var self = this; var waited = this.get_waited_jobs(); var missing = []; - _.each(waited, function(job) { - _.each(self.get_dependencies(job), function(job) { + _.each(waited, function (job) { + _.each(self.get_dependencies(job), function (job) { if (!(job in self.services)) { missing.push(job); } }); }); - return _.filter(_.difference(_.uniq(missing), waited), function (job) {return !job.error;}); + return _.filter(_.difference(_.uniq(missing), waited), function (job) { return !job.error; }); }, get_failed_jobs: function () { - return _.filter(jobs, function (job) {return !!job.error;}); + return _.filter(jobs, function (job) { return !!job.error; }); }, factories: factories, services: services, @@ -109,10 +114,10 @@ if (odoo.debug) { if (!(deps instanceof Array)) { - throw new Error ('Dependencies should be defined by an array', deps); + throw new Error('Dependencies should be defined by an array', deps); } if (typeof factory !== 'function') { - throw new Error ('Factory should be defined by a function', factory); + throw new Error('Factory should be defined by a function', factory); } if (typeof name !== 'string') { throw new Error("Invalid name definition (should be a string", name); @@ -133,7 +138,7 @@ job_names.push(name); _.each(deps, function (dep) { - job_deps.push({from:dep, to:name}); + job_deps.push({ from: dep, to: name }); }); this.process_jobs(jobs, services); @@ -149,7 +154,7 @@ var job; var jobdep; - for (var k=0; k<jobs.length; k++) { + for (var k = 0; k < jobs.length; k++) { debug_jobs[jobs[k].name] = job = { dependencies: jobs[k].deps, dependents: odoo.__DEBUG__.get_dependents(jobs[k].name), @@ -162,10 +167,10 @@ job.rejected = jobs[k].rejected; rejected.push(job.name); } - var deps = odoo.__DEBUG__.get_dependencies( job.name ); - for (var i=0; i<deps.length; i++) { + var deps = odoo.__DEBUG__.get_dependencies(job.name); + for (var i = 0; i < deps.length; i++) { if (job.name !== deps[i] && !(deps[i] in services)) { - jobdep = debug_jobs[deps[i]] || (deps[i] in factories && _.find(jobs, function (job) { return job.name === deps[i];})); + jobdep = debug_jobs[deps[i]] || (deps[i] in factories && _.find(jobs, function (job) { return job.name === deps[i]; })); if (jobdep && jobdep.rejected) { if (!job.rejected) { job.rejected = []; @@ -212,48 +217,49 @@ missing: missing, failed: _.pluck(failed, 'name'), }; - odoo.__DEBUG__.didLogInfo.resolve(); + didLogInfoResolve(); }, process_jobs: function (jobs, services) { var job; - function process_job (job) { + function process_job(job) { var require = make_require(job); var job_exec; - var def = $.Deferred(); - try { - job_exec = job.factory.call(null, require); - jobs.splice(jobs.indexOf(job), 1); - job_deferred.push(def); - } catch (e) { - job.error = e; - console.error('Error while loading ' + job.name); - console.error(e.stack); - } - if (!job.error) { - $.when(job_exec).then( - function (data) { - services[job.name] = data; - def.resolve(); - odoo.process_jobs(jobs, services); - }, function (e) { - job.rejected = e || true; - jobs.push(job); - def.resolve(); - } - ); - } + var def = new Promise(function (resolve) { + try { + job_exec = job.factory.call(null, require); + jobs.splice(jobs.indexOf(job), 1); + } catch (e) { + job.error = e; + console.error('Error while loading ' + job.name); + console.error(e.stack); + } + if (!job.error) { + Promise.resolve(job_exec).then( + function (data) { + services[job.name] = data; + resolve(); + odoo.process_jobs(jobs, services); + }).guardedCatch(function (e) { + job.rejected = e || true; + jobs.push(job); + resolve(); + } + ); + } + }); + job_promise.push(def); } - function is_ready (job) { + function is_ready(job) { return !job.error && !job.rejected && _.every(job.factory.deps, function (name) { return name in services; }); } - function make_require (job) { + function make_require(job) { var deps = _.pick(services, job.deps); - function require (name) { + function require(name) { if (!(name in deps)) { console.error('Undefined dependency: ', name); } else { @@ -278,9 +284,9 @@ // automatically log errors detected when loading modules var log_when_loaded = function () { _.delay(function () { - var len = job_deferred.length; - $.when.apply($, job_deferred).then(function () { - if (len === job_deferred.length) { + var len = job_promise.length; + Promise.all(job_promise).then(function () { + if (len === job_promise.length) { odoo.log(); } else { log_when_loaded(); diff --git a/addons/web/static/src/js/chrome/abstract_action.js b/addons/web/static/src/js/chrome/abstract_action.js index a5f98434ddb..1b258ccc6f6 100644 --- a/addons/web/static/src/js/chrome/abstract_action.js +++ b/addons/web/static/src/js/chrome/abstract_action.js @@ -104,7 +104,7 @@ var AbstractAction = Widget.extend(ActionMixin, { }; }); } - return $.when(def).then(function () { + return Promise.resolve(def).then(function () { var controlPanelView = new self.config.ControlPanelView(params); return controlPanelView.getController(self).then(function (controlPanel) { self._controlPanel = controlPanel; @@ -112,7 +112,7 @@ var AbstractAction = Widget.extend(ActionMixin, { }); }); } - return $.when(); + return Promise.resolve(); }, /** * @override diff --git a/addons/web/static/src/js/chrome/abstract_web_client.js b/addons/web/static/src/js/chrome/abstract_web_client.js index 7082f26e69d..68ac157868d 100644 --- a/addons/web/static/src/js/chrome/abstract_web_client.js +++ b/addons/web/static/src/js/chrome/abstract_web_client.js @@ -78,7 +78,7 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi if (event.data.on_success) { event.data.on_success(result); } - }).fail(function (result) { + }).guardedCatch(function (result) { if (event.data.on_fail) { event.data.on_fail(result); } @@ -116,17 +116,17 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi .then(function () { self.$el.toggleClass('o_rtl', _t.database.parameters.direction === "rtl"); self.bind_events(); - return $.when( + return Promise.all([ self.set_action_manager(), self.set_loading() - ); + ]); }).then(function () { if (session.session_is_valid()) { return self.show_application(); } else { // database manager needs the webclient to keep going even // though it has no valid session - return $.when(); + return Promise.resolve(); } }).then(function () { // Listen to 'scroll' event and propagate it on main bus @@ -145,12 +145,13 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi this.$el.on('mouseenter', '.oe_systray > div:not([data-toggle=tooltip])', function () { $(this).attr('data-toggle', 'tooltip').tooltip().trigger('mouseenter'); }); + // TODO: this handler seems useless since 11.0, should be removed this.$el.on('click', '.oe_dropdown_toggle', function (ev) { ev.preventDefault(); var $toggle = $(this); var doc_width = $(document).width(); var $menu = $toggle.siblings('.oe_dropdown_menu'); - $menu = $menu.size() >= 1 ? $menu : $toggle.find('.oe_dropdown_menu'); + $menu = $menu.length >= 1 ? $menu : $toggle.find('.oe_dropdown_menu'); var state = $menu.is('.oe_opened'); setTimeout(function () { // Do not alter propagation diff --git a/addons/web/static/src/js/chrome/action_manager.js b/addons/web/static/src/js/chrome/action_manager.js index 107c6e314eb..0537b45deb5 100644 --- a/addons/web/static/src/js/chrome/action_manager.js +++ b/addons/web/static/src/js/chrome/action_manager.js @@ -92,7 +92,7 @@ var ActionManager = Widget.extend({ * will be restored. It ensures that the current controller can be left (for * instance, that it has no unsaved changes). * - * @returns {Deferred} resolved if the current controller can be left, + * @returns {Promise} resolved if the current controller can be left, * rejected otherwise. */ clearUncommittedChanges: function () { @@ -100,7 +100,7 @@ var ActionManager = Widget.extend({ if (currentController) { return currentController.widget.canBeRemoved(); } - return $.when(); + return Promise.resolve(); }, /** * This is the entry point to execute Odoo actions, given as an ID in @@ -123,7 +123,7 @@ var ActionManager = Widget.extend({ * is useful when we come from a loadState()) * @param {boolean} [options.replace_last_action=false] set to true to * replace last part of the breadcrumbs with the action - * @return {$.Deferred<Object>} resolved with the action when the action is + * @return {Promise<Object>} resolved with the action when the action is * loaded and appended to the DOM ; rejected if the action can't be * executed (e.g. if doAction has been called to execute another action * before this one was complete). @@ -155,7 +155,7 @@ var ActionManager = Widget.extend({ }); } - return this.dp.add($.when(def)).then(function () { + return this.dp.add(Promise.resolve(def)).then(function () { // action.target 'main' is equivalent to 'current' except that it // also clears the breadcrumbs options.clear_breadcrumbs = action.target === 'main' || @@ -211,12 +211,12 @@ var ActionManager = Widget.extend({ * @param {Object} state * @param {integer|string} [state.action] the action to execute (given its * id or tag for client actions) - * @returns {Deferred} resolved when the UI has been updated + * @returns {Promise} resolved when the UI has been updated */ loadState: function (state) { var action; if (!state.action) { - return $.when(); + return Promise.resolve(); } if (_.isString(state.action) && core.action_registry.contains(state.action)) { action = { @@ -299,7 +299,7 @@ var ActionManager = Widget.extend({ * @param {string} [action.target="current"] set to "new" to render the * controller in a dialog * @param {Object} options @see doAction for details - * @returns {Deferred} resolved when the controller is started and appended + * @returns {Promise} resolved when the controller is started and appended */ _executeAction: function (action, options) { var self = this; @@ -338,7 +338,7 @@ var ActionManager = Widget.extend({ return action; }) - .fail(function () { + .guardedCatch(function () { self._removeAction(action.jsID); }); }, @@ -349,7 +349,7 @@ var ActionManager = Widget.extend({ * @private * @param {Object} action * @param {Object} options @see doAction for details - * @returns {Deferred} resolved when the controller is rendered inside a + * @returns {Promise} resolved when the controller is rendered inside a * dialog appended to the DOM */ _executeActionInDialog: function (action, options) { @@ -401,7 +401,7 @@ var ActionManager = Widget.extend({ return action; }); - }).fail(function () { + }).guardedCatch(function () { self._removeAction(action.jsID); }); }, @@ -412,14 +412,14 @@ var ActionManager = Widget.extend({ * @param {Object} action the description of the action to execute * @param {string} action.tag the key of the action in the action_registry * @param {Object} options @see doAction for details - * @returns {Deferred} resolved when the client action has been executed + * @returns {Promise} resolved when the client action has been executed */ _executeClientAction: function (action, options) { var self = this; var ClientAction = core.action_registry.get(action.tag); if (!ClientAction) { console.error("Could not find client action " + action.tag, action); - return $.Deferred().reject(); + return Promise.reject(); } if (!(ClientAction.prototype instanceof AbstractAction)) { console.warn('The client action ' + action.tag + ' should be an instance of AbstractAction!'); @@ -431,7 +431,7 @@ var ActionManager = Widget.extend({ if (next) { return this.doAction(next, options); } - return $.when(); + return Promise.resolve(); } var controllerID = _.uniqueId('controller_'); @@ -449,12 +449,14 @@ var ActionManager = Widget.extend({ }; this.controllers[controllerID] = controller; action.controllerID = controllerID; - return this._executeAction(action, options).done(function () { + var prom = this._executeAction(action, options); + prom.then(function () { // AAB: this should be done automatically in AbstractAction, so that // it can be overriden by actions that have specific stuff to push // (e.g. Discuss, Views) self._pushState(controllerID, {}); }); + return prom; }, /** * Executes actions of type 'ir.actions.act_window_close', i.e. closes the @@ -469,7 +471,7 @@ var ActionManager = Widget.extend({ * @param {Object} [action.effect] effect to show up, e.g. rainbow man. * @param {Object} [action.infos] infos on performing the close action. * Useful for providing some context for the `on_close` handler. - * @returns {Deferred} resolved immediately + * @returns {Promise} resolved immediately */ _executeCloseAction: function (action, options) { var result; @@ -484,7 +486,7 @@ var ActionManager = Widget.extend({ this.trigger_up('show_effect', action.effect); } - return $.when(result); + return Promise.resolve(result); }, /** * Executes actions of type 'ir.actions.server'. @@ -494,7 +496,7 @@ var ActionManager = Widget.extend({ * @param {integer} action.id the db ID of the action to execute * @param {Object} [action.context] * @param {Object} options @see doAction for details - * @returns {Deferred} resolved when the action has been executed + * @returns {Promise} resolved when the action has been executed */ _executeServerAction: function (action, options) { var self = this; @@ -520,7 +522,7 @@ var ActionManager = Widget.extend({ * @param {string} [action.target] set to 'self' to redirect in the current page, * redirects to a new page by default * @param {Object} options @see doAction for details - * @returns {Deferred} resolved when the redirection is done (immediately + * @returns {Promise} resolved when the redirection is done (immediately * when redirecting to a new page) */ _executeURLAction: function (action, options) { @@ -531,7 +533,7 @@ var ActionManager = Widget.extend({ if (action.target === 'self') { framework.redirect(url); - return $.Deferred(); + return Promise.resolve(); } else { var w = window.open(url, '_blank'); if (!w || w.closed || typeof w.closed === 'undefined') { @@ -544,7 +546,7 @@ var ActionManager = Widget.extend({ options.on_close(); - return $.when(); + return Promise.resolve(); }, /** * Returns a description of the controllers in the given controller stack. @@ -653,14 +655,14 @@ var ActionManager = Widget.extend({ * @param {Object} action * @param {string} action.type * @param {Object} options - * @returns {Deferred} resolved when the action has been executed ; rejected + * @returns {Promise} resolved when the action has been executed ; rejected * if the type of action isn't supported, or if the action can't be * executed */ _handleAction: function (action, options) { if (!action.type) { console.error("No type for action", action); - return $.Deferred().reject(); + return Promise.reject(); } switch (action.type) { case 'ir.actions.act_url': @@ -674,7 +676,7 @@ var ActionManager = Widget.extend({ default: console.error("The ActionManager can't handle actions of type " + action.type, action); - return $.Deferred().reject(); + return Promise.reject(); } }, /** @@ -749,16 +751,17 @@ var ActionManager = Widget.extend({ * @private * @param {integer|string} action's ID or xml ID * @param {Object} context - * @returns {Deferred<Object>} resolved with the description of the action + * @returns {Promise<Object>} resolved with the description of the action */ _loadAction: function (actionID, context) { - var def = $.Deferred(); - this.trigger_up('load_action', { - actionID: actionID, - context: context, - on_success: def.resolve.bind(def), + var self = this; + return new Promise(function (resolve, reject) { + self.trigger_up('load_action', { + actionID: actionID, + context: context, + on_success: resolve, + }); }); - return def; }, /** * Preprocesses the action before it is handled by the ActionManager @@ -815,20 +818,20 @@ var ActionManager = Widget.extend({ * * @private * @param {string} controllerID - * @returns {Deferred} resolved when the controller has been restored + * @returns {Promise} resolved when the controller has been restored */ _restoreController: function (controllerID) { var self = this; var controller = this.controllers[controllerID]; // AAB: AbstractAction should define a proper hook to execute code when - // it is restored (other than do_show), and it should return a deferred + // it is restored (other than do_show), and it should return a promise var action = this.actions[controller.actionID]; var def; if (action.on_reverse_breadcrumb) { def = action.on_reverse_breadcrumb(); } - return $.when(def).then(function () { - return $.when(controller.widget.do_show()).then(function () { + return Promise.resolve(def).then(function () { + return Promise.resolve(controller.widget.do_show()).then(function () { var index = _.indexOf(self.controllerStack, controllerID); self._pushController(controller, index); }); @@ -844,7 +847,7 @@ var ActionManager = Widget.extend({ * * @private * @param {Object} controller - * @returns {Deferred<Object>} resolved with the controller when it is ready + * @returns {Promise<Object>} resolved with the controller when it is ready */ _startController: function (controller) { var fragment = document.createDocumentFragment(); diff --git a/addons/web/static/src/js/chrome/action_manager_act_window.js b/addons/web/static/src/js/chrome/action_manager_act_window.js index 106f9f2aa47..d00b3a49eb2 100644 --- a/addons/web/static/src/js/chrome/action_manager_act_window.js +++ b/addons/web/static/src/js/chrome/action_manager_act_window.js @@ -144,7 +144,7 @@ ActionManager.include({ * @param {integer} [options.index=0] the controller's index in the stack * @param {boolean} [options.lazy=false] set to true to differ the * initialization of the controller's widget - * @returns {Deferred<Object>} resolved with the created controller + * @returns {Promise<Object>} resolved with the created controller */ _createViewController: function (action, viewType, viewOptions, options) { var self = this; @@ -153,7 +153,7 @@ ActionManager.include({ // the requested view type isn't specified in the action (e.g. // action with list view only, user clicks on a row in the list, it // tries to switch to form view) - return $.Deferred().reject(); + return Promise.reject(); } options = options || {}; @@ -186,26 +186,31 @@ ActionManager.include({ // communication with trigger_up controllerID: controllerID, }); + var rejection; var view = new viewDescr.Widget(viewDescr.fieldsView, viewOptions); - var def = $.Deferred(); - action.controllers[viewType] = def; - view.getController(this).then(function (widget) { - if (def.state() === 'rejected') { - // the deferred has been rejected meanwhile, meaning that - // the action has been removed, so simply destroy the widget - widget.destroy(); - } else { - controller.widget = widget; - def.resolve(controller); - } - }).fail(def.reject.bind(def)); - def.fail(function () { + var def = new Promise(function (resolve, reject) { + rejection = reject; + view.getController(self).then(function (widget) { + if (def.rejected) { + // the promise has been rejected meanwhile, meaning that + // the action has been removed, so simply destroy the widget + widget.destroy(); + } else { + controller.widget = widget; + resolve(controller); + } + }).guardedCatch(reject); + }); + // Need to define an reject property to call it into _destroyWindowAction + def.reject = rejection; + def.guardedCatch(function () { + def.rejected = true; delete self.controllers[controllerID]; }); + action.controllers[viewType] = def; } else { - action.controllers[viewType] = $.Deferred().resolve(controller); + action.controllers[viewType] = Promise.resolve(controller); } - return action.controllers[viewType]; }, /** @@ -217,18 +222,22 @@ ActionManager.include({ */ _destroyWindowAction: function (action) { var self = this; - _.each(action.controllers, function (controllerDef) { + for (var c in action.controllers) { + var controllerDef = action.controllers[c]; controllerDef.then(function (controller) { delete self.controllers[controller.jsID]; if (controller.widget) { controller.widget.destroy(); } }); - // reject the deferred if it is not yet resolved, so that the - // controller is correctly destroyed as soon as it is ready, and - // its reference is removed - controllerDef.reject(); - }); + // If controllerDef is not resolved yet, reject it so that the + // controller will be correctly destroyed as soon as it'll be ready, + // and its reference will be removed. Lazy-loaded controllers do + // not have a reject function on their promise + if (controllerDef.reject) { + controllerDef.reject(); + } + } }, /** * Executes actions of type 'ir.actions.act_window'. @@ -239,13 +248,13 @@ ActionManager.include({ * @param {Object} options @see doAction for details * @param {integer} [options.resID] the current res ID * @param {string} [options.viewType] the view to open - * @returns {Deferred} resolved when the action is appended to the DOM + * @returns {Promise} resolved when the action is appended to the DOM */ _executeWindowAction: function (action, options) { var self = this; return this.dp.add(this._loadViews(action)).then(function (fieldsViews) { var views = self._generateActionViews(action, fieldsViews); - action._views = action.views; // save the initial attribute + action._views = action.views; // save the initial attribute action.views = views; action.controlPanelFieldsView = fieldsViews.search; action.controllers = {}; @@ -285,7 +294,7 @@ ActionManager.include({ self.controllerStack.push(lazyLoadedController.jsID); }); } - return self.dp.add($.when(lazyViewDef)) + return self.dp.add(Promise.resolve(lazyViewDef)) .then(function () { var viewOptions = { controllerState: options.controllerState, @@ -300,7 +309,7 @@ ActionManager.include({ action.controllerID = controller.jsID; return self._executeAction(action, options); }) - .fail(self._destroyWindowAction.bind(self, action)); + .guardedCatch(self._destroyWindowAction.bind(self, action)); }); }, /** @@ -389,7 +398,7 @@ ActionManager.include({ * * @private * @param {Object} action - * @returns {Deferred} + * @returns {Promise} */ _loadViews: function (action) { var inDialog = action.target === 'new'; @@ -443,11 +452,11 @@ ActionManager.include({ return this.clearUncommittedChanges().then(function () { // AAB: this will be done directly in AbstractAction's restore // function - var def; + var def = Promise.resolve(); if (action.on_reverse_breadcrumb) { def = action.on_reverse_breadcrumb(); } - return $.when(def).then(function () { + return Promise.resolve(def).then(function () { return self._switchController(action, controller.viewType); }); }); @@ -461,14 +470,14 @@ ActionManager.include({ * @private * @param {Object} controller the controller to switch to * @param {Object} [viewOptions] - * @return {Deferred} resolved when the new controller is in the DOM + * @return {Promise} resolved when the new controller is in the DOM */ _switchController: function (action, viewType, viewOptions) { var self = this; var view = _.findWhere(action.views, {type: viewType}); if (!view) { // can't switch to an unknown view - return $.Deferred().reject(); + return Promise.reject(); } var currentController = this.getCurrentController(); var index; @@ -519,28 +528,30 @@ ActionManager.include({ }; var controllerDef = action.controllers[viewType]; - if (!controllerDef || controllerDef.state() === 'rejected') { - // if the controllerDef is rejected, it probably means that the js - // code or the requests made to the server crashed. In that case, - // if we reuse the same deferred, then the switch to the view is - // definitely blocked. We want to use a new controller, even though - // it is very likely that it will recrash again. At least, it will - // give more feedback to the user, and it could happen that one - // record crashes, but not another. - controllerDef = newController(); - } else { + if (controllerDef) { controllerDef = controllerDef.then(function (controller) { if (!controller.widget) { // lazy loaded -> load it now (with same jsID) return newController(controller.jsID); } else { - return $.when(controller.widget.willRestore()).then(function () { + return Promise.resolve(controller.widget.willRestore()).then(function () { return controller.widget.reload(viewOptions).then(function () { return controller; }); }); } + }, function () { + // if the controllerDef is rejected, it probably means that the js + // code or the requests made to the server crashed. In that case, + // if we reuse the same promise, then the switch to the view is + // definitely blocked. We want to use a new controller, even though + // it is very likely that it will recrash again. At least, it will + // give more feedback to the user, and it could happen that one + // record crashes, but not another. + return newController(); }); + } else { + controllerDef = newController(); } return this.dp.add(controllerDef).then(function (controller) { @@ -577,11 +588,11 @@ ActionManager.include({ var env = ev.data.env; var context = new Context(env.context, actionData.context || {}); var recordID = env.currentID || null; // pyUtils handles null value, not undefined - var def = $.Deferred(); + var def; // determine the action to execute according to the actionData if (actionData.special) { - def = $.when({type: 'ir.actions.act_window_close', infos: 'special'}); + def = Promise.resolve({type: 'ir.actions.act_window_close', infos: 'special'}); } else if (actionData.type === 'object') { // call a Python Object method, which may return an action to execute var args = recordID ? [[recordID]] : [env.resIDs]; @@ -611,13 +622,15 @@ ActionManager.include({ active_ids: env.resIDs, active_id: recordID, })); + } else { + def = new Promise(function () {}); } // use the DropPrevious to prevent from executing the handler if another // request (doAction, switchView...) has been done meanwhile ; execute // the fail handler if the 'call_button' or 'loadAction' failed but not // if the request failed due to the DropPrevious, - def.fail(ev.data.on_fail); + def.guardedCatch(ev.data.on_fail); this.dp.add(def).then(function (action) { // show effect if button have effect attribute // rainbowman can be displayed from two places: from attribute on a button or from python diff --git a/addons/web/static/src/js/chrome/action_manager_report.js b/addons/web/static/src/js/chrome/action_manager_report.js index 51922cd719f..30bac14006d 100644 --- a/addons/web/static/src/js/chrome/action_manager_report.js +++ b/addons/web/static/src/js/chrome/action_manager_report.js @@ -40,35 +40,36 @@ ActionManager.include({ * report generation and download. * * @param {string} url - * @returns {Deferred} resolved when the report has been downloaded ; + * @returns {Promise} resolved when the report has been downloaded ; * rejected if something went wrong during the report generation */ _downloadReport: function (url) { + var self = this; framework.blockUI(); - var def = $.Deferred(); - var type = 'qweb-' + url.split('/')[2]; - var blocked = !session.get_file({ - url: '/report/download', - data: { - data: JSON.stringify([url, type]), - }, - success: def.resolve.bind(def), - error: function () { - crash_manager.rpc_error.apply(crash_manager, arguments); - def.reject(); - }, - complete: framework.unblockUI, + return new Promise(function (resolve, reject) { + var type = 'qweb-' + url.split('/')[2]; + var blocked = !session.get_file({ + url: '/report/download', + data: { + data: JSON.stringify([url, type]), + }, + success: resolve, + error: function () { + crash_manager.rpc_error.apply(crash_manager, arguments); + reject(); + }, + complete: framework.unblockUI, + }); + if (blocked) { + // AAB: this check should be done in get_file service directly, + // should not be the concern of the caller (and that way, get_file + // could return a promise) + var message = _t('A popup window with your report was blocked. You ' + + 'may need to change your browser settings to allow ' + + 'popup windows for this page.'); + self.do_warn(_t('Warning'), message, true); + } }); - if (blocked) { - // AAB: this check should be done in get_file service directly, - // should not be the concern of the caller (and that way, get_file - // could return a deferred) - var message = _t('A popup window with your report was blocked. You ' + - 'may need to change your browser settings to allow ' + - 'popup windows for this page.'); - this.do_warn(_t('Warning'), message, true); - } - return def; }, /** @@ -77,7 +78,7 @@ ActionManager.include({ * @private * @param {Object} action the description of the action to execute * @param {Object} options @see doAction for details - * @returns {Deferred} resolved when the action has been executed + * @returns {Promise} resolved when the action has been executed */ _triggerDownload: function (action, options, type){ var self = this; @@ -97,7 +98,7 @@ ActionManager.include({ * @private * @param {Object} action the description of the action to execute * @param {Object} options @see doAction for details - * @returns {Deferred} resolved when the action has been executed + * @returns {Promise} resolved when the action has been executed */ _executeReportAction: function (action, options) { var self = this; @@ -125,7 +126,7 @@ ActionManager.include({ } else { console.error("The ActionManager can't handle reports of type " + action.report_type, action); - return $.Deferred().reject(); + return Promise.reject(); } }, /** @@ -135,7 +136,7 @@ ActionManager.include({ * * @param {Object} action * @param {Object} options - * @returns {Deferred} resolved when the client action has been executed + * @returns {Promise} resolved when the client action has been executed */ _executeReportClientAction: function (action, options) { var urls = this._makeReportUrls(action); diff --git a/addons/web/static/src/js/chrome/action_mixin.js b/addons/web/static/src/js/chrome/action_mixin.js index 92d6716a7c0..02345e7e969 100644 --- a/addons/web/static/src/js/chrome/action_mixin.js +++ b/addons/web/static/src/js/chrome/action_mixin.js @@ -75,7 +75,7 @@ var ActionMixin = { * Called by the action manager when action is restored (typically, when the * user clicks on the action in the breadcrumb) * - * @returns {Deferred|undefined} + * @returns {Promise|undefined} */ willRestore: function () {}, @@ -89,14 +89,14 @@ var ActionMixin = { * example, if the user has edited a form, maybe we should ask him if we * can discard all his changes when we switch to another action. In that * case, the action manager will call this method. If the returned - * deferred is succesfully resolved, then we can destroy the current action, + * promise is succesfully resolved, then we can destroy the current action, * otherwise, we need to stop. * - * @returns {Deferred} resolved if the action can be removed, rejected + * @returns {Promise} resolved if the action can be removed, rejected * otherwise */ canBeRemoved: function () { - return $.when(); + return Promise.resolve(); }, /** * This function is called when the current state of the action diff --git a/addons/web/static/src/js/chrome/loading.js b/addons/web/static/src/js/chrome/loading.js index 6c7f102231b..865aa081bf0 100644 --- a/addons/web/static/src/js/chrome/loading.js +++ b/addons/web/static/src/js/chrome/loading.js @@ -26,6 +26,8 @@ var Loading = Widget.extend({ this._super(parent); this.count = 0; this.blocked_ui = false; + }, + start: function() { core.bus.on('rpc_request', this, this.request_call); core.bus.on("rpc_response", this, this.response_call); core.bus.on("rpc_response_failed", this, this.response_call); diff --git a/addons/web/static/src/js/chrome/menu.js b/addons/web/static/src/js/chrome/menu.js index 8722724c9a6..552dfce811c 100644 --- a/addons/web/static/src/js/chrome/menu.js +++ b/addons/web/static/src/js/chrome/menu.js @@ -64,20 +64,22 @@ var Menu = Widget.extend({ // Apps Menu this._appsMenu = new AppsMenu(self, this.menu_data); - this._appsMenu.appendTo(this.$menu_apps); + var appsMenuProm = this._appsMenu.appendTo(this.$menu_apps); // Systray Menu this.systray_menu = new SystrayMenu(this); - this.systray_menu.attachTo(this.$('.o_menu_systray')); - - dom.initAutoMoreMenu(this.$section_placeholder, { + var systrayMenuProm = this.systray_menu.attachTo(this.$('.o_menu_systray')).then(function() { + dom.initAutoMoreMenu(self.$section_placeholder, { maxWidth: function () { return self.$el.width() - (self.$menu_apps.outerWidth(true) + self.$menu_brand_placeholder.outerWidth(true) + self.systray_menu.$el.outerWidth(true)); }, sizeClass: 'SM', + }); }); - return this._super.apply(this, arguments); + + + return Promise.all([this._super.apply(this, arguments), appsMenuProm, systrayMenuProm]); }, change_menu_section: function (primary_menu_id) { if (!this.$menu_sections[primary_menu_id]) { diff --git a/addons/web/static/src/js/chrome/pager.js b/addons/web/static/src/js/chrome/pager.js index c7f22a6f2c9..86700d93374 100644 --- a/addons/web/static/src/js/chrome/pager.js +++ b/addons/web/static/src/js/chrome/pager.js @@ -28,7 +28,7 @@ var Pager = Widget.extend({ * @param {boolean} [options.can_edit] editable feature of the pager * @param {boolean} [options.single_page_hidden] (not) to display the pager * if only one page - * @param {function} [options.validate] callback returning a Deferred to + * @param {function} [options.validate] callback returning a Promise to * validate changes */ init: function (parent, size, current_min, limit, options) { @@ -46,7 +46,7 @@ var Pager = Widget.extend({ can_edit: true, // editable single_page_hidden: false, // displayed even if there is a single page validate: function() { - return $.Deferred().resolve(); + return Promise.resolve(); }, withAccessKey: true, // can be disabled, for example, for x2m widgets }); @@ -55,7 +55,7 @@ var Pager = Widget.extend({ /** * Renders the pager * - * @returns {jQuery.Deferred} + * @returns {Promise} */ start: function () { this.$value = this.$('.o_pager_value'); @@ -216,7 +216,6 @@ var Pager = Widget.extend({ } self.trigger('pager_changed', _.clone(self.state)); } - }).always(function() { // Render the pager's new state (removes the input) self._render(); }); diff --git a/addons/web/static/src/js/chrome/sidebar.js b/addons/web/static/src/js/chrome/sidebar.js index 5cf6005a788..b7883cbb0f7 100644 --- a/addons/web/static/src/js/chrome/sidebar.js +++ b/addons/web/static/src/js/chrome/sidebar.js @@ -153,7 +153,7 @@ var Sidebar = Widget.extend({ action_id: item.action.id, context: context, }, - }).done(function (result) { + }).then(function (result) { result.context = new Context( result.context || {}, activeIdsContext) .set_eval_context(context); diff --git a/addons/web/static/src/js/chrome/systray_menu.js b/addons/web/static/src/js/chrome/systray_menu.js index 24fd02a3736..fd214c0b650 100644 --- a/addons/web/static/src/js/chrome/systray_menu.js +++ b/addons/web/static/src/js/chrome/systray_menu.js @@ -16,20 +16,19 @@ var SystrayMenu = Widget.extend({ this._super(parent); this.items = []; this.widgets = []; - this.load = $.Deferred(); }, /** * @override - * @returns {Deferred} + * @returns {Promise} */ start: function () { var self = this; self._super.apply(this, arguments); self._loadItems(); - $.when.apply($, self.items).always(function () { - self.load.resolve(); + + return new Promise(function (resolve, reject) { + Promise.all(self.items).then(resolve).guardedCatch(resolve); }); - return self.load; }, //-------------------------------------------------------------------------- diff --git a/addons/web/static/src/js/chrome/user_menu.js b/addons/web/static/src/js/chrome/user_menu.js index a066b661fb2..2cc39b4d6bd 100644 --- a/addons/web/static/src/js/chrome/user_menu.js +++ b/addons/web/static/src/js/chrome/user_menu.js @@ -22,7 +22,7 @@ var UserMenu = Widget.extend({ /** * @override - * @returns {Deferred} + * @returns {Promise} */ start: function () { var self = this; @@ -36,7 +36,7 @@ var UserMenu = Widget.extend({ var $avatar = self.$('.oe_topbar_avatar'); if (!session.uid) { $avatar.attr('src', $avatar.data('default-src')); - return $.when(); + return Promise.resolve(); } var topbar_name = session.name; if (session.debug) { @@ -67,7 +67,7 @@ var UserMenu = Widget.extend({ .then(function (url) { framework.redirect(url); }) - .fail(function (result, ev){ + .guardedCatch(function (result, ev){ ev.preventDefault(); framework.redirect('https://accounts.odoo.com/account'); }); @@ -100,7 +100,7 @@ var UserMenu = Widget.extend({ model: "res.users", method: "action_get" }) - .done(function (result) { + .then(function (result) { result.res_id = session.uid; self.do_action(result); }); diff --git a/addons/web/static/src/js/chrome/web_client.js b/addons/web/static/src/js/chrome/web_client.js index 0688adc362f..392d534f372 100644 --- a/addons/web/static/src/js/chrome/web_client.js +++ b/addons/web/static/src/js/chrome/web_client.js @@ -111,7 +111,7 @@ return AbstractWebClient.extend({ instanciate_menu_widgets: function () { var self = this; - var defs = []; + var proms = []; return this.load_menus().then(function (menuData) { self.menu_data = menuData; @@ -122,8 +122,8 @@ return AbstractWebClient.extend({ self.menu.destroy(); } self.menu = new Menu(self, menuData); - defs.push(self.menu.prependTo(self.$el)); - return $.when.apply($, defs); + proms.push(self.menu.prependTo(self.$el)); + return Promise.all(proms); }); }, @@ -133,7 +133,7 @@ return AbstractWebClient.extend({ on_hashchange: function (event) { if (this._ignore_hashchange) { this._ignore_hashchange = false; - return $.when(); + return Promise.resolve(); } var self = this; @@ -181,48 +181,51 @@ return AbstractWebClient.extend({ return this.menu_dm.add(data_manager.load_action(ev.data.action_id)) .then(function (result) { return self.action_mutex.exec(function () { - var completed = $.Deferred(); - var options = _.extend({}, ev.data.options, { - clear_breadcrumbs: true, - action_menu_id: ev.data.menu_id, - }); - $.when(self._openMenu(result, options)).fail(function () { - completed.resolve(); - }).done(function () { - self._on_app_clicked_done(ev) - .then(completed.resolve.bind(completed)) - .fail(completed.reject.bind(completed)); + var completed = new Promise(function (resolve, reject) { + var options = _.extend({}, ev.data.options, { + clear_breadcrumbs: true, + action_menu_id: ev.data.menu_id, + }); + + Promise.resolve(self._openMenu(result, options)) + .then(function() { + self._on_app_clicked_done(ev) + .then(resolve) + .guardedCatch(reject); + }).guardedCatch(function() { + resolve(); + }); + setTimeout(function () { + resolve(); + }, 2000); }); - setTimeout(function () { - completed.resolve(); - }, 2000); return completed; }); }); }, _on_app_clicked_done: function (ev) { core.bus.trigger('change_menu_section', ev.data.menu_id); - return $.Deferred().resolve(); + return Promise.resolve(); }, on_menu_clicked: function (ev) { var self = this; return this.menu_dm.add(data_manager.load_action(ev.data.action_id)) .then(function (result) { - return self.action_mutex.exec(function () { - var completed = $.Deferred(); - $.when(self._openMenu(result, { - clear_breadcrumbs: true, - })).always(function () { - completed.resolve(); - }); + self.$el.removeClass('o_mobile_menu_opened'); - setTimeout(function () { - completed.resolve(); - }, 2000); + return self.action_mutex.exec(function () { + var completed = new Promise(function (resolve, reject) { + Promise.resolve(self._openMenu(result, { + clear_breadcrumbs: true, + })).then(resolve).guardedCatch(reject); + setTimeout(function () { + resolve(); + }, 2000); + }); return completed; }); - }).always(function () { + }).guardedCatch(function () { self.$el.removeClass('o_mobile_menu_opened'); }); }, @@ -233,7 +236,7 @@ return AbstractWebClient.extend({ * @private * @param {Object} action * @param {Object} options - * @returns {Deferred} + * @returns {Promise} */ _openMenu: function (action, options) { return this.do_action(action, options); diff --git a/addons/web/static/src/js/core/ajax.js b/addons/web/static/src/js/core/ajax.js index 509126498bc..8e7216c7b00 100644 --- a/addons/web/static/src/js/core/ajax.js +++ b/addons/web/static/src/js/core/ajax.js @@ -3,22 +3,19 @@ odoo.define('web.ajax', function (require) { var config = require('web.config'); var core = require('web.core'); -var utils = require('web.utils'); var time = require('web.time'); var download = require('web.download'); var contentdisposition = require('web.contentdisposition'); var _t = core._t; -function genericJsonRpc (fct_name, params, settings, fct) { +function _genericJsonRpc (fct_name, params, settings, fct) { var shadow = settings.shadow || false; delete settings.shadow; if (!shadow) { core.bus.trigger('rpc_request'); } - var deferred = $.Deferred(); - var data = { jsonrpc: "2.0", method: fct_name, @@ -26,7 +23,7 @@ function genericJsonRpc (fct_name, params, settings, fct) { id: Math.floor(Math.random() * 1000 * 1000 * 1000) }; var xhr = fct(data); - var result = xhr.pipe(function(result) { + var result = xhr.then(function(result) { core.bus.trigger('rpc:result', data, result); if (result.error !== undefined) { if (result.error.data.arguments[0] !== "bus.Bus not available in test mode") { @@ -36,65 +33,86 @@ function genericJsonRpc (fct_name, params, settings, fct) { console.error("Server application error", JSON.stringify(result.error)); } } - return $.Deferred().reject("server", result.error); + return Promise.reject({type: "server", error: result.error}); } else { return result.result; } }, function() { //console.error("JsonRPC communication error", _.toArray(arguments)); - var def = $.Deferred(); - return def.reject.apply(def, ["communication"].concat(_.toArray(arguments))); + var reason = { + type: 'communication', + error: arguments[0], + textStatus: arguments[1], + errorThrown: arguments[2], + }; + return Promise.reject(reason); }); - deferred.abort = function () { - deferred.reject({message: "XmlHttpRequestError abort"}, $.Event('abort')); - if (xhr.abort) { - xhr.abort(); - } - }; - result.then(function (result) { - if (!shadow) { - core.bus.trigger('rpc_response'); - } - deferred.resolve(result); - }, function (type, error, textStatus, errorThrown) { - if (type === "server") { + var rejection; + var promise = new Promise(function (resolve, reject) { + rejection = reject; + + result.then(function (result) { if (!shadow) { core.bus.trigger('rpc_response'); } - if (error.code === 100) { - core.bus.trigger('invalidate_session'); - } - deferred.reject(error, $.Event()); - } else { - if (!shadow) { - core.bus.trigger('rpc_response_failed'); + resolve(result); + }, function (reason) { + var type = reason.type; + var error = reason.error; + var textStatus = reason.textStatus; + var errorThrown = reason.errorThrown; + if (type === "server") { + if (!shadow) { + core.bus.trigger('rpc_response'); + } + if (error.code === 100) { + core.bus.trigger('invalidate_session'); + } + reject({message: error, event: $.Event()}); + } else { + if (!shadow) { + core.bus.trigger('rpc_response_failed'); + } + var nerror = { + code: -32098, + message: "XmlHttpRequestError " + errorThrown, + data: { + type: "xhr"+textStatus, + debug: error.responseText, + objects: [error, errorThrown] + }, + }; + reject({message: nerror, event: $.Event()}); } - var nerror = { - code: -32098, - message: "XmlHttpRequestError " + errorThrown, - data: { - type: "xhr"+textStatus, - debug: error.responseText, - objects: [error, errorThrown] - }, - }; - deferred.reject(nerror, $.Event()); - } + }); }); - deferred.fail(function () { // Allow deferred user to disable rpc_error call in fail - deferred.fail(function (error, event) { - if (!event.isDefaultPrevented()) { - core.bus.trigger('rpc_error', error, event); - } + + // FIXME: jsonp? + promise.abort = function () { + rejection({ + message: "XmlHttpRequestError abort", + event: $.Event('abort') }); + if (xhr.abort) { + xhr.abort(); + } + }; + promise.guardedCatch(function (reason) { // Allow promise user to disable rpc_error call in case of failure + setTimeout(function () { + // we want to execute this handler after all others (hence + // setTimeout) to let the other handlers prevent the event + if (!reason.event.isDefaultPrevented()) { + core.bus.trigger('rpc_error', reason.message, reason.event); + } + }, 0); }); - return deferred; -} + return promise; +}; function jsonRpc(url, fct_name, params, settings) { settings = settings || {}; - return genericJsonRpc(fct_name, params, settings, function(data) { + return _genericJsonRpc(fct_name, params, settings, function(data) { return $.ajax(url, _.extend({}, settings, { url: url, dataType: 'json', @@ -110,19 +128,6 @@ function rpc(url, params, settings) { return jsonRpc(url, 'call', params, settings); } -// helper -function realSetTimeout (fct, millis) { - var finished = new Date().getTime() + millis; - var wait = function() { - var current = new Date().getTime(); - if (current < finished) { - setTimeout(wait, finished - current); - } else { - fct(); - } - }; - setTimeout(wait, millis); -} /** * Load css asynchronously: fetch it from the url parameter and add a link tag @@ -131,26 +136,27 @@ function realSetTimeout (fct, millis) { * immediately. * * @param {String} url of the css to be fetched - * @returns {Deferred} resolved when the css has been loaded. + * @returns {Promise} resolved when the css has been loaded. */ var loadCSS = (function () { - var urlDefs = Object.create(null); + var urlDefs = {}; return function loadCSS(url) { if (url in urlDefs) { // nothing to do here } else if ($('link[href="' + url + '"]').length) { - // the link is already in the DOM, the deferred can be resolved - urlDefs[url] = $.when(); + // the link is already in the DOM, the promise can be resolved + urlDefs[url] = Promise.resolve(); } else { var $link = $('<link>', { 'href': url, 'rel': 'stylesheet', 'type': 'text/css' }); - urlDefs[url] = $.Deferred(); - $link.on('load', function () { - urlDefs[url].resolve(); + urlDefs[url] = new Promise(function (resolve, reject) { + $link.on('load', function () { + resolve(); + }); }); $('head').append($link); } @@ -159,44 +165,45 @@ var loadCSS = (function () { })(); var loadJS = (function () { - var urls = []; - var defs = []; + var dependenciesPromise = {}; var load = function loadJS(url) { // Check the DOM to see if a script with the specified url is already there var alreadyRequired = ($('script[src="' + url + '"]').length > 0); - // If loadJS was already called with the same URL, it will have a registered deferred indicating if - // the script has been fully loaded. If not, the deferred has to be initialized. This is initialized - // as already resolved if the script was already there without the need of loadJS. - var index = _.indexOf(urls, url); - if (index < 0) { - urls.push(url); - index = defs.push(alreadyRequired ? $.when() : $.Deferred()) - 1; + // If loadJS was already called with the same URL, it will have a registered promise indicating if + // the script has been fully loaded. If not, the promise has to be initialized. + // This is initialized as already resolved if the script was already there without the need of loadJS. + if (url in dependenciesPromise) { + return dependenciesPromise[url]; } + var scriptLoadedPromise = new Promise(function (resolve, reject) { + if (alreadyRequired) { + resolve(); + } else { + // Get the script associated promise and returns it after initializing the script if needed. The + // promise is marked to be resolved on script load and rejected on script error. + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = url; + script.onload = script.onreadystatechange = function() { + if ((script.readyState && script.readyState !== "loaded" && script.readyState !== "complete") || script.onload_done) { + return; + } + script.onload_done = true; + resolve(url); + }; + script.onerror = function () { + console.error("Error loading file", script.src); + reject(url); + }; + var head = document.head || document.getElementsByTagName('head')[0]; + head.appendChild(script); + } + }); - // Get the script associated deferred and returns it after initializing the script if needed. The - // deferred is marked to be resolved on script load and rejected on script error. - var def = defs[index]; - if (!alreadyRequired) { - var script = document.createElement('script'); - script.type = 'text/javascript'; - script.src = url; - script.onload = script.onreadystatechange = function() { - if ((script.readyState && script.readyState !== "loaded" && script.readyState !== "complete") || script.onload_done) { - return; - } - script.onload_done = true; - def.resolve(url); - }; - script.onerror = function () { - console.error("Error loading file", script.src); - def.reject(url); - }; - var head = document.head || document.getElementsByTagName('head')[0]; - head.appendChild(script); - } - return def; + dependenciesPromise[url] = scriptLoadedPromise; + return scriptLoadedPromise; }; return load; @@ -308,22 +315,6 @@ function get_file(options) { } function post (controller_url, data) { - - var progressHandler = function (deferred) { - return function (state) { - if(state.lengthComputable) { - deferred.notify({ - h_loaded: utils.human_size(state.loaded), - h_total : utils.human_size(state.total), - loaded : state.loaded, - total : state.total, - pcent : Math.round((state.loaded/state.total)*100) - }); - } - }; - }; - - var Def = $.Deferred(); var postData = new FormData(); $.each(data, function(i,val) { @@ -333,25 +324,20 @@ function post (controller_url, data) { postData.append('csrf_token', core.csrf_token); } - var xhr = new XMLHttpRequest(); - if(xhr.upload) xhr.upload.addEventListener('progress', progressHandler(Def), false); - - var ajaxDef = $.ajax(controller_url, { - xhr: function() {return xhr;}, - data: postData, - processData: false, - contentType: false, - type: 'POST' - }).then(function (data) {Def.resolve(data);}) - .fail(function (data) {Def.reject(data);}); - - return Def; + return new Promise(function (resolve, reject) { + $.ajax(controller_url, { + data: postData, + processData: false, + contentType: false, + type: 'POST' + }).then(resolve).fail(reject); + }); } /** * Loads an XML file according to the given URL and adds its associated qweb * templates to the given qweb engine. The function can also be used to get - * the deferred which indicates when all the calls to the function are finished. + * the promise which indicates when all the calls to the function are finished. * * Note: "all the calls" = the calls that happened before the current no-args * one + the calls that will happen after but when the previous ones are not @@ -359,8 +345,8 @@ function post (controller_url, data) { * * @param {string} [url] - an URL where to find qweb templates * @param {QWeb} [qweb] - the engine to which the templates need to be added - * @returns {Deferred} - * If no argument is given to the function, the deferred's state + * @returns {Promise} + * If no argument is given to the function, the promise's state * indicates if "all the calls" are finished (see main description). * Otherwise, it indicates when the templates associated to the given * url have been loaded. @@ -369,63 +355,64 @@ var loadXML = (function () { // Some "static" variables associated to the loadXML function var isLoading = false; var loadingsData = []; - var allLoadingsDef = $.when(); var seenURLs = []; return function (url, qweb) { - // If no argument, simply returns the deferred which indicates when + function _load() { + isLoading = true; + if (loadingsData.length) { + // There is something to load, load it, resolve the associated + // promise then start loading the next one + var loadingData = loadingsData[0]; + loadingData.qweb.add_template(loadingData.url, function () { + // Remove from array only now so that multiple calls to + // loadXML with the same URL returns the right promise + loadingsData.shift(); + loadingData.resolve(); + _load(); + }); + } else { + // There is nothing to load anymore, so resolve the + // "all the calls" promise + isLoading = false; + } + } + + // If no argument, simply returns the promise which indicates when // "all the calls" are finished if (!url || !qweb) { - return allLoadingsDef; + return Promise.resolve(); } // If the given URL has already been seen, do nothing but returning the - // associated deferred + // associated promise if (_.contains(seenURLs, url)) { var oldLoadingData = _.findWhere(loadingsData, {url: url}); - return oldLoadingData ? oldLoadingData.def : $.when(); + return oldLoadingData ? oldLoadingData.def : Promise.resolve(); } seenURLs.push(url); + // Add the information about the new data to load: the url, the qweb - // engine and the associated deferred + // engine and the associated promise var newLoadingData = { url: url, qweb: qweb, - def: $.Deferred(), }; + newLoadingData.def = new Promise(function (resolve, reject) { + newLoadingData.resolve = resolve; + newLoadingData.reject = reject; + }); loadingsData.push(newLoadingData); // If not already started, start the loading loop (reinitialize the - // "all the calls" deferred to an unresolved state) + // "all the calls" promise to an unresolved state) if (!isLoading) { - allLoadingsDef = $.Deferred(); _load(); } - // Return the deferred associated to the new given URL + // Return the promise associated to the new given URL return newLoadingData.def; - - function _load() { - isLoading = true; - if (loadingsData.length) { - // There is something to load, load it, resolve the associated - // deferred then start loading the next one - var loadingData = loadingsData[0]; - loadingData.qweb.add_template(loadingData.url, function () { - // Remove from array only now so that multiple calls to - // loadXML with the same URL returns the right deferred - loadingsData.shift(); - loadingData.def.resolve(); - _load(); - }); - } else { - // There is nothing to load anymore, so resolve the - // "all the calls" deferred - isLoading = false; - allLoadingsDef.resolve(); - } - } }; })(); @@ -444,7 +431,7 @@ var loadAsset = (function () { var load = function loadAsset(xmlId) { if (cache[xmlId]) { - return $.when(cache[xmlId]); + return Promise.resolve(cache[xmlId]); } var params = { args: [xmlId, { @@ -493,22 +480,20 @@ var loadAsset = (function () { * @param {Array<string>} [libs.assetLibs=[]] A list of xmlId. The loaded template * contains the script and link to be loaded * - * @returns {Deferred} + * @returns {Promise} */ function loadLibs (libs) { var defs = []; - _.each(libs.jsLibs || [], function (urls) { - defs.push($.when.apply($, defs).then(function () { + (libs.jsLibs || []).forEach(function (urls) { + defs.push(Promise.all(defs).then(function () { if (typeof(urls) === 'string') { return ajax.loadJS(urls); } else { - return $.when.apply($, _.map(urls, function (url) { - return ajax.loadJS(url); - })); + return Promise.all(urls.map(ajax.loadJS)); } })); }); - _.each(libs.cssLibs || [], function (url) { + (libs.cssLibs || []).forEach(function (url) { defs.push(ajax.loadCSS(url)); }); _.each(libs.assetLibs || [], function (xmlId) { @@ -516,7 +501,7 @@ function loadLibs (libs) { return loadLibs(asset); })); }); - return $.when.apply($, defs); + return Promise.all(defs); } var ajax = { diff --git a/addons/web/static/src/js/core/concurrency.js b/addons/web/static/src/js/core/concurrency.js index 6eeebe7ff58..716abafa919 100644 --- a/addons/web/static/src/js/core/concurrency.js +++ b/addons/web/static/src/js/core/concurrency.js @@ -8,7 +8,7 @@ odoo.define('web.concurrency', function (require) { * everything concurrency related in Odoo. * * The basic concurrency primitives in Odoo JS are the callback, and the - * promises. Promises (deferred) are more composable, so we usually use them + * promises. Promises (promise) are more composable, so we usually use them * whenever possible. We use the jQuery implementation. * * Those functions are really nothing special, but are simply the result of how @@ -19,54 +19,15 @@ var Class = require('web.Class'); return { /** - * The jquery implementation for $.when has a (most of the time) useful - * property: it is synchronous, if the deferred is resolved immediately. - * - * This means that when we execute $.when(def), then all registered - * callbacks will be executed before the next line is executed. This is - * useful quite often, but in some rare cases, we might want to force an - * async behavior. This is the purpose of this function, which simply adds a - * setTimeout before resolving the deferred. - * - * @returns {Deferred} - */ - asyncWhen: function () { - var async = false; - var def = $.Deferred(); - $.when.apply($, arguments).done(function() { - var args = arguments; - var action = function() { - def.resolve.apply(def, args); - }; - if (async) { - action(); - } else { - setTimeout(action, 0); - } - }).fail(function() { - var args = arguments; - var action = function() { - def.reject.apply(def, args); - }; - if (async) { - action(); - } else { - setTimeout(action, 0); - } - }); - async = true; - return def; - }, - /** - * Returns a deferred resolved after 'wait' milliseconds + * Returns a promise resolved after 'wait' milliseconds * * @param {int} [wait=0] the delay in ms - * @return {Deferred} + * @return {Promise} */ delay: function (wait) { - var def = $.Deferred(); - setTimeout(def.resolve, wait); - return def; + return new Promise(function (resolve) { + setTimeout(resolve, wait); + }); }, /** * The DropMisordered abstraction is useful for situations where you have @@ -93,27 +54,27 @@ return { this.failMisordered = failMisordered || false; }, /** - * Adds a deferred (usually an async request) to the sequencer + * Adds a promise (usually an async request) to the sequencer * - * @param {Deferred} deferred to ensure add - * @returns {Deferred} + * @param {Promise} promise to ensure add + * @returns {Promise} */ - add: function (deferred) { - var res = $.Deferred(); - - var self = this, seq = this.lsn++; - deferred.done(function () { - if (seq > self.rsn) { - self.rsn = seq; - res.resolve.apply(res, arguments); - } else if (self.failMisordered) { - res.reject(); - } - }).fail(function () { - res.reject.apply(res, arguments); + add: function (promise) { + var self = this; + var seq = this.lsn++; + var res = new Promise(function (resolve, reject) { + promise.then(function (result) { + if (seq > self.rsn) { + self.rsn = seq; + resolve(result); + } else if (self.failMisordered) { + reject(); + } + }).guardedCatch(function (result) { + reject(result); + }); }); - - return res.promise(); + return res; }, }), /** @@ -152,17 +113,26 @@ return { */ DropPrevious: Class.extend({ /** - * Registers a new deferred and rejects the previous one + * Registers a new promise and rejects the previous one * - * @param {Deferred} deferred the new deferred + * @param {Promise} promise the new promise * @returns {Promise} */ - add: function (deferred) { - if (this.current_def) { this.current_def.reject(); } - var res = $.Deferred(); - deferred.then(res.resolve, res.reject); - this.current_def = res; - return res.promise(); + add: function (promise) { + if (this.currentDef) { + this.currentDef.reject(); + } + var rejection; + var res = new Promise(function (resolve, reject) { + rejection = reject; + promise.then(resolve).catch(function (reason) { + reject(reason); + }); + }); + + this.currentDef = res; + this.currentDef.reject = rejection; + return res; } }), /** @@ -171,7 +141,7 @@ return { * state and cause some corrupted state. * * Imagine that we have a function to fetch some data _load(), which returns - * a deferred which resolves to something useful. Now, we have some code + * a promise which resolves to something useful. Now, we have some code * looking like this:: * * return this._load().then(function (result) { @@ -200,37 +170,51 @@ return { */ Mutex: Class.extend({ init: function () { - this.def = $.Deferred().resolve(); - this.unlockedDef = undefined; + this.lock = Promise.resolve(); + this.queueSize = 0; + this.unlockedProm = undefined; + this._unlock = undefined; }, /** * Add a computation to the queue, it will be executed as soon as the * previous computations are completed. * - * @param {function} action a function which may return a deferred - * @returns {Deferred} + * @param {function} action a function which may return a Promise + * @returns {Promise} */ exec: function (action) { var self = this; - var current = this.def; - var next = this.def = $.Deferred(); - this.unlockedDef = this.unlockedDef || $.Deferred(); - return current.then(function() { - return $.when(action()).always(function () { - next.resolve(); - if (self.def.state() === 'resolved' && self.unlockedDef) { - self.unlockedDef.resolve(); - self.unlockedDef = undefined; - } + var currentLock = this.lock; + var result; + this.queueSize++; + this.unlockedProm = this.unlockedProm || new Promise(function (resolve) { + self._unlock = resolve; + }); + this.lock = new Promise(function (unlockCurrent) { + currentLock.then(function () { + result = action(); + var always = function (returnedResult) { + unlockCurrent(); + self.queueSize--; + if (self.queueSize === 0) { + self.unlockedProm = undefined; + self._unlock(); + } + return returnedResult; + }; + Promise.resolve(result).then(always).guardedCatch(always); }); }); + return this.lock.then(function () { + return result; + }); }, /** * @returns {Promise} resolved as soon as the Mutex is unlocked * (directly if it is currently idle) */ getUnlockedDef: function () { - return $.when(this.unlockedDef); + return this.unlockedProm || Promise.resolve(); }, }), /** @@ -253,65 +237,86 @@ return { * * A MutexedDropPrevious has to be a class, and not a function, because we * have to keep track of some internal state. The exec function takes as - * argument an action (and not a deferred as DropPrevious for example) + * argument an action (and not a promise as DropPrevious for example) * because it's the MutexedDropPrevious role to trigger the RPC call that - * returns a deferred when it's time. + * returns a promise when it's time. */ MutexedDropPrevious: Class.extend({ init: function () { - this.currentDef = null; this.locked = false; + this.currentProm = null; this.pendingAction = null; - this.pendingDef = null; + this.pendingProm = null; }, /** - * @param {function} action a function which may return a deferred - * @returns {Deferred} + * @param {function} action a function which may return a promise + * @returns {Promise} */ exec: function (action) { var self = this; + var resolution; + var rejection; if (this.locked) { this.pendingAction = action; - var oldPendingDef = this.pendingDef; - var pendingDef = this.pendingDef = $.Deferred(); - if (oldPendingDef) { - oldPendingDef.reject(); - } - this.currentDef.reject(); - return pendingDef.promise(); + var oldPendingDef = this.pendingProm; + + this.pendingProm = new Promise(function (resolve, reject) { + resolution = resolve; + rejection = reject; + if (oldPendingDef) { + oldPendingDef.reject(); + } + self.currentProm.reject(); + }); + this.pendingProm.resolve = resolution; + this.pendingProm.reject = rejection; + return this.pendingProm; } else { this.locked = true; - this.currentDef = $.Deferred(); - $.when(action()) - .then(this.currentDef.resolve.bind(this.currentDef)) - .fail(this.currentDef.reject.bind(this.currentDef)) - .always(function () { + this.currentProm = new Promise(function (resolve, reject) { + resolution = resolve; + rejection = reject; + function unlock() { self.locked = false; if (self.pendingAction) { var action = self.pendingAction; + var prom = self.pendingProm; self.pendingAction = null; + self.pendingProm = null; self.exec(action) - .then(self.pendingDef.resolve.bind(self.pendingDef)) - .fail(self.pendingDef.reject.bind(self.pendingDef)); + .then(prom.resolve) + .guardedCatch(prom.reject); } + } + Promise.resolve(action()) + .then(function (result) { + resolve(result); + unlock(); + }) + .guardedCatch(function (reason) { + reject(reason); + unlock(); + }); }); - return this.currentDef.promise(); + this.currentProm.resolve = resolution; + this.currentProm.reject = rejection; + return this.currentProm; } - }, + } }), /** - * Rejects a deferred as soon as a reference deferred is either resolved or + * Rejects a promise as soon as a reference promise is either resolved or * rejected * - * @param {Deferred} [target_def] the deferred to potentially reject - * @param {Deferred} [reference_def] the reference target - * @returns {Deferred} + * @param {Promise} [target_def] the promise to potentially reject + * @param {Promise} [reference_def] the reference target + * @returns {Promise} */ rejectAfter: function (target_def, reference_def) { - var res = $.Deferred(); - target_def.then(res.resolve, res.reject); - reference_def.always(res.reject); - return res.promise(); + return new Promise(function (resolve, reject) { + target_def.then(resolve).guardedCatch(reject); + reference_def.then(reject).guardedCatch(reject); + }); } }; diff --git a/addons/web/static/src/js/core/dialog.js b/addons/web/static/src/js/core/dialog.js index 83eded92c8a..8630b6b9364 100644 --- a/addons/web/static/src/js/core/dialog.js +++ b/addons/web/static/src/js/core/dialog.js @@ -52,9 +52,11 @@ var Dialog = Widget.extend({ * (use this for non-editor frontend features) */ init: function (parent, options) { + var self = this; this._super(parent); - this._opened = $.Deferred(); - + this._opened = new Promise(function (resolve) { + self._openedResolver = resolve; + }); options = _.defaults(options || {}, { title: _t('Odoo'), subtitle: '', size: 'large', @@ -140,7 +142,7 @@ var Dialog = Widget.extend({ def = buttonData.click.call(self, e); } if (buttonData.close) { - $.when(def).always(self.close.bind(self)); + Promise.resolve(def).then(self.close.bind(self)).guardedCatch(self.close.bind(self)); } }); if (self.technical) { @@ -185,7 +187,7 @@ var Dialog = Widget.extend({ self.$modal.attr('open', true); self.$modal.removeAttr("aria-hidden"); self.$modal.modal('show'); - self._opened.resolve(); + self._openedResolver(); if (options && options.shouldFocusButtons) { self._onFocusControlButton(); } diff --git a/addons/web/static/src/js/core/dom.js b/addons/web/static/src/js/core/dom.js index b8a5b8be3a4..3faa5977c8c 100644 --- a/addons/web/static/src/js/core/dom.js +++ b/addons/web/static/src/js/core/dom.js @@ -1,11 +1,10 @@ odoo.define('web.dom_ready', function (require) { 'use strict'; -var def = $.Deferred(); -$(def.resolve.bind(def)); -return def; + return new Promise(function (resolve, reject) { + $(resolve); + }); }); - //============================================================================== odoo.define('web.dom', function (require) { @@ -33,7 +32,7 @@ var _t = core._t; * that on_attach_callback() will be called on each w with arguments args */ function _notify(content, callbacks) { - _.each(callbacks, function (c) { + callbacks.forEach(function (c) { if (c.widget && c.widget.on_attach_callback) { c.widget.on_attach_callback(c.callback_args); } @@ -170,14 +169,14 @@ var dom = { * @return {jQuery} the detached elements */ detach: function (to_detach, options) { - _.each(to_detach, function (d) { + to_detach.forEach( function (d) { if (d.widget.on_detach_callback) { d.widget.on_detach_callback(d.callback_args); } }); var $to_detach = options && options.$to_detach; if (!$to_detach) { - $to_detach = $(_.map(to_detach, function (d) { + $to_detach = $(to_detach.map(function (d) { return d.widget.el; })); } @@ -229,7 +228,7 @@ var dom = { * @param {function|boolean} stopPropagation */ makeAsyncHandler: function (fct, preventDefault, stopPropagation) { - // Create a deferred indicating if a previous call to this handler is + // Create a promise indicating if a previous call to this handler is // still pending. var def = $.when(); @@ -284,11 +283,13 @@ var dom = { // a 'real' debounce creation useless. Also, during the debouncing // part, the button is disabled without any visual effect. $button.addClass('o_debounce_disabled'); - $.when(dom.DEBOUNCE && concurrency.delay(dom.DEBOUNCE)).then(function () { + Promise.resolve(dom.DEBOUNCE && concurrency.delay(dom.DEBOUNCE)).then(function () { $button.addClass('disabled').prop('disabled', true); $button.removeClass('o_debounce_disabled'); - return $.when(result).always(function () { + return Promise.resolve(result).then(function () { + $button.removeClass('disabled').prop('disabled', false); + }).guardedCatch(function () { $button.removeClass('disabled').prop('disabled', false); }); }); diff --git a/addons/web/static/src/js/core/misc.js b/addons/web/static/src/js/core/misc.js index cd9a73ef22b..39fd8021071 100644 --- a/addons/web/static/src/js/core/misc.js +++ b/addons/web/static/src/js/core/misc.js @@ -114,7 +114,7 @@ function redirect (url, wait) { }; var wait_server = function() { - ajax.rpc("/web/webclient/version_info", {}).done(load).fail(function() { + ajax.rpc("/web/webclient/version_info", {}).then(load).guardedCatch(function () { setTimeout(wait_server, 250); }); }; @@ -172,7 +172,7 @@ core.action_registry.add("login", login); function logout() { redirect('/web/session/logout'); - return $.Deferred(); + return new Promise(); } core.action_registry.add("logout", logout); diff --git a/addons/web/static/src/js/core/mixins.js b/addons/web/static/src/js/core/mixins.js index e29c97ea086..228ac2d68dd 100644 --- a/addons/web/static/src/js/core/mixins.js +++ b/addons/web/static/src/js/core/mixins.js @@ -62,37 +62,36 @@ var ParentedMixin = { * Utility method to only execute asynchronous actions if the current * object has not been destroyed. * - * @param {$.Deferred} promise The promise representing the asynchronous + * @param {Promise} promise The promise representing the asynchronous * action. - * @param {bool} [reject=false] If true, the returned promise will be + * @param {bool} [shouldReject=false] If true, the returned promise will be * rejected with no arguments if the current * object is destroyed. If false, the * returned promise will never be resolved * or rejected. - * @returns {$.Deferred} A promise that will mirror the given promise if + * @returns {Promise} A promise that will mirror the given promise if * everything goes fine but will either be rejected * with no arguments or never resolved if the * current object is destroyed. */ - alive: function (promise, reject) { + alive: function (promise, shouldReject) { var self = this; - return $.Deferred(function (def) { - promise.then(function () { + + return new Promise(function (resolve, reject) { + promise.then(function (result) { if (!self.isDestroyed()) { - def.resolve.apply(def, arguments); + resolve(result); + } else if (shouldReject) { + reject(); } - }, function () { + }).guardedCatch(function (reason) { if (!self.isDestroyed()) { - def.reject.apply(def, arguments); - } - }).always(function () { - if (reject) { - // noop if def already resolved or rejected - def.reject(); + reject(reason); + } else if (shouldReject) { + reject(); } - // otherwise leave promise in limbo }); - }).promise(); + }); }, /** * Inform the object it should destroy itself, releasing any diff --git a/addons/web/static/src/js/core/mvc.js b/addons/web/static/src/js/core/mvc.js index cdef88c4211..6e0e4267caa 100644 --- a/addons/web/static/src/js/core/mvc.js +++ b/addons/web/static/src/js/core/mvc.js @@ -76,7 +76,7 @@ var Model = Class.extend(mixins.EventDispatcherMixin, ServicesMixin, { * @returns {Promise} The promise resolves to some kind of handle */ load: function () { - return $.when(); + return Promise.resolve(); }, }); @@ -119,9 +119,9 @@ var Controller = Widget.extend({ * @returns {Promise} */ start: function () { - return $.when( - this._super.apply(this, arguments), - this._startRenderer() + return Promise.all( + [this._super.apply(this, arguments), + this._startRenderer()] ); }, @@ -177,7 +177,8 @@ var Factory = Class.extend({ getController: function (parent) { var self = this; var model = this.getModel(parent); - return $.when(this._loadData(model), ajax.loadLibs(this)).then(function (state) { + return Promise.all([this._loadData(model), ajax.loadLibs(this)]).then(function (result) { + var state = result[0]; var renderer = self.getRenderer(parent, state); var Controller = self.Controller || self.config.Controller; var controllerParams = _.extend({ diff --git a/addons/web/static/src/js/core/rpc.js b/addons/web/static/src/js/core/rpc.js index cc56cb74ebb..21ec43e3d95 100644 --- a/addons/web/static/src/js/core/rpc.js +++ b/addons/web/static/src/js/core/rpc.js @@ -11,7 +11,7 @@ return { * * @param {Object} params @see buildQuery for a description * @param {Object} options - * @returns {Deferred<any>} + * @returns {Promise<any>} */ query: function (params, options) { var query = this.buildQuery(params); diff --git a/addons/web/static/src/js/core/service_mixins.js b/addons/web/static/src/js/core/service_mixins.js index e8088d45cb5..4f296b6bb90 100644 --- a/addons/web/static/src/js/core/service_mixins.js +++ b/addons/web/static/src/js/core/service_mixins.js @@ -124,7 +124,7 @@ var ServicesMixin = { return result; }, /** - * Builds and executes RPC query. Returns a deferred's promise resolved with + * Builds and executes RPC query. Returns a promise resolved with * the RPC result. * * @param {string} params either a route or a model @@ -133,11 +133,17 @@ var ServicesMixin = { */ _rpc: function (params, options) { var query = rpc.buildQuery(params); - var def = this.call('ajax', 'rpc', query.route, query.params, options, this) || $.Deferred(); - var promise = def.promise(); - var abort = (def.abort ? def.abort : def.reject) || function () {}; - promise.abort = abort.bind(def); - return promise; + var prom = this.call('ajax', 'rpc', query.route, query.params, options, this); + if (!prom) { + prom = new Promise(function () {}); + prom.abort = function () {}; + } + var abort = prom.abort ? prom.abort : prom.reject; + if (!abort) { + throw new Error("a rpc promise should always have a reject function"); + } + prom.abort = abort.bind(prom); + return prom; }, loadFieldView: function (modelName, context, view_id, view_type, options) { return this.loadViews(modelName, context, [[view_id, view_type]], options).then(function (result) { @@ -145,41 +151,45 @@ var ServicesMixin = { }); }, loadViews: function (modelName, context, views, options) { - var def = $.Deferred(); - this.trigger_up('load_views', { - modelName: modelName, - context: context, - views: views, - options: options, - on_success: def.resolve.bind(def), + var self = this; + return new Promise(function (resolve) { + self.trigger_up('load_views', { + modelName: modelName, + context: context, + views: views, + options: options, + on_success: resolve, + }); }); - return def; }, loadFilters: function (modelName, actionId, context) { - var def = $.Deferred(); - this.trigger_up('load_filters', { - modelName: modelName, - actionId: actionId, - context: context, - on_success: def.resolve.bind(def), + var self = this; + return new Promise(function (resolve, reject) { + self.trigger_up('load_filters', { + modelName: modelName, + actionId: actionId, + context: context, + on_success: resolve, + }); }); - return def; }, createFilter: function (filter) { - var def = $.Deferred(); - this.trigger_up('create_filter', { - filter: filter, - on_success: def.resolve.bind(def), + var self = this; + return new Promise(function (resolve, reject) { + self.trigger_up('create_filter', { + filter: filter, + on_success: resolve, + }); }); - return def; }, deleteFilter: function (filterId) { - var def = $.Deferred(); - this.trigger_up('delete_filter', { - filterId: filterId, - on_success: def.resolve.bind(def), + var self = this; + return new Promise(function (resolve, reject) { + self.trigger_up('delete_filter', { + filterId: filterId, + on_success: resolve, + }); }); - return def; }, // Session stuff getSession: function () { @@ -195,22 +205,22 @@ var ServicesMixin = { * Informs the action manager to do an action. This supposes that the action * manager can be found amongst the ancestors of the current widget. * If that's not the case this method will simply return an unresolved - * deferred. + * promise. * * @param {any} action * @param {any} options - * @returns {Deferred} + * @returns {Promise} */ do_action: function (action, options) { - var def = $.Deferred(); - - this.trigger_up('do_action', { - action: action, - options: options, - on_success: function (result) { def.resolve(result); }, - on_fail: function (result) { def.reject(result); }, + var self = this; + return new Promise(function (resolve, reject) { + self.trigger_up('do_action', { + action: action, + options: options, + on_success: resolve, + on_fail: reject, + }); }); - return def; }, do_notify: function (title, message, sticky, className) { return this.call('notification', 'notify', {title: title, message: message, sticky: sticky, className: className}); diff --git a/addons/web/static/src/js/core/session.js b/addons/web/static/src/js/core/session.js index 14886591d7b..2874a2ea559 100644 --- a/addons/web/static/src/js/core/session.js +++ b/addons/web/static/src/js/core/session.js @@ -84,27 +84,27 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { */ session_init: function () { var self = this; - var def = this.session_reload(); + var prom = this.session_reload(); if (this.is_frontend) { - return def.then(function () { + return prom.then(function () { return self.load_translations(); }); } - return def.then(function () { + return prom.then(function () { var modules = self.module_list.join(','); - var deferred = self.load_qweb(modules); + var promise = self.load_qweb(modules); if (self.session_is_valid()) { - return deferred.then(function () { return self.load_modules(); }); + return promise.then(function () { return self.load_modules(); }); } - return $.when( - deferred, + return Promise.all([ + promise, self.rpc('/web/webclient/bootstrap_translations', {mods: self.module_list}) .then(function (trans) { _t.database.set_bundle(trans); }) - ); + ]); }); }, session_is_valid: function () { @@ -119,7 +119,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { */ session_authenticate: function () { var self = this; - return $.when(this._session_authenticate.apply(this, arguments)).then(function () { + return Promise.resolve(this._session_authenticate.apply(this, arguments)).then(function () { return self.load_modules(); }); }, @@ -131,7 +131,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { var params = {db: db, login: login, password: password}; return this.rpc("/web/session/authenticate", params).then(function (result) { if (!result.uid) { - return $.Deferred().reject(); + return Promise.reject(); } _.extend(self, result); }); @@ -142,7 +142,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { }, user_has_group: function (group) { if (!this.uid) { - return $.when(false); + return Promise.resolve(false); } var def = this._groups_def[group]; if (!def) { @@ -196,22 +196,24 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { var to_load = _.difference(modules, self.module_list).join(','); this.module_list = all_modules; - var loaded = $.when(self.load_translations()); + var loaded = Promise.resolve(self.load_translations()); var locale = "/web/webclient/locale/" + self.user_context.lang || 'en_US'; var file_list = [ locale ]; if(to_load.length) { - loaded = $.when( + loaded = Promise.all([ loaded, - self.rpc('/web/webclient/csslist', {mods: to_load}).done(self.load_css.bind(self)), + self.rpc('/web/webclient/csslist', {mods: to_load}) + .then(self.load_css.bind(self)), self.load_qweb(to_load), - self.rpc('/web/webclient/jslist', {mods: to_load}).done(function (files) { - file_list = file_list.concat(files); - }) - ); + self.rpc('/web/webclient/jslist', {mods: to_load}) + .then(function (files) { + file_list = file_list.concat(files); + }) + ]); } return loaded.then(function () { return self.load_js(file_list); - }).done(function () { + }).then(function () { self.on_modules_loaded(); self.trigger('module_loaded'); }); @@ -227,24 +229,24 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { }, load_js: function (files) { var self = this; - var d = $.Deferred(); - if (files.length !== 0) { - var file = files.shift(); - var url = self.url(file, null); - ajax.loadJS(url).done(d.resolve); - } else { - d.resolve(); - } - return d; + return new Promise(function (resolve, reject) { + if (files.length !== 0) { + var file = files.shift(); + var url = self.url(file, null); + ajax.loadJS(url).then(resolve); + } else { + resolve(); + } + }); }, load_qweb: function (mods) { - this.qweb_mutex.exec(function () { + var lock = this.qweb_mutex.exec(function () { return $.get('/web/webclient/qweb?mods=' + mods).then(function (doc) { if (!doc) { return; } qweb.add_template(doc); }); }); - return this.qweb_mutex.def; + return lock; }, on_modules_loaded: function () { var openerp = window.openerp; @@ -276,12 +278,12 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { * (re)loads the content of a session: db name, username, user id, session * context and status of the support contract * - * @returns {$.Deferred} deferred indicating the session is done reloading + * @returns {Promise} promise indicating the session is done reloading */ session_reload: function () { var result = _.extend({}, window.odoo.session_info); _.extend(this, result); - return $.when(); + return Promise.resolve(); }, /** * Executes an RPC call, registering the provided callbacks. @@ -293,7 +295,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { * @param {String} url RPC endpoint * @param {Object} params call parameters * @param {Object} options additional options for rpc call - * @returns {jQuery.Deferred} jquery-provided ajax deferred + * @returns {Promise} */ rpc: function (url, params, options) { var self = this; diff --git a/addons/web/static/src/js/core/translation.js b/addons/web/static/src/js/core/translation.js index 76493fdb432..83c556c3c37 100644 --- a/addons/web/static/src/js/core/translation.js +++ b/addons/web/static/src/js/core/translation.js @@ -58,14 +58,14 @@ var TranslationDataBase = Class.extend(/** @lends instance.TranslationDataBase# @param {Object} [lang] lang The language. If not specified it will default to the language of the current user. @param {string} [url='/web/webclient/translations'] - @returns {jQuery.Deferred} + @returns {Promise} */ load_translations: function(session, modules, lang, url) { var self = this; return session.rpc(url || '/web/webclient/translations', { "mods": modules || null, "lang": lang || null - }).done(function(trans) { + }).then(function (trans) { self.set_bundle(trans); }); } diff --git a/addons/web/static/src/js/core/widget.js b/addons/web/static/src/js/core/widget.js index d3b9cc1033c..0de8c6aa1cd 100644 --- a/addons/web/static/src/js/core/widget.js +++ b/addons/web/static/src/js/core/widget.js @@ -31,7 +31,7 @@ var ServicesMixin = require('web.ServicesMixin'); * }, * willStart: function () { * // async work that need to be done before the widget is ready - * // this method should return a deferred + * // this method should return a promise * }, * start: function() { * // stuff you want to make after the rendering, `this.$el` holds a correct value @@ -135,45 +135,47 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, { * Method called between @see init and @see start. Performs asynchronous * calls required by the rendering and the start method. * - * This method should return a Deferred which is resolved when start can be + * This method should return a Promose which is resolved when start can be * executed. * - * @returns {Deferred} + * @returns {Promise} */ willStart: function () { - var defs = []; + var proms = []; if (this.xmlDependencies) { - defs.push.apply(defs, _.map(this.xmlDependencies, function (xmlPath) { + proms.push.apply(proms, _.map(this.xmlDependencies, function (xmlPath) { return ajax.loadXML(xmlPath, core.qweb); })); } if (this.jsLibs || this.cssLibs || this.assetLibs) { - defs.push(ajax.loadLibs(this)); + proms.push(ajax.loadLibs(this)); } - return $.when.apply($, defs); + return Promise.all(proms); }, /** * Method called after rendering. Mostly used to bind actions, perform * asynchronous calls, etc... * * By convention, this method should return an object that can be passed to - * $.when() to inform the caller when this widget has been initialized. + * Promise.resolve() to inform the caller when this widget has been initialized. * * Note that, for historic reasons, many widgets still do work in the start * method that would be more suited to the willStart method. * - * @returns {Deferred} + * @returns {Promise} */ start: function () { - return $.when(); + return Promise.resolve(); }, /** * Destroys the current widget, also destroys all its children before * destroying itself. */ destroy: function () { - _.invoke(this.getChildren(), 'destroy'); - if(this.$el) { + this.getChildren().forEach(function (child) { + child.destroy(); + }); + if (this.$el) { this.$el.remove(); } mixins.PropertiesMixin.destroy.call(this); @@ -187,6 +189,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, { * Renders the current widget and appends it to the given jQuery object. * * @param {jQuery} target + * @returns {Promise} */ appendTo: function (target) { var self = this; @@ -198,6 +201,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, { * Attach the current widget to a dom element * * @param {jQuery} target + * @returns {Promise} */ attachTo: function (target) { var self = this; @@ -234,6 +238,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, { * object. * * @param {jQuery} target + * @returns {Promise} */ insertAfter: function (target) { var self = this; @@ -246,6 +251,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, { * object. * * @param {jQuery} target + * @returns {Promise} */ insertBefore: function (target) { var self = this; @@ -257,6 +263,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, { * Renders the current widget and prepends it to the given jQuery object. * * @param {jQuery} target + * @returns {Promise} */ prependTo: function (target) { var self = this; @@ -282,6 +289,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, { * Renders the current widget and replaces the given jQuery object. * * @param target A jQuery object or a Widget instance. + * @returns {Promise} */ replace: function (target) { return this._widgetRenderAndInsert(_.bind(function (t) { @@ -415,7 +423,7 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, { * @private * @param {function: jQuery -> any} insertion * @param {jQuery} target - * @returns {Deferred} + * @returns {Promise} */ _widgetRenderAndInsert: function (insertion, target) { var self = this; diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js index edc7c03ac02..822c90c31f4 100644 --- a/addons/web/static/src/js/fields/abstract_field.js +++ b/addons/web/static/src/js/fields/abstract_field.js @@ -184,7 +184,7 @@ var AbstractField = Widget.extend({ * When a field widget is appended to the DOM, its start method is called, * and will automatically call render. Most widgets should not override this. * - * @returns {Deferred} + * @returns {Promise} */ start: function () { var self = this; @@ -236,7 +236,7 @@ var AbstractField = Widget.extend({ * if the value changed but was not notified. * * @abstract - * @returns {Deferred|undefined} + * @returns {Promise|undefined} */ commitChanges: function () {}, /** @@ -290,12 +290,12 @@ var AbstractField = Widget.extend({ * is optional, and may be used by a widget to share information from the * moment a field change event is triggered to the moment a reset * operation is applied. - * @returns {Deferred} A Deferred, which resolves when the widget rendering + * @returns {Promise} A promise, which resolves when the widget rendering * is complete */ reset: function (record, event) { this._reset(record, event); - return this._render() || $.when(); + return this._render() || Promise.resolve(); }, /** * Remove the invalid class on a field @@ -396,7 +396,7 @@ var AbstractField = Widget.extend({ * synchronous. * * @private - * @returns {Deferred|undefined} + * @returns {Promise|undefined} */ _render: function () { if (this.attrs.decorations) { @@ -413,7 +413,7 @@ var AbstractField = Widget.extend({ * concrete widget. * * @private - * @returns {Deferred|undefined} + * @returns {Promise|undefined} */ _renderEdit: function () { }, @@ -422,7 +422,7 @@ var AbstractField = Widget.extend({ * the concrete widget. * * @private - * @returns {Deferred|undefined} + * @returns {Promise|undefined} */ _renderReadonly: function () { }, @@ -457,13 +457,13 @@ var AbstractField = Widget.extend({ * will not notify and not trigger the onchange, even though it was changed. * @param {boolean} [options.forceChange=false] if true, the change event will be * triggered even if the new value is the same as the old one - * @returns {Deferred} + * @returns {Promise} */ _setValue: function (value, options) { // we try to avoid doing useless work, if the value given has not // changed. Note that we compare the unparsed values. if (this.lastSetValue === value || (this.value === false && value === '')) { - return $.when(); + return Promise.resolve(); } this.lastSetValue = value; try { @@ -472,25 +472,26 @@ var AbstractField = Widget.extend({ } catch (e) { this._isValid = false; this.trigger_up('set_dirty', {dataPointID: this.dataPointID}); - return $.Deferred().reject(); + return Promise.reject({message: "Value set is not valid"}); } if (!(options && options.forceChange) && this._isSameValue(value)) { - return $.when(); + return Promise.resolve(); } - var def = $.Deferred(); - var changes = {}; - changes[this.name] = value; - this.trigger_up('field_changed', { - dataPointID: this.dataPointID, - changes: changes, - viewType: this.viewType, - doNotSetDirty: options && options.doNotSetDirty, - notifyChange: !options || options.notifyChange !== false, - allowWarning: options && options.allowWarning, - onSuccess: def.resolve.bind(def), - onFailure: def.reject.bind(def), + var self = this; + return new Promise(function (resolve, reject) { + var changes = {}; + changes[self.name] = value; + self.trigger_up('field_changed', { + dataPointID: self.dataPointID, + changes: changes, + viewType: self.viewType, + doNotSetDirty: options && options.doNotSetDirty, + notifyChange: !options || options.notifyChange !== false, + allowWarning: options && options.allowWarning, + onSuccess: resolve, + onFailure: reject, + }); }); - return def; }, //-------------------------------------------------------------------------- diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index c6df3885a86..a87ff3e4436 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -213,7 +213,7 @@ var InputField = DebouncedField.extend({ // sure they are recomputed. this._applyDecorations(); } - return $.when(); + return Promise.resolve(); } else { return this._render(); } @@ -486,8 +486,6 @@ var LinkButton = AbstractField.extend({ }); - - var FieldDate = InputField.extend({ className: "o_field_date", tagName: "span", @@ -513,7 +511,7 @@ var FieldDate = InputField.extend({ */ start: function () { var self = this; - var def; + var prom; if (this.mode === 'edit') { this.datewidget = this._makeDatePicker(); this.datewidget.on('datetime_changed', this, function () { @@ -522,13 +520,13 @@ var FieldDate = InputField.extend({ this._setValue(value); } }); - def = this.datewidget.appendTo('<div>').done(function () { + prom = this.datewidget.appendTo('<div>').then(function () { self.datewidget.$el.addClass(self.$el.attr('class')); self._prepareInput(self.datewidget.$input); self._replaceElement(self.datewidget.$el); }); } - return $.when(def, this._super.apply(this, arguments)); + return Promise.resolve(prom).then(this._super.bind(this)); }, //-------------------------------------------------------------------------- @@ -728,7 +726,7 @@ var FieldMonetary = InputField.extend({ this.$el.empty(); // Prepare and add the input - this._prepareInput(this.$input).appendTo(this.$el); + var def = this._prepareInput(this.$input).appendTo(this.$el); if (this.currency) { // Prepare and add the currency symbol @@ -739,6 +737,7 @@ var FieldMonetary = InputField.extend({ this.$el.prepend($currencySymbol); } } + return def; }, /** * @override @@ -1136,7 +1135,7 @@ var FieldText = InputField.extend(TranslatableFieldMixin, { */ reset: function () { var self = this; - return $.when(this._super.apply(this, arguments)).then(function () { + return Promise.resolve(this._super.apply(this, arguments)).then(function () { if (self.mode === 'edit') { self.$input.trigger('change'); } @@ -2568,7 +2567,7 @@ var JournalDashboardGraph = AbstractField.extend({ if (this._isInDOM) { return this._renderInDOM(); } - return $.when(); + return Promise.resolve(); }, /** * Render the widget. This function assumes that it is attached to the DOM. @@ -2743,13 +2742,13 @@ var FieldDomain = AbstractField.extend({ /** * @private * @override _render from AbstractField - * @returns {Deferred} + * @returns {Promise} */ _render: function () { // If there is no model, only change the non-domain-selector content if (!this._domainModel) { this._replaceContent(); - return $.when(); + return Promise.resolve(); } // Convert char value to array value @@ -2901,7 +2900,7 @@ var AceEditor = DebouncedField.extend({ /** * @override start from AbstractField (Widget) * - * @returns {Deferred} + * @returns {Promise} */ start: function () { this._startAce(this.$('.ace-view-editor')[0]); diff --git a/addons/web/static/src/js/fields/field_registry.js b/addons/web/static/src/js/fields/field_registry.js index 35d10c5a5e4..0e0ba3dc117 100644 --- a/addons/web/static/src/js/fields/field_registry.js +++ b/addons/web/static/src/js/fields/field_registry.js @@ -6,7 +6,7 @@ var Registry = require('web.Registry'); return new Registry(); }); -odoo.define('web._field_registry', function(require) { +odoo.define('web._field_registry', function (require) { "use strict"; var AbstractField = require('web.AbstractField'); diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index bd46faf1e08..469cedb8ff1 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -171,11 +171,11 @@ var FieldMany2One = AbstractField.extend({ * is saved. * * @override - * @returns {Deferred} resolved as soon as there is no longer record being + * @returns {Promise} resolved as soon as there is no longer record being * (quick) created */ commitChanges: function () { - return $.when(this.createDef); + return Promise.resolve(this.createDef); }, /** * @override @@ -208,7 +208,7 @@ var FieldMany2One = AbstractField.extend({ this.isDirty = false; } if (this.isDirty) { - return $.when(); + return Promise.resolve(); } else { return this._render(); } @@ -257,9 +257,9 @@ var FieldMany2One = AbstractField.extend({ if (!source.validation || source.validation.call(self, req.term)) { source.loading = true; - // Wrap the returned value of the source.method with $.when. + // Wrap the returned value of the source.method with a promise // So event if the returned value is not async, it will work - $.when(source.method.call(self, req.term)).then(function (results) { + Promise.resolve(source.method.call(self, req.term)).then(function (results) { source.results = results; source.loading = false; resp(self._concatenateAutocompleteResults()); @@ -366,49 +366,55 @@ var FieldMany2One = AbstractField.extend({ /** * @private * @param {string} name - * @returns {Deferred} resolved after the name_create or when the slowcreate + * @returns {Promise} resolved after the name_create or when the slowcreate * modal is closed. */ _quickCreate: function (name) { var self = this; - var def = $.Deferred(); - this.createDef = this.createDef || $.Deferred(); - // called when the record has been quick created, or when the dialog has - // been closed (in the case of a 'slow' create), meaning that the job is - // done - var createDone = function () { - def.resolve(); - self.createDef.resolve(); - self.createDef = undefined; - }; - // called if the quick create is disabled on this many2one, or if the - // quick creation failed (probably because there are mandatory fields on - // the model) - var slowCreate = function () { - var dialog = self._searchCreatePopup("form", false, self._createContext(name)); - dialog.on('closed', self, createDone); - }; - if (this.nodeOptions.quick_create) { - var nameCreateDef = this._rpc({ - model: this.field.relation, - method: 'name_create', - args: [name], - context: this.record.getContext(this.recordParams), - }).fail(function (error, ev) { - ev.preventDefault(); - slowCreate(); + var createDone; + + var def = new Promise(function (resolve, reject) { + self.createDef = new Promise(function (innerResolve) { + // called when the record has been quick created, or when the dialog has + // been closed (in the case of a 'slow' create), meaning that the job is + // done + createDone = function () { + innerResolve(); + resolve(); + self.createDef = undefined; + }; }); - this.dp.add(nameCreateDef) - .then(function (result) { - if (self.mode === "edit") { - self.reinitialize({id: result[0], display_name: result[1]}); - } - createDone(); - }) - .fail(def.reject.bind(def)); - } else { - slowCreate(); - } + + // called if the quick create is disabled on this many2one, or if the + // quick creation failed (probably because there are mandatory fields on + // the model) + var slowCreate = function () { + var dialog = self._searchCreatePopup("form", false, self._createContext(name)); + dialog.on('closed', self, createDone); + }; + if (self.nodeOptions.quick_create) { + var nameCreateDef = self._rpc({ + model: self.field.relation, + method: 'name_create', + args: [name], + context: self.record.getContext(self.recordParams), + }).guardedCatch(function (reason) { + reason.event.preventDefault(); + slowCreate(); + }); + self.dp.add(nameCreateDef) + .then(function (result) { + if (self.mode === "edit") { + self.reinitialize({id: result[0], display_name: result[1]}); + } + createDone(); + }) + .guardedCatch(reject); + } else { + slowCreate(); + } + }); + return def; }, /** @@ -456,35 +462,32 @@ var FieldMany2One = AbstractField.extend({ * * @private * @param {string} search_val - * @returns {Deferred} + * @returns {Promise} */ _search: function (search_val) { var self = this; - var def = $.Deferred(); - this.orderer.add(def); - - var context = this.record.getContext(this.recordParams); - var domain = this.record.getDomain(this.recordParams); + var def = new Promise(function (resolve, reject) { + var context = self.record.getContext(self.recordParams); + var domain = self.record.getDomain(self.recordParams); - // Add the additionalContext - _.extend(context, this.additionalContext); + // Add the additionalContext + _.extend(context, self.additionalContext); - var blacklisted_ids = this._getSearchBlacklist(); - if (blacklisted_ids.length > 0) { - domain.push(['id', 'not in', blacklisted_ids]); - } + var blacklisted_ids = self._getSearchBlacklist(); + if (blacklisted_ids.length > 0) { + domain.push(['id', 'not in', blacklisted_ids]); + } - this._rpc({ - model: this.field.relation, - method: "name_search", - kwargs: { - name: search_val, - args: domain, - operator: "ilike", - limit: this.limit + 1, - context: context, - }}) - .then(function (result) { + self._rpc({ + model: self.field.relation, + method: "name_search", + kwargs: { + name: search_val, + args: domain, + operator: "ilike", + limit: self.limit + 1, + context: context, + }}).then(function (result) { // possible selections for the m2o var values = _.map(result, function (x) { x[1] = self._getDisplayName(x[1]); @@ -516,7 +519,7 @@ var FieldMany2One = AbstractField.extend({ }, }); } - $.when(prom).then(function (results) { + Promise.resolve(prom).then(function (results) { var dynamicFilters; if (results) { var ids = _.map(results, function (x) { @@ -563,9 +566,10 @@ var FieldMany2One = AbstractField.extend({ }); } - def.resolve(values); + resolve(values); }); - + }); + this.orderer.add(def); return def; }, /** @@ -670,8 +674,9 @@ var FieldMany2One = AbstractField.extend({ readonly: !self.can_write, on_saved: function (record, changed) { if (changed) { - self._setValue(self.value.data, {forceChange: true}); - self.trigger_up('reload', {db_id: self.value.id}); + self._setValue(self.value.data, {forceChange: true}).then(function() { + self.trigger_up('reload', {db_id: self.value.id}); + }); } }, }).open(); @@ -868,14 +873,14 @@ var FieldX2Many = AbstractField.extend({ * user if he wants to discard it if necessary. * * @override - * @returns {Deferred} + * @returns {Promise} */ commitChanges: function () { var self = this; var inEditionRecordID = - this.renderer - && this.renderer.viewType === "list" - && this.renderer.getEditableRecordID(); + this.renderer && + this.renderer.viewType === "list" && + this.renderer.getEditableRecordID(); if (inEditionRecordID) { return this.renderer.commitChanges(inEditionRecordID).then(function () { return self._saveLine(inEditionRecordID); @@ -894,7 +899,7 @@ var FieldX2Many = AbstractField.extend({ * @param {Object} record * @param {OdooEvent} [ev] an event that triggered the reset action * @param {Boolean} [fieldChanged] if true, the widget field has changed - * @returns {Deferred} + * @returns {Promise} */ reset: function (record, ev, fieldChanged) { // If 'fieldChanged' is false, it means that the reset was triggered by @@ -904,7 +909,7 @@ var FieldX2Many = AbstractField.extend({ if (!fieldChanged) { var newEval = this._evalColumnInvisibleFields(); if (_.isEqual(this.currentColInvisibleFields, newEval)) { - return $.when(); + return Promise.resolve(); } } else if (ev && ev.target === this && ev.data.changes && this.view.arch.tag === 'tree') { var command = ev.data.changes[this.name]; @@ -994,17 +999,20 @@ var FieldX2Many = AbstractField.extend({ * * @override * @private - * @returns {Deferred|undefined} + * @returns {Promise|undefined} */ _render: function () { + var self = this; if (!this.view) { return this._super(); } if (this.renderer) { this.currentColInvisibleFields = this._evalColumnInvisibleFields(); - this.renderer.updateState(this.value, {'columnInvisibleFields': this.currentColInvisibleFields}); - this.pager.updateState({ size: this.value.count }); - return $.when(); + return this.renderer.updateState(this.value, { + columnInvisibleFields: this.currentColInvisibleFields, + }).then(function () { + self.pager.updateState({ size: self.value.count }); + }); } var arch = this.view.arch; var viewType; @@ -1050,11 +1058,11 @@ var FieldX2Many = AbstractField.extend({ * Prepends the control panel's $el to this widget's $el. * * @private - * @returns {Deferred} + * @returns {Promise} */ _renderControlPanel: function () { if (!this.view) { - return $.when(); + return Promise.resolve(); } var self = this; var defs = []; @@ -1072,7 +1080,7 @@ var FieldX2Many = AbstractField.extend({ validate: function () { var isList = self.view.arch.tag === 'tree'; // TODO: we should have some common method in the basic renderer... - return isList ? self.renderer.unselectRow() : $.when(); + return isList ? self.renderer.unselectRow() : Promise.resolve(); }, }); this.pager.on('pager_changed', this, function (new_state) { @@ -1089,7 +1097,7 @@ var FieldX2Many = AbstractField.extend({ this._renderButtons(); defs.push(this.pager.appendTo($('<div>'))); // start the pager defs.push(cpDef); - return $.when.apply($, defs).then(function () { + return Promise.all(defs).then(function () { self._controlPanel.updateContents({ cp_content: { $buttons: self.$buttons, @@ -1123,24 +1131,24 @@ var FieldX2Many = AbstractField.extend({ * * @private * @param {string} recordID - * @returns {Deferred} resolved if the line was properly saved or discarded. + * @returns {Promise} resolved if the line was properly saved or discarded. * rejected if the line could not be saved and the user * did not agree to discard. */ _saveLine: function (recordID) { var self = this; - var def = $.Deferred(); - var fieldNames = this.renderer.canBeSaved(recordID); - if (fieldNames.length) { - this.trigger_up('discard_changes', { - recordID: recordID, - onSuccess: def.resolve.bind(def), - onFailure: def.reject.bind(def), - }); - } else { - this.renderer.setRowMode(recordID, 'readonly').done(def.resolve.bind(def)); - } - return def.then(function () { + return new Promise(function (resolve, reject) { + var fieldNames = self.renderer.canBeSaved(recordID); + if (fieldNames.length) { + self.trigger_up('discard_changes', { + recordID: recordID, + onSuccess: resolve, + onFailure: reject, + }); + } else { + self.renderer.setRowMode(recordID, 'readonly').then(resolve); + } + }).then(function () { self.pager.updateState({ size: self.value.count }); var newEval = self._evalColumnInvisibleFields(); if (!_.isEqual(self.currentColInvisibleFields, newEval)) { @@ -1233,7 +1241,7 @@ var FieldX2Many = AbstractField.extend({ this.trigger_up('edited_list', { id: this.value.id }); var editedRecord = this.value.data[ev.data.index]; this.renderer.setRowMode(editedRecord.id, 'edit') - .done(ev.data.onSuccess); + .then(ev.data.onSuccess); }, /** * Updates the given record with the changes. @@ -1260,11 +1268,11 @@ var FieldX2Many = AbstractField.extend({ operation: 'UPDATE', id: ev.data.dataPointID, data: changes, - }).done(function () { + }).then(function () { if (ev.data.onSuccess) { ev.data.onSuccess(); } - }).fail(function () { + }).guardedCatch(function () { if (ev.data.onFailure) { ev.data.onFailure(); } @@ -1332,8 +1340,8 @@ var FieldX2Many = AbstractField.extend({ self.trigger_up('mutexify', { action: function () { return self._saveLine(ev.data.recordID) - .done(ev.data.onSuccess) - .fail(ev.data.onFailure); + .then(ev.data.onSuccess) + .guardedCatch(ev.data.onFailure); }, }); }); @@ -1361,18 +1369,20 @@ var FieldX2Many = AbstractField.extend({ notifyChange: false, }); }); - $.when.apply($, defs).then(function () { + Promise.all(defs).then(function () { + function always() { + self.trigger_up('toggle_column_order', { + id: self.value.id, + name: event.data.handleField, + }); + } + // trigger only once the onchange for parent record self._setValue({ operation: 'UPDATE', id: rowID, data: _.object([event.data.handleField], [event.data.offset + rowIDs.length]), - }).always(function () { - self.trigger_up('toggle_column_order', { - id: self.value.id, - name: event.data.handleField, - }); - }); + }).then(always).guardedCatch(always); }); }, /** @@ -1418,7 +1428,7 @@ var FieldOne2Many = FieldX2Many.extend({ * @override * @param {Object} record * @param {OdooEvent} [ev] an event that triggered the reset action - * @returns {Deferred} + * @returns {Promise} */ reset: function (record, ev) { var self = this; @@ -1527,13 +1537,16 @@ var FieldOne2Many = FieldX2Many.extend({ context: data.context, }, { allowWarning: data.allowWarning - }).always(function () { + }).then(function () { self.creatingRecord = false; - }).done(function (){ + }).then(function (){ if (data.onSuccess){ data.onSuccess(); } - }); + }).guardedCatch(function() { + self.creatingRecord = false; + }) + ; } } else { this._openFormDialog({ @@ -1646,8 +1659,10 @@ var FieldMany2Many = FieldX2Many.extend({ domain: this.record.getDomain(this.recordParams), fields_view: this.attrs.views && this.attrs.views.form, on_saved: function () { - self._setValue({operation: 'TRIGGER_ONCHANGE'}, {forceChange: true}); - self.trigger_up('reload', {db_id: ev.data.id}); + self._setValue({operation: 'TRIGGER_ONCHANGE'}, {forceChange: true}) + .then(function () { + self.trigger_up('reload', {db_id: ev.data.id}); + }); }, on_remove: function () { self._setValue({operation: 'FORGET', ids: [ev.data.id]}); @@ -1918,10 +1933,12 @@ var FieldMany2ManyTags = AbstractField.extend({ * @override */ reset: function (record, event) { - this._super.apply(this, arguments); - if (event && event.target === this) { - this.activate(); - } + var self = this; + return this._super.apply(this, arguments).then(function(){ + if (event && event.target === self) { + self.activate(); + } + }); }, //-------------------------------------------------------------------------- diff --git a/addons/web/static/src/js/fields/upgrade_fields.js b/addons/web/static/src/js/fields/upgrade_fields.js index dd91ca2efc2..8dd1f9d3ae8 100644 --- a/addons/web/static/src/js/fields/upgrade_fields.js +++ b/addons/web/static/src/js/fields/upgrade_fields.js @@ -34,7 +34,7 @@ var AbstractFieldUpgrade = { * Redirects the user to the odoo-enterprise/uprade page * * @private - * @returns {Deferred} + * @returns {Promise} */ _confirmUpgrade: function () { return this._rpc({ diff --git a/addons/web/static/src/js/public/public_root.js b/addons/web/static/src/js/public/public_root.js index 7c508bd9192..97448408282 100644 --- a/addons/web/static/src/js/public/public_root.js +++ b/addons/web/static/src/js/public/public_root.js @@ -49,11 +49,11 @@ var PublicRoot = publicWidget.RootWidget.extend(ServiceProviderMixin, { */ willStart: function () { // TODO would be even greater to wait for localeDef only when necessary - return $.when( + return Promise.all([ this._super.apply(this, arguments), session.is_bound, localeDef - ); + ]); }, /** * @override @@ -82,7 +82,7 @@ var PublicRoot = publicWidget.RootWidget.extend(ServiceProviderMixin, { $('input, textarea').placeholder(); } - return $.when.apply($, defs); + return Promise.all(defs); }, //-------------------------------------------------------------------------- @@ -193,9 +193,9 @@ var PublicRoot = publicWidget.RootWidget.extend(ServiceProviderMixin, { self.publicWidgets.push(widget); return widget.attachTo($(el)); }); - return $.when.apply($, defs); + return Promise.all(defs); }); - return $.when.apply($, defs); + return Promise.all(defs); }, /** * Destroys all registered widget instances. Website would need this before @@ -259,8 +259,8 @@ var PublicRoot = publicWidget.RootWidget.extend(ServiceProviderMixin, { */ _onWidgetsStartRequest: function (ev) { this._startWidgets(ev.data.$target, ev.data.options) - .done(ev.data.onSuccess) - .fail(ev.data.onFailure); + .then(ev.data.onSuccess) + .guardedCatch(ev.data.onFailure); }, /** * Called when the root is notified that the public widgets have to be diff --git a/addons/web/static/src/js/public/public_widget.js b/addons/web/static/src/js/public/public_widget.js index ec9c50ad9a8..79382e117d8 100644 --- a/addons/web/static/src/js/public/public_widget.js +++ b/addons/web/static/src/js/public/public_widget.js @@ -43,7 +43,7 @@ var RootWidget = Widget.extend({ defs.push(this._attachComponents()); this._listenToUpdates = true; - return $.when.apply($, defs); + return Promise.all(defs); }, //-------------------------------------------------------------------------- @@ -71,7 +71,7 @@ var RootWidget = Widget.extend({ self._widgets.push(w); return w.attachTo(element); }); - return $.when.apply($, defs); + return Promise.all(defs); }, /** * Instantiates the child widgets that need to be according to the linked @@ -88,7 +88,7 @@ var RootWidget = Widget.extend({ var defs = _.map(childInfos, function (childInfo) { return self._attachComponent(childInfo, $from); }); - return $.when.apply($, defs); + return Promise.all(defs); }, /** * Returns the `RootWidgetRegistry` instance that is linked to this diff --git a/addons/web/static/src/js/report/client_action.js b/addons/web/static/src/js/report/client_action.js index 099ee77a527..c8b5621e6de 100644 --- a/addons/web/static/src/js/report/client_action.js +++ b/addons/web/static/src/js/report/client_action.js @@ -37,7 +37,7 @@ var ReportAction = AbstractAction.extend({ start: function () { var self = this; this.iframe = this.$('iframe')[0]; - return $.when(this._super.apply(this, arguments), session.is_bound).then(function () { + return Promise.all([this._super.apply(this, arguments), session.is_bound]).then(function () { var web_base_url = session['web.base.url']; var trusted_host = utils.get_host_from_url(web_base_url); var trusted_protocol = utils.get_protocol_from_url(web_base_url); @@ -105,7 +105,7 @@ var ReportAction = AbstractAction.extend({ /** * Helper allowing to send a message to the `this.el` iframe's window and - * seting the `targetOrigin` as `this.trusted_origin` (which is the + * seting the `targetOrigin` as `this.trusted_origin` (which is the * `web.base.url` ir.config_parameter key) - in other word, only when using * this method we only send the message to a trusted domain. */ diff --git a/addons/web/static/src/js/services/ajax_service.js b/addons/web/static/src/js/services/ajax_service.js index 0cecaae9da3..cb83dd9d553 100644 --- a/addons/web/static/src/js/services/ajax_service.js +++ b/addons/web/static/src/js/services/ajax_service.js @@ -7,19 +7,20 @@ var session = require('web.session'); var AjaxService = AbstractService.extend({ rpc: function (route, args, options, target) { - var def = $.Deferred(); - var promise = def.promise(); - var xhrDef = session.rpc(route, args, options); - promise.abort = xhrDef.abort.bind(xhrDef); - xhrDef.then(function () { - if (!target.isDestroyed()) { - def.resolve.apply(def, arguments); - } - }).fail(function () { - if (!target.isDestroyed()) { - def.reject.apply(def, arguments); - } + var rpcPromise; + var promise = new Promise(function (resolve, reject) { + rpcPromise = session.rpc(route, args, options); + rpcPromise.then(function (result) { + if (!target.isDestroyed()) { + resolve(result); + } + }).guardedCatch(function (reason) { + if (!target.isDestroyed()) { + reject(reason); + } + }); }); + promise.abort = rpcPromise.abort.bind(rpcPromise); return promise; }, }); diff --git a/addons/web/static/src/js/services/core.js b/addons/web/static/src/js/services/core.js index 3d6cfc4e684..ee37479e217 100644 --- a/addons/web/static/src/js/services/core.js +++ b/addons/web/static/src/js/services/core.js @@ -13,15 +13,15 @@ var translation = require('web.translation'); * * @type Boolean */ -var bus = new Bus (); +var bus = new Bus(); -_.each('click,dblclick,keydown,keypress,keyup'.split(','), function(evtype) { - $('html').on(evtype, function(ev) { +_.each('click,dblclick,keydown,keypress,keyup'.split(','), function (evtype) { + $('html').on(evtype, function (ev) { bus.trigger(evtype, ev); }); }); -_.each('resize,scroll'.split(','), function(evtype) { - $(window).on(evtype, function(ev) { +_.each('resize,scroll'.split(','), function (evtype) { + $(window).on(evtype, function (ev) { bus.trigger(evtype, ev); }); }); @@ -37,7 +37,7 @@ return { _lt: translation._lt, // registries - action_registry : new Registry(), + action_registry: new Registry(), crash_registry: new Registry(), serviceRegistry: new Registry(), /** diff --git a/addons/web/static/src/js/services/crash_manager.js b/addons/web/static/src/js/services/crash_manager.js index db88d688505..a29e21ca8ca 100644 --- a/addons/web/static/src/js/services/crash_manager.js +++ b/addons/web/static/src/js/services/crash_manager.js @@ -65,7 +65,7 @@ var CrashManager = core.Class.extend({ ajax.jsonRpc('/web/webclient/version_info', 'call', {}, {shadow:true}).then(function () { core.bus.trigger('connection_restored'); self.isConnected = true; - }).fail(function () { + }).guardedCatch(function () { // exponential backoff, with some jitter delay = (delay * 1.5) + 500*Math.random(); setTimeout(checkConnection, delay); diff --git a/addons/web/static/src/js/services/data_manager.js b/addons/web/static/src/js/services/data_manager.js index 348054691bc..b2f6ef1feb1 100644 --- a/addons/web/static/src/js/services/data_manager.js +++ b/addons/web/static/src/js/services/data_manager.js @@ -33,7 +33,7 @@ return core.Class.extend({ * * @param {int|string} [action_id] the action id or xmlid * @param {Object} [additional_context] used to load the action - * @return {Deferred} resolved with the action whose id or xmlid is action_id + * @return {Promise} resolved with the action whose id or xmlid is action_id */ load_action: function (action_id, additional_context) { var self = this; @@ -69,7 +69,7 @@ return core.Class.extend({ * - options.load_filters: whether or not to load the filters, * - options.action_id: the action_id (required to load filters), * - options.toolbar: whether or not a toolbar will be displayed, - * @return {Deferred} resolved with the requested views information + * @return {Promise} resolved with the requested views information */ load_views: function (params, options) { var self = this; @@ -108,12 +108,12 @@ return core.Class.extend({ var fvg = result.fields_views[view_descr[1]]; fvg.viewFields = fvg.fields; fvg.fields = result.fields; - self._cache.fields_views[fv_key] = $.when(fvg); + self._cache.fields_views[fv_key] = Promise.resolve(fvg); }); // Insert filters, if any, into the filters cache if (result.filters) { - self._cache.filters[filters_key] = $.when(result.filters); + self._cache.filters[filters_key] = Promise.resolve(result.filters); } return result.fields_views; @@ -130,7 +130,7 @@ return core.Class.extend({ * @param {string} params.modelName * @param {Object} params.context * @param {integer} params.actionId - * @return {Deferred} resolved with the requested filters + * @return {Promise} resolved with the requested filters */ load_filters: function (params) { var key = this._gen_key(params.modelName, params.actionId); @@ -143,7 +143,7 @@ return core.Class.extend({ }, model: 'ir.filters', method: 'get_filters', - }).fail(this._invalidate.bind(this, this._cache.filters, key)); + }).guardedCatch(this._invalidate.bind(this, this._cache.filters, key)); } return this._cache.filters[key]; }, @@ -152,7 +152,7 @@ return core.Class.extend({ * Calls 'create_or_replace' on 'ir_filters'. * * @param {Object} [filter] the filter description - * @return {Deferred} resolved with the id of the created or replaced filter + * @return {Promise} resolved with the id of the created or replaced filter */ create_filter: function (filter) { var self = this; @@ -175,7 +175,7 @@ return core.Class.extend({ * Calls 'unlink' on 'ir_filters'. * * @param {integer} filterId Id of the filter to remove - * @return {Deferred} + * @return {Promise} */ delete_filter: function (filterId) { var self = this; diff --git a/addons/web/static/src/js/services/report_service.js b/addons/web/static/src/js/services/report_service.js index 006c546041a..ed4907a6d6d 100644 --- a/addons/web/static/src/js/services/report_service.js +++ b/addons/web/static/src/js/services/report_service.js @@ -15,7 +15,7 @@ var ReportService = AbstractService.extend({ * Checks the state of the installation of wkhtmltopdf on the server. * Implements an internal cache to do the request only once. * - * @returns {Deferred} resolved with the state of wkhtmltopdf on the server + * @returns {Promise} resolved with the state of wkhtmltopdf on the server * (possible values are 'ok', 'broken', 'install', 'upgrade', 'workers'). */ checkWkhtmltopdf: function () { diff --git a/addons/web/static/src/js/tools/debug_manager.js b/addons/web/static/src/js/tools/debug_manager.js index f09d8745f04..172148e03ef 100644 --- a/addons/web/static/src/js/tools/debug_manager.js +++ b/addons/web/static/src/js/tools/debug_manager.js @@ -62,7 +62,7 @@ var DebugManager = Widget.extend({ this._has_features = false; // whether the current user is an administrator this._is_admin = session.is_system; - return $.when( + return Promise.resolve( this._rpc({ model: 'res.users', method: 'check_access_rights', @@ -160,7 +160,7 @@ var DebugManager = Widget.extend({ .append(QWeb.render('WebClient.DebugManager.Global', { manager: this, })); - return $.when(); + return Promise.resolve(); }, select_view: function () { var self = this; @@ -281,7 +281,7 @@ DebugManager.include({ model: 'ir.model', method: 'search', args: [[['model', '=', model]]] - }).done(function (ids) { + }).then(function (ids) { self.do_action({ res_model: 'ir.model.fields', name: _t('View Fields'), @@ -324,7 +324,7 @@ DebugManager.include({ DebugManager.include({ start: function () { this._can_edit_views = false; - return $.when( + return Promise.all([ this._super(), this._rpc({ model: 'ir.ui.view', @@ -334,6 +334,7 @@ DebugManager.include({ .then(function (ar) { this._can_edit_views = ar; }.bind(this)) + ] ); }, update: function (tag, descriptor, widget) { @@ -382,7 +383,7 @@ DebugManager.include({ model: this._action.res_model, method: 'get_metadata', args: [selectedIDs], - }).done(function(result) { + }).then(function(result) { var metadata = result[0]; metadata.creator = field_utils.format.many2one(metadata.create_uid); metadata.lastModifiedBy = field_utils.format.many2one(metadata.write_uid); @@ -515,7 +516,7 @@ DebugManager.include({ true, condition || false, ], - }).done(function () { d.close(); }); + }).then(function () { d.close(); }); }} ] }); diff --git a/addons/web/static/src/js/tools/test_menus.js b/addons/web/static/src/js/tools/test_menus.js index 4e31b2d4467..d950d5d56ee 100644 --- a/addons/web/static/src/js/tools/test_menus.js +++ b/addons/web/static/src/js/tools/test_menus.js @@ -65,16 +65,16 @@ } console.log('Found ', $listOfAppMenuItems.length, 'apps to test'); - var testDef = $.when(); - testDef = chainDeferred($listOfAppMenuItems, testDef, testApp); - return testDef.then(function() { + var testPromise = Promise.resolve(); + testPromise = chainDeferred($listOfAppMenuItems, testPromise, testApp); + return testPromise.then(function () { console.log("Successfully tested ", testedApps.length, " apps"); console.log("Successfully tested ", testedMenus.length - testedApps.length, " menus"); console.log("test successful"); - }).always(function() { - console.log("Test took ", (performance.now() - startTime)/1000, " seconds"); - }).fail(function () { - console.error("test failed"); + console.log("Test took ", (performance.now() - startTime) / 1000, " seconds"); + }).catch(function () { + console.log("Test took ", (performance.now() - startTime) / 1000, " seconds"); + console.error("Error !"); }); } @@ -86,18 +86,18 @@ * 3 - clicking on each menu * 3.1 - clicking on each view * @param {DomElement} element: the App menu item - * @returns {Deferred} + * @returns {Promise} */ function testApp(element){ console.log("Testing app menu:", element.dataset.menuXmlid); - if (testedApps.indexOf(element.dataset.menuXmlid) >= 0) return $.Deferred().resolve(); // Another infinite loop protection + if (testedApps.indexOf(element.dataset.menuXmlid) >= 0) return Promise.resolve(); // Another infinite loop protection testedApps.push(element.dataset.menuXmlid); return testMenuItem(element).then(function () { var $subMenuItems; $subMenuItems = $('.o_menu_entry_lvl_1, .o_menu_entry_lvl_2, .o_menu_entry_lvl_3, .o_menu_entry_lvl_4'); - var testMenuDef = $.when(); - testMenuDef = chainDeferred($subMenuItems, testMenuDef, testMenuItem); - return testMenuDef; + var testMenuPromise = Promise.resolve(); + testMenuPromise = chainDeferred($subMenuItems, testMenuPromise, testMenuItem); + return testMenuPromise; }).then(function(){ // no effect in community var $homeMenu = $("nav.o_main_navbar > a.o_menu_toggle.fa-th"); @@ -112,10 +112,10 @@ * 2 - Orchestrate the view switch * * @param {DomElement} element: the menu item - * @returns {Deferred} + * @returns {Promise} */ function testMenuItem(element){ - if (testedMenus.indexOf(element.dataset.menuXmlid) >= 0) return $.Deferred().resolve(); // Avoid infinite loop + if (testedMenus.indexOf(element.dataset.menuXmlid) >= 0) return Promise.resolve(); // Avoid infinite loop console.log("Testing menu", element.innerText.trim(), " ", element.dataset.menuXmlid); testedMenus.push(element.dataset.menuXmlid); var startActionCount = clientActionCount; @@ -128,7 +128,7 @@ $modal.modal('hide'); isModal = true; return true; - }; + } return startActionCount != clientActionCount; }).then(function() { if (!isModal) { @@ -138,7 +138,7 @@ if (!isModal) { return testViews(); } - }).fail(function() { + }).catch(function () { console.error("Error while testing", element); }); }; @@ -151,21 +151,21 @@ */ function testViews() { var $switches = $("nav.o_cp_switch_buttons > button:not(.active):visible"); - var testSwitchDef = $.when(); - // chainDeferred($switches, testSwitchDef, testViewSwitch # FIXME + var testSwitchPromise = Promise.resolve(); + // chainDeferred($switches, testSwitchPromise, testViewSwitch # FIXME _.each($switches, function(switchButton) { - testSwitchDef = testSwitchDef.then(function () { + testSwitchPromise = testSwitchPromise.then(function () { // get the view view-type data attribute return testViewSwitch(switchButton.dataset.viewType); }); }); - return testSwitchDef; + return testSwitchPromise; } /** * Test a view button * @param {string} viewType: a string for the view type to test (list, kanban ...) - * @returns {Deferred} a deferred that wait for the view to be loaded + * @returns {Promise} a promise that wait for the view to be loaded */ function testViewSwitch(viewType){ console.log("Testing view switch: ", viewType); @@ -188,20 +188,20 @@ * Click on each filter in the control pannel */ function testFilters() { - var filterDef = $.when(); + var filterProm = Promise.resolve(); // var $filters = $('div.o_control_panel div.btn-group.o_dropdown > ul.o_filters_menu > li:not(.o_add_custom_filter)'); var $filters = $('.o_filters_menu > .o_menu_item'); console.log("Testing " + $filters.length + " filters"); var filter_ids = _.compact(_.map($filters, function(f) { return f.dataset.id})); _.each(filter_ids, function(filter_id){ - filterDef = filterDef.then(function(){ + filterProm = filterProm.then(function(){ var currentViewCount = viewUpdateCount; var $filter = $('.o_menu_item[data-id="' + filter_id + '"] a'); // with some customized search views, the filter cannot be found if ($filter[0] === undefined) { console.warn('Filter with ID ', filter_id , 'cannot be found'); - return $.Deferred().resolve(); - }; + return Promise.resolve(); + } console.log('Clicking on filter "', $filter.text().trim(), '"'); $filter[0].click(); setTimeout(function() { @@ -220,52 +220,55 @@ }); }); }); - return filterDef; + return filterProm; } // utility functions /** * Wait a certain amount of time for a condition to occur - * @param stopCondition: a function that returns a boolean - * @returns {Deferred} that is rejected if the timeout is exceeded + * @param {function} stopCondition a function that returns a boolean + * @returns {Promise} that is rejected if the timeout is exceeded */ function waitForCondition(stopCondition) { - var def = $.Deferred(); - var interval = 250; - var timeLimit = 15000; - function checkCondition() { - if (stopCondition()) { - def.resolve(); - } else { - timeLimit -= interval; - if (timeLimit > 0) { - // recursive call until the resolve or the timeout - setTimeout(checkCondition, interval); + var prom = new Promise(function (resolve, reject) { + var interval = 250; + var timeLimit = 15000; + + function checkCondition() { + if (stopCondition()) { + resolve(); } else { - console.error("Timeout exceeded", stopCondition); - def.reject(); + timeLimit -= interval; + if (timeLimit > 0) { + // recursive call until the resolve or the timeout + setTimeout(checkCondition, interval); + } else { + console.error("Timeout exceeded", stopCondition); + reject(); + } } } - } - setTimeout(checkCondition, interval); - return def; - }; + setTimeout(checkCondition, interval); + }); + return prom; + } /** - * chain deferred actions - * @param $elements: a list of jquery elements to be passed as arg to the function - * @param deferred: the deferred on which other deferreds will be chained - * @param f: the function to be deferred - * @returns : the chained deferred + * Chain deferred actions. + * + * @param {jQueryElement} $elements a list of jquery elements to be passed as arg to the function + * @param {Promise} promise the promise on which other promises will be chained + * @param {function} f the function to be deferred + * @returns {Promise} the chained promise */ - function chainDeferred($elements, deferred, f) { + function chainDeferred($elements, promise, f) { _.each($elements, function(el) { - deferred = deferred.then(function () { + promise = promise.then(function () { return f(el); }); }); - return deferred; + return promise; } diff --git a/addons/web/static/src/js/views/abstract_controller.js b/addons/web/static/src/js/views/abstract_controller.js index b5d7c8fe8b3..0c251874480 100644 --- a/addons/web/static/src/js/views/abstract_controller.js +++ b/addons/web/static/src/js/views/abstract_controller.js @@ -62,7 +62,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { /** * Simply renders and updates the url. * - * @returns {Deferred} + * @returns {Promise} */ start: function () { var self = this; @@ -70,12 +70,16 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { this.$el.addClass('o_view_controller'); return this._super.apply(this, arguments).then(function () { + var prom; if (self._controlPanel) { // render the ControlPanel elements (buttons, pager, sidebar...) - self.controlPanelElements = self._renderControlPanelElements(); - self._controlPanel.$el.prependTo(self.$el); + prom = self._renderControlPanelElements().then(function (elements) { + self.controlPanelElements = elements; + self._controlPanel.$el.prependTo(self.$el); + }); } - + return Promise.resolve(prom); + }).then(function () { return self._update(self.initialState); }); }, @@ -119,7 +123,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { */ canBeRemoved: function () { // AAB: get rid of this option when on_hashchange mechanism is improved - return this.discardChanges(undefined, {readonlyIfRealDiscard: true}); + return this.discardChanges(undefined, { readonlyIfRealDiscard: true }); }, /** * Discards the changes made on the record associated to the given ID, or @@ -128,15 +132,15 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { * this method on the active view to make sure it is ok to open the home * screen (and lose all current state). * - * Note that it returns a deferred, because the view could choose to ask the + * Note that it returns a Promise, because the view could choose to ask the * user if he agrees to discard. * * @param {string} [recordID] * if not given, we consider all the changes made by the controller - * @returns {Deferred} resolved if properly discarded, rejected otherwise + * @returns {Promise} resolved if properly discarded, rejected otherwise */ discardChanges: function (recordID) { - return $.when(); + return Promise.resolve(); }, /** * Export the state of the controller containing information that is shared @@ -155,7 +159,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { /** * Gives the focus to the renderer */ - giveFocus: function() { + giveFocus: function () { this.renderer.giveFocus(); }, /** @@ -174,7 +178,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { * Short helper method to reload the view * * @param {Object} [params] This object will simply be given to the update - * @returns {Deferred} + * @returns {Promise} */ reload: function (params) { params = params || {}; @@ -186,7 +190,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { params = _.extend({}, params, searchQuery); }); } - return $.when(def).then(this.update.bind(this, params, {})); + return Promise.resolve(def).then(this.update.bind(this, params, {})); }, /** * For views that require a pager, this method will be called to allow the @@ -195,16 +199,20 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { * your view does not want a pager, just let this method empty. * * @param {jQuery Node} $node + * @return {Promise} */ renderPager: function ($node) { + return Promise.resolve(); }, /** * Same as renderPager, but for the 'sidebar' zone (the zone with the menu * dropdown in the control panel next to the buttons) * * @param {jQuery Node} $node + * @return {Promise} */ renderSidebar: function ($node) { + return Promise.resolve(); }, /** * This is the main entry point for the controller. Changes from the search @@ -219,12 +227,12 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { * @param {Object} [options] * @param {boolean} [options.reload=true] if true, the model will reload data * - * @returns {Deferred} + * @returns {Promise} */ update: function (params, options) { var self = this; var shouldReload = (options && 'reload' in options) ? options.reload : true; - var def = shouldReload ? this.model.reload(this.handle, params) : $.when(); + var def = shouldReload ? this.model.reload(this.handle, params) : Promise.resolve(); // we check here that the updateIndex of the control panel hasn't changed // between the start of the update request and the moment the controller // asks the control panel to update itself ; indeed, it could happen that @@ -285,17 +293,17 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { * return {'html': '<h1>hello, world</h1>'} * * @private - * @returns {Deferred} + * @returns {Promise} */ _renderBanner: function () { if (this.bannerRoute !== undefined) { var self = this; return this.dp - .add(this._rpc({route: this.bannerRoute})) + .add(this._rpc({ route: this.bannerRoute })) .then(function (response) { if (!response.html) { self.$el.removeClass('o_has_banner'); - return $.when(); + return Promise.resolve(); } self.$el.addClass('o_has_banner'); var $banner = $(response.html); @@ -313,22 +321,24 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { defs.push(ajax.loadJS(js.src)); js.remove(); }); - return $.when.apply($, defs).then(function () { + return Promise.all(defs).then(function () { $banner.prependTo(self.$('> .o_content')); self._$banner = $banner; }); }); } - return $.when(); + return Promise.resolve(); }, /** * Renders the control elements (buttons, pager and sidebar) of the current * view. * * @private - * @returns {Object} an object containing the control panel jQuery elements + * @returns {Promise<Object>} resolved with an object containing the control + * panel jQuery elements */ _renderControlPanelElements: function () { + var self = this; var elements = { $buttons: $('<div>'), $sidebar: $('<div>'), @@ -336,15 +346,18 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { }; this.renderButtons(elements.$buttons); - this.renderSidebar(elements.$sidebar); - this.renderPager(elements.$pager); - // remove the unnecessary outer div - elements = _.mapObject(elements, function($node) { - return $node && $node.contents(); - }); - elements.$switch_buttons = this._renderSwitchButtons(); + var sidebarProm = this.renderSidebar(elements.$sidebar); + var pagerProm = this.renderPager(elements.$pager); - return elements; + return Promise.all([sidebarProm, pagerProm]).then(function () { + // remove the unnecessary outer div + elements = _.mapObject(elements, function ($node) { + return $node && $node.contents(); + }); + elements.$switch_buttons = self._renderSwitchButtons(); + + return elements; + }); }, /** * Renders the switch buttons and binds listeners on them. @@ -354,7 +367,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { */ _renderSwitchButtons: function () { var self = this; - var views = _.filter(this.actionViews, {multiRecord: this.isMultiRecord}); + var views = _.filter(this.actionViews, { multiRecord: this.isMultiRecord }); if (views.length <= 1) { return $(); @@ -373,7 +386,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { var $switchButtonsFiltered = config.device.isMobile ? $switchButtons.find('button') : $switchButtons.filter('button'); $switchButtonsFiltered.click(_.debounce(function (event) { var viewType = $(event.target).data('view-type'); - self.trigger_up('switch_view', {view_type: viewType}); + self.trigger_up('switch_view', { view_type: viewType }); }, 200, true)); // set active view's icon as view switcher button's icon in mobile @@ -402,7 +415,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { * * @private * @param {Object} state the state given by the model - * @returns {Deferred} + * @returns {Promise} */ _update: function () { // AAB: update the control panel -> this will be moved elsewhere at some point @@ -524,7 +537,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { */ _onOpenRecord: function (ev) { ev.stopPropagation(); - var record = this.model.get(ev.data.id, {raw: true}); + var record = this.model.get(ev.data.id, { raw: true }); this.trigger_up('switch_view', { view_type: 'form', res_id: record.res_id, diff --git a/addons/web/static/src/js/views/abstract_model.js b/addons/web/static/src/js/views/abstract_model.js index da73ac664a3..d18367b1756 100644 --- a/addons/web/static/src/js/views/abstract_model.js +++ b/addons/web/static/src/js/views/abstract_model.js @@ -31,7 +31,7 @@ var AbstractModel = mvc.Model.extend({ * @returns {Promise} */ reload: function (params) { - return $.when(); + return Promise.resolve(); }, /** * Processes date(time) and selection field values sent by the server. diff --git a/addons/web/static/src/js/views/abstract_renderer.js b/addons/web/static/src/js/views/abstract_renderer.js index 0f18263c9ee..724b63bae79 100644 --- a/addons/web/static/src/js/views/abstract_renderer.js +++ b/addons/web/static/src/js/views/abstract_renderer.js @@ -23,14 +23,14 @@ return mvc.Renderer.extend({ this.noContentHelp = params.noContentHelp; }, /** - * The rendering can be asynchronous (but it is not encouraged). The start + * The rendering is asynchronous. The start * method simply makes sure that we render the view. * - * @returns {Deferred} + * @returns {Promise} */ start: function () { this.$el.addClass(this.arch.attrs.class); - return $.when(this._render(), this._super()); + return Promise.all([this._render(), this._super()]); }, /** * Called each time the renderer is attached into the DOM. @@ -86,11 +86,11 @@ return mvc.Renderer.extend({ * @param {Object} params * @param {boolean} [params.noRender=false] * if true, the method only updates the state without rerendering - * @returns {Deferred} + * @returns {Promise} */ updateState: function (state, params) { this.state = state; - return params.noRender ? $.when() : this._render(); + return params.noRender ? Promise.resolve() : this._render(); }, //-------------------------------------------------------------------------- @@ -102,10 +102,10 @@ return mvc.Renderer.extend({ * * @abstract * @private - * @returns {Deferred} + * @returns {Promise} */ _render: function () { - return $.when(); + return Promise.resolve(); }, }); diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js index ce8951f6ee8..ba3c198bd21 100644 --- a/addons/web/static/src/js/views/abstract_view.js +++ b/addons/web/static/src/js/views/abstract_view.js @@ -194,11 +194,12 @@ var AbstractView = Factory.extend({ def = this._createControlPanel(parent); } var _super = this._super.bind(this); - return $.when(def).then(function (controlPanel) { + return Promise.resolve(def).then(function (controlPanel) { // get the parent of the model if it already exists, as _super will // set the new controller as parent, which we don't want var modelParent = self.model && self.model.getParent(); - return _super(parent).done(function (controller) { + var prom = _super(parent); + prom.then(function (controller) { if (controlPanel) { controlPanel.setParent(controller); } @@ -207,7 +208,7 @@ var AbstractView = Factory.extend({ self.model.setParent(modelParent); } }); - + return prom; }); }, /** diff --git a/addons/web/static/src/js/views/basic/basic_controller.js b/addons/web/static/src/js/views/basic/basic_controller.js index 07646c83273..edfaba60b54 100644 --- a/addons/web/static/src/js/views/basic/basic_controller.js +++ b/addons/web/static/src/js/views/basic/basic_controller.js @@ -41,11 +41,11 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { // savingDef is used to ensure that we always wait for pending save // operations to complete before checking if there are changes to // discard when discardChanges is called - this.savingDef = $.when(); + this.savingDef = Promise.resolve(); }, /** * @override - * @returns {Deferred} + * @returns {Promise} */ start: function () { // add classname to reflect the (absence of) access rights (used to @@ -66,24 +66,27 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { * * @override * @param {string} [recordID] - default to main recordID - * @returns {Deferred<boolean>} + * @returns {Promise<boolean>} * resolved if can be discarded, a boolean value is given to tells * if there is something to discard or not * rejected otherwise */ canBeDiscarded: function (recordID) { + var self = this; if (!this.isDirty(recordID)) { - return $.when(false); + return Promise.resolve(false); } var message = _t("The record has been modified, your changes will be discarded. Do you want to proceed?"); - var def = $.Deferred(); - var dialog = Dialog.confirm(this, message, { - title: _t("Warning"), - confirm_callback: def.resolve.bind(def, true), - cancel_callback: def.reject.bind(def), + var def; + def = new Promise(function (resolve, reject) { + var dialog = Dialog.confirm(self, message, { + title: _t("Warning"), + confirm_callback: resolve.bind(self, true), + cancel_callback: reject, + }); + dialog.on('closed', def, reject); }); - dialog.on('closed', def, def.reject); return def; }, /** @@ -114,7 +117,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { * @see _.discardChanges */ discardChanges: function (recordID, options) { - return $.when(this.mutex.getUnlockedDef(), this.savingDef) + return Promise.all([this.mutex.getUnlockedDef(), this.savingDef]) .then(this._discardChanges.bind(this, recordID || this.handle, options)); }, /** @@ -144,6 +147,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { * @override */ renderPager: function ($node, options) { + var self = this; var data = this.model.get(this.handle, {raw: true}); this.pager = new Pager(this, data.count, data.offset + 1, data.limit, options); @@ -161,19 +165,21 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { }) .then(this.pager.enable.bind(this.pager)); }); - this.pager.appendTo($node); - this._updatePager(); // to force proper visibility + return this.pager.appendTo($node).then(function() { + self._updatePager(); // to force proper visibility + }); }, /** * Saves the record whose ID is given if necessary (@see _saveRecord). * * @param {string} [recordID] - default to main recordID * @param {Object} [options] - * @returns {Deferred} + * @returns {Promise} * Resolved with the list of field names (whose value has been modified) * Rejected if the record can't be saved */ saveRecord: function (recordID, options) { + var self = this; // Some field widgets can't detect (all) their changes immediately or // may have to validate them before notifying them, so we ask them to // commit their current value before saving. This has to be done outside @@ -187,23 +193,22 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { // user if some discarding has to be made). This operation must also be // mutex-protected as commitChanges function of x2m has to be aware of // all final changes made to a row. - var self = this; - var savingDef = $.Deferred(); - this.savingDef = savingDef; - - return this.mutex.getUnlockedDef() + var unlockedMutex = this.mutex.getUnlockedDef() .then(function () { return self.renderer.commitChanges(recordID || self.handle); }) .then(function () { return self.mutex.exec(self._saveRecord.bind(self, recordID, options)); - }) - .then(savingDef.resolve.bind(savingDef)) - .fail(savingDef.resolve.bind(savingDef)); + }); + this.savingDef = new Promise(function (resolve) { + unlockedMutex.then(resolve).guardedCatch(resolve); + }); + + return unlockedMutex; }, /** * @override - * @returns {Deferred} + * @returns {Promise} */ update: function (params, options) { var self = this; @@ -267,31 +272,32 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { * @private * @param {Object} attrs the attrs of the button clicked * @param {Object} [record] the current state of the view - * @returns {Deferred} + * @returns {Promise} */ _callButtonAction: function (attrs, record) { var self = this; - var def = $.Deferred(); - var reload = function () { - return self.isDestroyed() ? $.when() : self.reload(); - }; - record = record || this.model.get(this.handle); + var def = new Promise(function (resolve, reject) { + var reload = function () { + return self.isDestroyed() ? Promise.resolve() : self.reload(); + }; + record = record || self.model.get(self.handle); - this.trigger_up('execute_action', { - action_data: _.extend({}, attrs, { - context: record.getContext({additionalContext: attrs.context || {}}), - }), - env: { - context: record.getContext(), - currentID: record.data.id, - model: record.model, - resIDs: record.res_ids, - }, - on_success: def.resolve.bind(def), - on_fail: function () { - self.update({}, {reload: false}).always(def.reject.bind(def)); - }, - on_closed: reload, + self.trigger_up('execute_action', { + action_data: _.extend({}, attrs, { + context: record.getContext({additionalContext: attrs.context || {}}), + }), + env: { + context: record.getContext(), + currentID: record.data.id, + model: record.model, + resIDs: record.res_ids, + }, + on_success: resolve, + on_fail: function () { + self.update({}, {reload: false}).then(reject).guardedCatch(reject); + }, + on_closed: reload, + }); }); return this.alive(def); }, @@ -305,7 +311,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { * @param {string} id - the id of one of the view's records * @param {string[]} fields - the changed fields * @param {OdooEvent} e - the event that triggered the change - * @returns {Deferred} + * @returns {Promise} */ _confirmChange: function (id, fields, e) { if (e.name === 'discard_changes' && e.target.reset) { @@ -364,7 +370,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { * the new record to be in edit mode too, but the view manager calls * this function as the URL changes...) @todo get rid of this when * the webclient/action_manager's hashchange mechanism is improved. - * @returns {Deferred} + * @returns {Promise} */ _discardChanges: function (recordID, options) { var self = this; @@ -459,7 +465,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { * @param {boolean} [options.savePoint=false] * if true, the record will only be 'locally' saved: its changes * will move from the _changes key to the data key - * @returns {Deferred} + * @returns {Promise} * Resolved with the list of field names (whose value has been modified) * Rejected if the record can't be saved */ @@ -490,7 +496,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { } return saveDef; } else { - return $.Deferred().reject(); // Cannot be saved + return Promise.reject("SaveRecord: this.canBeSave is false"); // Cannot be saved } }, /** @@ -501,7 +507,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { * @private * @param {string} mode - 'readonly' or 'edit' * @param {string} [recordID] - * @returns {Deferred} + * @returns {Promise} */ _setMode: function (mode, recordID) { if ((recordID || this.handle) === this.handle) { @@ -511,7 +517,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { core.bus.trigger('DOM_updated'); }); } - return $.when(); + return Promise.resolve(); }, /** * Helper method, to get the current environment variables from the model @@ -564,14 +570,14 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { ev.stopPropagation(); var recordID = ev.data.recordID; this._discardChanges(recordID) - .done(function () { + .then(function () { // TODO this will tell the renderer to rerender the widget that // asked for the discard but will unfortunately lose the click // made on another row if any self._confirmChange(recordID, [ev.data.fieldName], ev) - .always(ev.data.onSuccess); + .then(ev.data.onSuccess).guardedCatch(ev.data.onSuccess); }) - .fail(ev.data.onFailure); + .guardedCatch(ev.data.onFailure); }, /** * Forces to save directly the changes if the controller is in readonly, diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index 4d86ef4293b..95b6d439ec1 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -173,7 +173,7 @@ var BasicModel = AbstractModel.extend({ * @param {Object} [options] * @param {string} [options.position=top] if the new record should be added * on top or on bottom of the list - * @returns {Deferred<string>} resolves to the id of the new created record + * @returns {Promise<string>} resolves to the id of the new created record */ addDefaultRecord: function (listID, options) { var self = this; @@ -241,7 +241,7 @@ var BasicModel = AbstractModel.extend({ * assume main viewType from the record * @param {Array} [options.fieldNames] list of field names for which a * default value must be generated (used to complete the values dict) - * @returns {Deferred} + * @returns {Promise} */ applyDefaultValues: function (recordID, values, options) { options = options || {}; @@ -304,7 +304,7 @@ var BasicModel = AbstractModel.extend({ } } - return $.when.apply($, defs); + return Promise.all(defs); }, /** * Onchange RPCs may return values for fields that are not in the current @@ -321,7 +321,7 @@ var BasicModel = AbstractModel.extend({ * * @param {string} recordID local resource id of a record * @param {string} viewType the current viewType - * @returns {Deferred<string>} resolves to the id of the record + * @returns {Promise<string>} resolves to the id of the record */ applyRawChanges: function (recordID, viewType) { var record = this.localData[recordID]; @@ -388,7 +388,7 @@ var BasicModel = AbstractModel.extend({ * @param {string[]} recordIds list of local resources ids. They should all * be of type 'record', be of the same model and have the same parent. * @param {string} modelName mode name used to unlink the records - * @returns {Deferred} + * @returns {Promise} */ deleteRecords: function (recordIds, modelName) { var self = this; @@ -457,7 +457,7 @@ var BasicModel = AbstractModel.extend({ * Duplicate a record (by calling the 'copy' route) * * @param {string} recordID id for a local resource - * @returns {Deferred<string>} resolves to the id of duplicate record + * @returns {Promise<string>} resolves to the id of duplicate record */ duplicateRecord: function (recordID) { var self = this; @@ -725,7 +725,7 @@ var BasicModel = AbstractModel.extend({ * @param {Object} params.fields contains the description of each field * @param {string} [params.type] 'record' or 'list' * @param {string} [params.recordID] an ID for an existing resource. - * @returns {Deferred<string>} resolves to a local id, or handle + * @returns {Promise<string>} resolves to a local id, or handle */ load: function (params) { params.type = params.type || (params.res_id !== undefined ? 'record' : 'list'); @@ -758,7 +758,7 @@ var BasicModel = AbstractModel.extend({ * @param {string} model name of the model * @param {Object[]} fields a description of field properties * @param {Object} [fieldInfo] various field info that we want to set - * @returns {string} the local id for the created resource + * @returns {Promise<string>} the local id for the created resource */ makeRecord: function (model, fields, fieldInfo) { var self = this; @@ -849,7 +849,7 @@ var BasicModel = AbstractModel.extend({ record.data[field.name] = field.value; } }); - return $.when.apply($, defs).then(function () { + return Promise.all(defs).then(function () { return record.id; }); }, @@ -863,7 +863,7 @@ var BasicModel = AbstractModel.extend({ * @param {Object} changes a map field => new value * @param {Object} [options] will be transferred to the applyChange method * @see _applyChange - * @returns {string[]} list of changed fields + * @returns {Promise<string[]>} list of changed fields */ notifyChanges: function (record_id, changes, options) { return this.mutex.exec(this._applyChange.bind(this, record_id, changes, options)); @@ -876,7 +876,7 @@ var BasicModel = AbstractModel.extend({ * @param {Object} [options] * @param {boolean} [options.keepChanges=false] if true, doesn't discard the * changes on the record before reloading it - * @returns {Deferred<string>} resolves to the id of the resource + * @returns {Promise<string>} resolves to the id of the resource */ reload: function (id, options) { return this.mutex.exec(this._reload.bind(this, id, options)); @@ -925,12 +925,12 @@ var BasicModel = AbstractModel.extend({ * @param {object} [options] * @param {integer} [options.offset] * @param {string} [options.field] the field name used as sequence - * @returns {Deferred<string>} resolves to the local id of the parent + * @returns {Promise<string>} resolves to the local id of the parent */ resequence: function (modelName, resIDs, parentID, options) { options = options || {}; if ((resIDs.length <= 1)) { - return $.when(parentID); // there is nothing to sort + return Promise.resolve(parentID); // there is nothing to sort } var self = this; var data = this.localData[parentID]; @@ -952,7 +952,7 @@ var BasicModel = AbstractModel.extend({ if (!wasResequenced) { // the field on which the resequence was triggered does not // exist, so no resequence happened server-side - return $.when(); + return Promise.resolve(); } var field = params.field ? params.field : 'sequence'; @@ -1010,7 +1010,7 @@ var BasicModel = AbstractModel.extend({ * be restored later by call discardChanges with option rollback to true * @param {string} [options.viewType] current viewType. If not set, we will * assume main viewType from the record - * @returns {Deferred} + * @returns {Promise} * Resolved with the list of field names (whose value has been modified) */ save: function (recordID, options) { @@ -1054,57 +1054,59 @@ var BasicModel = AbstractModel.extend({ }); } - var def = $.Deferred(); - var changedFields = Object.keys(changes); + var prom = new Promise(function (resolve, reject) { + var changedFields = Object.keys(changes); - if (options.savePoint) { - return def.resolve(changedFields); - } + if (options.savePoint) { + resolve(changedFields); + return; + } - def.then(function () { - record._isDirty = false; - }); - // in the case of a write, only perform the RPC if there are changes to save - if (method === 'create' || changedFields.length) { - var args = method === 'write' ? [[record.data.id], changes] : [changes]; - self._rpc({ - model: record.model, - method: method, - args: args, - context: record.getContext(), - }).then(function (id) { - if (method === 'create') { - record.res_id = id; // create returns an id, write returns a boolean - record.data.id = id; - record.offset = record.res_ids.length; - record.res_ids.push(id); - record.count++; - } + // in the case of a write, only perform the RPC if there are changes to save + if (method === 'create' || changedFields.length) { + var args = method === 'write' ? [[record.data.id], changes] : [changes]; + self._rpc({ + model: record.model, + method: method, + args: args, + context: record.getContext(), + }).then(function (id) { + if (method === 'create') { + record.res_id = id; // create returns an id, write returns a boolean + record.data.id = id; + record.offset = record.res_ids.length; + record.res_ids.push(id); + record.count++; + } - var _changes = record._changes; + var _changes = record._changes; - // Erase changes as they have been applied - record._changes = {}; + // Erase changes as they have been applied + record._changes = {}; - // Optionally clear the DataManager's cache - self._invalidateCache(record); + // Optionally clear the DataManager's cache + self._invalidateCache(record); - self.unfreezeOrder(record.id); + self.unfreezeOrder(record.id); - // Update the data directly or reload them - if (shouldReload) { - self._fetchRecord(record).then(function () { - def.resolve(changedFields); - }); - } else { - _.extend(record.data, _changes); - def.resolve(changedFields); - } - }).fail(def.reject.bind(def)); - } else { - def.resolve(changedFields); - } - return def; + // Update the data directly or reload them + if (shouldReload) { + self._fetchRecord(record).then(function () { + resolve(changedFields); + }); + } else { + _.extend(record.data, _changes); + resolve(changedFields); + } + }).guardedCatch(reject); + } else { + resolve(changedFields); + } + }); + prom.then(function () { + record._isDirty = false; + }); + return prom; }); }, /** @@ -1123,7 +1125,7 @@ var BasicModel = AbstractModel.extend({ * * @param {string} list_id id for the list resource * @param {string} fieldName valid field name - * @returns {Deferred} + * @returns {Promise} */ setSort: function (list_id, fieldName) { var list = this.localData[list_id]; @@ -1153,7 +1155,7 @@ var BasicModel = AbstractModel.extend({ // sort field is still unknown (i.e. on other pages for example) return this._fetchUngroupedList(list); } - return $.when(); + return Promise.resolve(); }, /** * Toggle the active value of given records (to archive/unarchive them) @@ -1161,7 +1163,7 @@ var BasicModel = AbstractModel.extend({ * @param {Array} recordIDs local ids of the records to (un)archive * @param {boolean} value false to archive, true to unarchive (value of the active field) * @param {string} parentID id of the parent resource to reload - * @returns {Deferred<string>} resolves to the parent id + * @returns {Promise<string>} resolves to the parent id */ toggleActive: function (recordIDs, value, parentID) { var self = this; @@ -1185,7 +1187,7 @@ var BasicModel = AbstractModel.extend({ * data * * @param {string} groupId - * @returns {Deferred<string>} resolves to the group id + * @returns {Promise<string>} resolves to the group id */ toggleGroup: function (groupId) { var self = this; @@ -1196,7 +1198,7 @@ var BasicModel = AbstractModel.extend({ group.res_ids = []; group.offset = 0; this._updateParentResIDs(group); - return $.when(groupId); + return Promise.resolve(groupId); } if (!group.isOpen) { group.isOpen = true; @@ -1206,7 +1208,7 @@ var BasicModel = AbstractModel.extend({ self._updateParentResIDs(group); }); } - return $.when(def).then(function () { + return Promise.resolve(def).then(function () { return groupId; }); } @@ -1260,7 +1262,7 @@ var BasicModel = AbstractModel.extend({ * If several contexts are found, multiple records are added * @param {boolean} [options.allowWarning=false] if true, the default record * operation can complete, even if a warning is raised - * @returns {Deferred<[string]>} resolves to the new records ids + * @returns {Promise<[string]>} resolves to the new records ids */ _addX2ManyDefaultRecord: function (list, options) { var self = this; @@ -1286,9 +1288,9 @@ var BasicModel = AbstractModel.extend({ makeDefaultRecords.push(self._makeDefaultRecord(list.model, params)); } - return $.when.apply($, makeDefaultRecords).then(function (){ + return Promise.all(makeDefaultRecords).then(function (resultIds){ var ids = []; - _.each(arguments, function (id){ + _.each(resultIds, function (id){ ids.push(id); list._changes.push({operation: 'ADD', id: id, position: position, isNew: true}); @@ -1324,7 +1326,7 @@ var BasicModel = AbstractModel.extend({ * @param {boolean} [options.allowWarning=false] if true, change * operation can complete, even if a warning is raised * (only supported by X2ManyChange) - * @returns {Deferred} list of changed fields + * @returns {Promise} list of changed fields */ _applyChange: function (recordID, changes, options) { var self = this; @@ -1354,10 +1356,10 @@ var BasicModel = AbstractModel.extend({ } if (options.notifyChange === false) { - return $.Deferred().resolve(_.keys(changes)); + return Promise.resolve(_.keys(changes)); } - return $.when.apply($, defs).then(function () { + return Promise.all(defs).then(function () { var onChangeFields = []; // the fields that have changed and that have an on_change for (var fieldName in changes) { field = record.fields[fieldName]; @@ -1368,22 +1370,22 @@ var BasicModel = AbstractModel.extend({ } } } - var onchangeDef = $.Deferred(); - if (onChangeFields.length) { - self._performOnChange(record, onChangeFields, options.viewType) + return new Promise(function (resolve, reject) { + if (onChangeFields.length) { + self._performOnChange(record, onChangeFields, options.viewType) .then(function (result) { delete record._warning; - onchangeDef.resolve(_.keys(changes).concat(Object.keys(result && result.value || {}))); - }).fail(function () { + resolve(_.keys(changes).concat(Object.keys(result && result.value || {}))); + }).guardedCatch(function () { self._visitChildren(record, function (elem) { _.extend(elem, initialData[elem.id]); }); - onchangeDef.resolve({}); + resolve({}); }); - } else { - onchangeDef = $.Deferred().resolve(_.keys(changes)); - } - return onchangeDef.then(function (fieldNames) { + } else { + resolve(_.keys(changes)); + } + }).then(function (fieldNames) { _.each(fieldNames, function (name) { if (record._changes && record._changes[name] === record.data[name]) { delete record._changes[name]; @@ -1407,13 +1409,13 @@ var BasicModel = AbstractModel.extend({ * @param {Object} record * @param {string} fieldName * @param {Object} [data] - * @returns {Deferred} + * @returns {Promise} */ _applyX2OneChange: function (record, fieldName, data) { var self = this; if (!data || !data.id) { record._changes[fieldName] = false; - return $.when(); + return Promise.resolve(); } // here, we check that the many2one really changed. If the res_id is the @@ -1429,7 +1431,7 @@ var BasicModel = AbstractModel.extend({ } var relatedRecord = this.localData[relatedID]; if (relatedRecord && (data.id === this.localData[relatedID].res_id)) { - return $.when(); + return Promise.resolve(); } var rel_data = _.pick(data, 'id', 'display_name'); var field = record.fields[fieldName]; @@ -1450,7 +1452,7 @@ var BasicModel = AbstractModel.extend({ rel_data.display_name = result[0][1]; }); } - return $.when(def).then(function () { + return Promise.resolve(def).then(function () { var rec = self._makeDataPoint({ context: record.context, data: rel_data, @@ -1471,7 +1473,7 @@ var BasicModel = AbstractModel.extend({ * @param {Object} record * @param {string} [viewType] current viewType. If not set, we will assume * main viewType from the record - * @returns {Deferred} + * @returns {Promise} */ _applyOnChange: function (values, record, viewType) { var self = this; @@ -1634,7 +1636,7 @@ var BasicModel = AbstractModel.extend({ var def = self._readUngroupedList(list).then(function () { var x2ManysDef = self._fetchX2ManysBatched(list); var referencesDef = self._fetchReferencesBatched(list); - return $.when(x2ManysDef, referencesDef); + return Promise.all([x2ManysDef, referencesDef]); }); defs.push(def); } else { @@ -1644,7 +1646,7 @@ var BasicModel = AbstractModel.extend({ } } }); - return $.when.apply($, defs); + return Promise.all(defs); // inner function that adds a record (based on its res_id) to a list // dataPoint (used for onchanges that return commands 4 (LINK TO) or @@ -1680,7 +1682,7 @@ var BasicModel = AbstractModel.extend({ * @param {boolean} [allowWarning=false] if true, change * operation can complete, even if a warning is raised * (only supported by the 'CREATE' command.operation) - * @returns {Deferred} + * @returns {Promise} */ _applyX2ManyChange: function (record, fieldName, command, viewType, allowWarning) { if (command.operation === 'TRIGGER_ONCHANGE') { @@ -1688,7 +1690,7 @@ var BasicModel = AbstractModel.extend({ // there is no need to apply any change on the record (the changes // have probably been already applied and saved, usecase: many2many // edition in a dialog) - return $.when(); + return Promise.resolve(); } var self = this; @@ -1765,10 +1767,10 @@ var BasicModel = AbstractModel.extend({ list_records[record.id].data = record; self._parseServerData(fieldNames, list, record); }); - return $.when( + return Promise.all([ self._fetchX2ManysBatched(list), self._fetchReferencesBatched(list) - ); + ]); }); defs.push(def); } @@ -1850,10 +1852,10 @@ var BasicModel = AbstractModel.extend({ }), }, viewType); } - return $.when(addDef, removedDef); + return Promise.all([addDef, removedDef]); } - return $.when.apply($, defs).then(function () { + return Promise.all(defs).then(function () { // ensure to fetch up to 'limit' records (may be useful if records of // the current page have been removed) return self._readUngroupedList(list).then(function () { @@ -2103,7 +2105,7 @@ var BasicModel = AbstractModel.extend({ * Fetch all name_gets for the many2ones in a group * * @param {Object[]} groups a list of object with context and record sub keys - * @returns {Deferred} + * @returns {Promise} */ _fetchMany2OneGroup: function (groups) { var ids = _.uniq(_.pluck(_.pluck(groups, 'record'), 'res_id')); @@ -2124,7 +2126,7 @@ var BasicModel = AbstractModel.extend({ * Fetch name_get for a record datapoint. * * @param {Object} dataPoint - * @returns {Deferred} + * @returns {Promise} */ _fetchNameGet: function (dataPoint) { return this._rpc({ @@ -2143,7 +2145,7 @@ var BasicModel = AbstractModel.extend({ * @params {Object} list: must be a datapoint of type list * (for example: a datapoint representing a x2many) * @params {string} fieldName: the name of a field of type Many2one or Reference - * @returns {Deferred} + * @returns {Promise} */ _fetchNameGets: function (list, fieldName) { var self = this; @@ -2168,7 +2170,7 @@ var BasicModel = AbstractModel.extend({ }); if (!ids.length) { - return $.when(); + return Promise.resolve(); } return this._rpc({ model: model, @@ -2195,7 +2197,7 @@ var BasicModel = AbstractModel.extend({ * @param {string} [options.viewType] the type of view for which the record * is fetched (usefull to load the adequate fields), by defaults, uses * record.viewType - * @returns {Deferred<Object>} resolves to the record or is rejected in + * @returns {Promise<Object>} resolves to the record or is rejected in * case no id given were valid ids */ _fetchRecord: function (record, options) { @@ -2211,7 +2213,7 @@ var BasicModel = AbstractModel.extend({ }) .then(function (result) { if (result.length === 0) { - return $.Deferred().reject(); + return Promise.reject(); } result = result[0]; record.data = _.extend({}, record.data, result); @@ -2220,10 +2222,10 @@ var BasicModel = AbstractModel.extend({ self._parseServerData(fieldNames, record, record.data); }) .then(function () { - return $.when( + return Promise.all([ self._fetchX2Manys(record, options), self._fetchReferences(record, options) - ).then(function () { + ]).then(function () { return self._postprocess(record, options); }); }); @@ -2234,7 +2236,7 @@ var BasicModel = AbstractModel.extend({ * @private * @param {Object} record * @param {string} fieldName - * @returns {Deferred} + * @returns {Promise} */ _fetchReference: function (record, fieldName) { var self = this; @@ -2259,7 +2261,7 @@ var BasicModel = AbstractModel.extend({ }); }); } - return $.when(def); + return Promise.resolve(def); }, /** * Fetches data for reference fields and assigns these data to newly @@ -2270,7 +2272,7 @@ var BasicModel = AbstractModel.extend({ * @see _getDataToFetchByModel * @param {string} model * @param {string} fieldName - * @returns {Deferred} + * @returns {Promise} */ _fetchReferenceData: function (datapoints, model, fieldName) { var self = this; @@ -2307,7 +2309,7 @@ var BasicModel = AbstractModel.extend({ * * @private * @param {Object} record - * @returns {Deferred} + * @returns {Promise} */ _fetchReferences: function (record, options) { var self = this; @@ -2324,7 +2326,7 @@ var BasicModel = AbstractModel.extend({ defs.push(def); } }); - return $.when.apply($, defs); + return Promise.all(defs); }, /** * Batch requests for one reference field in list (one request by different @@ -2333,7 +2335,7 @@ var BasicModel = AbstractModel.extend({ * @see _fetchReferencesBatched * @param {Object} list * @param {string} fieldName - * @returns {Deferred} + * @returns {Promise} */ _fetchReferenceBatched: function (list, fieldName) { var self = this; @@ -2347,13 +2349,13 @@ var BasicModel = AbstractModel.extend({ defs.push(self._fetchReferenceData(datapoints, model, fieldName)); }); - return $.when.apply($, defs); + return Promise.all(defs); }, /** * Batch requests for references for datapoint of type list. * * @param {Object} list - * @returns {Deferred} + * @returns {Promise} */ _fetchReferencesBatched: function (list) { var defs = []; @@ -2364,7 +2366,7 @@ var BasicModel = AbstractModel.extend({ defs.push(this._fetchReferenceBatched(list, fieldNames[i])); } } - return $.when.apply($, defs); + return Promise.all(defs); }, /** * Batch reference requests for all records in list. @@ -2372,7 +2374,7 @@ var BasicModel = AbstractModel.extend({ * @see _fetchReferencesSingleBatch * @param {Object} list a valid resource object * @param {string} fieldName - * @returns {Deferred} + * @returns {Promise} */ _fetchReferenceSingleBatch: function (list, fieldName) { var self = this; @@ -2390,14 +2392,14 @@ var BasicModel = AbstractModel.extend({ defs.push(self._fetchReferenceData(datapoints, model, fieldName)); }); - return $.when.apply($, defs); + return Promise.all(defs); }, /** * Batch requests for all reference field in list's children. * Called by _readGroup to make only one 'name_get' rpc by fieldName. * * @param {Object} list a valid resource object - * @returns {Deferred} + * @returns {Promise} */ _fetchReferencesSingleBatch: function (list) { var defs = []; @@ -2408,7 +2410,7 @@ var BasicModel = AbstractModel.extend({ defs.push(this._fetchReferenceSingleBatch(list, fieldNames[fIndex])); } } - return $.when.apply($, defs); + return Promise.all(defs); }, /** * Fetch model data from server, relationally to fieldName and resulted @@ -2420,7 +2422,7 @@ var BasicModel = AbstractModel.extend({ * @param {Object} toFetch a list of records and res_ids, * @see _getDataToFetch * @param {string} fieldName - * @returns {Deferred} + * @returns {Promise} */ _fetchRelatedData: function (list, toFetch, fieldName) { var self = this; @@ -2431,7 +2433,7 @@ var BasicModel = AbstractModel.extend({ var fieldInfo = list.fieldsInfo[list.viewType][fieldName]; if (!ids.length || fieldInfo.__no_fetch) { - return $.when(); + return Promise.resolve(); } var def; @@ -2445,7 +2447,7 @@ var BasicModel = AbstractModel.extend({ context: list.getContext() || {}, }); } else { - def = $.when(_.map(ids, function (id) { + def = Promise.resolve(_.map(ids, function (id) { return {id:id}; })); } @@ -2465,10 +2467,10 @@ var BasicModel = AbstractModel.extend({ * This method is currently only called by _makeDefaultRecord, it should be * called by the onchange methods at some point. * - * @todo fix bug: returns a list of deferred, not a deferred + * @todo fix bug: returns a list of promise, not a promise * * @param {Object} record a valid resource object - * @returns {Deferred} + * @returns {Promise} */ _fetchRelationalData: function (record) { var self = this; @@ -2496,7 +2498,7 @@ var BasicModel = AbstractModel.extend({ return [elem.record.model, JSON.stringify(elem.context)].join(); }); - return $.when.apply($, _.map(groups, this._fetchMany2OneGroup.bind(this))); + return Promise.all(_.map(groups, this._fetchMany2OneGroup.bind(this))); }, /** * Check the AbstractField specializations that are (will be) used by the @@ -2507,15 +2509,15 @@ var BasicModel = AbstractModel.extend({ * * @param {Object} record - an element from the localData * @param {Object} options - * @returns {Deferred<Array>} - * The deferred is resolved with an array containing the names of + * @returns {Promise<Array>} + * The promise is resolved with an array containing the names of * the field whose special data has been changed. */ _fetchSpecialData: function (record, options) { var self = this; var specialFieldNames = []; var fieldNames = (options && options.fieldNames) || record.getFieldNames(); - return $.when.apply($, _.map(fieldNames, function (name) { + return Promise.all(_.map(fieldNames, function (name) { var viewType = (options && options.viewType) || record.viewType; var fieldInfo = record.fieldsInfo[viewType][name] || {}; var Widget = fieldInfo.Widget; @@ -2541,16 +2543,16 @@ var BasicModel = AbstractModel.extend({ * @param {Object} fieldInfo * @param {string[]} [fieldsToRead] - the m2os fields to read (id and * display_name are automatic). - * @returns {Deferred<any>} - * The deferred is resolved with the fetched special data. If this + * @returns {Promise<any>} + * The promise is resolved with the fetched special data. If this * data is the same as the previously fetched one (for the given - * parameters), no RPC is done and the deferred is resolved with + * parameters), no RPC is done and the promise is resolved with * the undefined value. */ _fetchSpecialMany2ones: function (record, fieldName, fieldInfo, fieldsToRead) { var field = record.fields[fieldName]; if (field.type !== "many2one") { - return $.when(); + return Promise.resolve(); } var context = record.getContext({fieldName: fieldName}); @@ -2571,7 +2573,7 @@ var BasicModel = AbstractModel.extend({ domain: domain, }); if (!hasChanged) { - return $.when(); + return Promise.resolve(); } var self = this; @@ -2607,16 +2609,16 @@ var BasicModel = AbstractModel.extend({ * * @param {Object} record - an element from the localData * @param {Object} fieldName - the name of the field - * @returns {Deferred<any>} - * The deferred is resolved with the fetched special data. If this + * @returns {Promise<any>} + * The promise is resolved with the fetched special data. If this * data is the same as the previously fetched one (for the given - * parameters), no RPC is done and the deferred is resolved with + * parameters), no RPC is done and the promise is resolved with * the undefined value. */ _fetchSpecialRelation: function (record, fieldName) { var field = record.fields[fieldName]; if (!_.contains(["many2one", "many2many", "one2many"], field.type)) { - return $.when(); + return Promise.resolve(); } var context = record.getContext({fieldName: fieldName}); @@ -2628,7 +2630,7 @@ var BasicModel = AbstractModel.extend({ domain: domain, }); if (!hasChanged) { - return $.when(); + return Promise.resolve(); } return this._rpc({ @@ -2645,7 +2647,7 @@ var BasicModel = AbstractModel.extend({ * @private * @param {Object} record - an element from the localData * @param {Object} fieldName - the name of the field - * @returns {Deferred} + * @returns {Promise} */ _fetchSpecialReference: function (record, fieldName) { var def; @@ -2655,7 +2657,7 @@ var BasicModel = AbstractModel.extend({ // needs to be fetched a posteriori def = this._fetchReference(record, fieldName); } - return $.when(def); + return Promise.resolve(def); }, /** * Fetches all the m2o records associated to the given fieldName. If the @@ -2666,10 +2668,10 @@ var BasicModel = AbstractModel.extend({ * @param {Object} record - an element from the localData * @param {Object} fieldName - the name of the field * @param {Object} fieldInfo - * @returns {Deferred<any>} - * The deferred is resolved with the fetched special data. If this + * @returns {Promise<any>} + * The promise is resolved with the fetched special data. If this * data is the same as the previously fetched one (for the given - * parameters), no RPC is done and the deferred is resolved with + * parameters), no RPC is done and the promise is resolved with * the undefined value. */ _fetchSpecialStatus: function (record, fieldName, fieldInfo) { @@ -2689,13 +2691,14 @@ var BasicModel = AbstractModel.extend({ * @param {Object} record - an element from the localData * @param {Object} fieldName - the name of the field * @param {Object} fieldInfo - * @returns {Deferred<any>} - * The deferred is resolved with the fetched special data. If this + * @returns {Promise<any>} + * The promise is resolved with the fetched special data. If this * data is the same as the previously fetched one (for the given - * parameters), no RPC is done and the deferred is resolved with + * parameters), no RPC is done and the promise is resolved with * the undefined value. */ _fetchSpecialDomain: function (record, fieldName, fieldInfo) { + var self = this; var context = record.getContext({fieldName: fieldName}); var domainModel = fieldInfo.options.model; @@ -2711,34 +2714,37 @@ var BasicModel = AbstractModel.extend({ domainValue: domainValue, }); if (!hasChanged) { - return $.when(); + return Promise.resolve(); } else if (!domainModel) { - return $.when({ + return Promise.resolve({ model: domainModel, nbRecords: 0, }); } - var def = $.Deferred(); - var evalContext = this._getEvalContext(record); - this._rpc({ + return new Promise(function (resolve) { + var evalContext = self._getEvalContext(record); + self._rpc({ model: domainModel, method: 'search_count', args: [Domain.prototype.stringToArray(domainValue, evalContext)], context: context }) - .then(_.identity, function (error, e) { - e.preventDefault(); // prevent traceback (the search_count might be intended to break) - return false; - }) - .always(function (nbRecords) { - def.resolve({ + .then(function (nbRecords) { + resolve({ model: domainModel, nbRecords: nbRecords, }); + }) + .guardedCatch(function (reason) { + var e = reason.event; + e.preventDefault(); // prevent traceback (the search_count might be intended to break) + resolve({ + model: domainModel, + nbRecords: 0, + }); }); - - return def; + }); }, /** * Fetch all data in a ungrouped list @@ -2748,7 +2754,7 @@ var BasicModel = AbstractModel.extend({ * @param {boolean} [options.enableRelationalFetch=true] if false, will not * fetch x2m and relational data (that will be done by _readGroup in this * case). - * @returns {Deferred<Object>} resolves to the fetched list + * @returns {Promise<Object>} resolves to the fecthed list */ _fetchUngroupedList: function (list, options) { options = _.defaults(options || {}, {enableRelationalFetch: true}); @@ -2762,7 +2768,7 @@ var BasicModel = AbstractModel.extend({ var defs = _.map(many2ones, function (name) { return self._fetchNameGets(list, name); }); - return $.when.apply($, defs); + return Promise.all(defs); } }); } else { @@ -2770,10 +2776,10 @@ var BasicModel = AbstractModel.extend({ } return def.then(function () { if (options.enableRelationalFetch) { - return $.when( - self._fetchX2ManysBatched(list, options), - self._fetchReferencesBatched(list, options) - ); + return Promise.all([ + self._fetchX2ManysBatched(list), + self._fetchReferencesBatched(list) + ]); } }).then(function () { return list; @@ -2785,7 +2791,7 @@ var BasicModel = AbstractModel.extend({ * @see _fetchX2ManysBatched * @param {Object} list * @param {string} fieldName - * @returns {Deferred} + * @returns {Promise} */ _fetchX2ManyBatched: function (list, fieldName) { list = this._applyX2ManyOperations(list); @@ -2810,7 +2816,7 @@ var BasicModel = AbstractModel.extend({ * @param {string} [options.viewType] the type of view for which the main * record is fetched (useful to load the adequate fields), by defaults, * uses record.viewType - * @returns {Deferred} + * @returns {Promise} */ _fetchX2Manys: function (record, options) { var self = this; @@ -2845,22 +2851,22 @@ var BasicModel = AbstractModel.extend({ record.data[fieldName] = list.id; if (!fieldInfo.__no_fetch) { var def = self._readUngroupedList(list).then(function () { - return $.when( + return Promise.all([ self._fetchX2ManysBatched(list), self._fetchReferencesBatched(list) - ); + ]); }); defs.push(def); } } }); - return $.when.apply($, defs); + return Promise.all(defs); }, /** * batch request for x2ms for datapoint of type list * * @param {Object} list - * @returns {Deferred} + * @returns {Promise} */ _fetchX2ManysBatched: function (list) { var defs = []; @@ -2871,7 +2877,7 @@ var BasicModel = AbstractModel.extend({ defs.push(this._fetchX2ManyBatched(list, fieldNames[i])); } } - return $.when.apply($, defs); + return Promise.all(defs); }, /** * For a non-static list, batches requests for all its sublists' records. @@ -2881,7 +2887,7 @@ var BasicModel = AbstractModel.extend({ * @param {Object} list a valid resource object, its data must be another * list containing records * @param {string} fieldName - * @returns {Deferred} + * @returns {Promise} */ _fetchX2ManySingleBatch: function (list, fieldName) { var self = this; @@ -2904,7 +2910,7 @@ var BasicModel = AbstractModel.extend({ * Called by _readGroup to make only one 'read' rpc by fieldName. * * @param {Object} list a valid resource object - * @returns {Deferred} + * @returns {Promise} */ _fetchX2ManysSingleBatch: function (list) { var defs = []; @@ -2915,7 +2921,7 @@ var BasicModel = AbstractModel.extend({ defs.push(this._fetchX2ManySingleBatch(list, fieldNames[i])); } } - return $.when.apply($, defs); + return Promise.all(defs); }, /** * Generates an object mapping field names to their changed value in a given @@ -3584,12 +3590,12 @@ var BasicModel = AbstractModel.extend({ * @param {boolean} [options.keepEmptyGroups=false] if set, the groups not * present in the read_group anymore (empty groups) will stay in the * datapoint (used to mimic the kanban renderer behaviour for example) - * @returns {Deferred} + * @returns {Promise} */ _load: function (dataPoint, options) { if (options && options.onlyGroups && !(dataPoint.type === 'list' && dataPoint.groupedBy.length)) { - return $.when(dataPoint); + return Promise.resolve(dataPoint); } if (dataPoint.type === 'record') { @@ -3733,7 +3739,7 @@ var BasicModel = AbstractModel.extend({ * @param {Object} params.fields contains the description of each field * @param {Object} params.context the context for the new record * @param {string} params.viewType the key in fieldsInfo of the fields to load - * @returns {Deferred<string>} resolves to the id for the created resource + * @returns {Promise<string>} resolves to the id for the created resource */ _makeDefaultRecord: function (modelName, params) { var self = this; @@ -3787,16 +3793,19 @@ var BasicModel = AbstractModel.extend({ return self.applyDefaultValues(record.id, result, {fieldNames: fieldNames}) .then(function () { - var def = $.Deferred(); - self._performOnChange(record, fields_key).always(function () { - if (record._warning) { - if (params.allowWarning) { - delete record._warning; - } else { - def.reject(); + var def = new Promise(function (resolve, reject) { + var always = function () { + if (record._warning) { + if (params.allowWarning) { + delete record._warning; + } else { + reject(); + } } - } - def.resolve(); + resolve(); + }; + self._performOnChange(record, fields_key) + .then(always).guardedCatch(always); }); return def; }) @@ -3865,13 +3874,13 @@ var BasicModel = AbstractModel.extend({ * @param {string[]} fields changed fields * @param {string} [viewType] current viewType. If not set, we will assume * main viewType from the record - * @returns {Deferred} + * @returns {Promise} */ _performOnChange: function (record, fields, viewType) { var self = this; var onchangeSpec = this._buildOnchangeSpecs(record, viewType); if (!onchangeSpec) { - return $.when(); + return Promise.resolve(); } var idList = record.data.id ? [record.data.id] : []; var options = { @@ -3925,7 +3934,7 @@ var BasicModel = AbstractModel.extend({ * @param {Object} [options] * @param {Object} [options.viewType] current viewType. If not set, we will * assume main viewType from the record - * @returns {Deferred<Object>} resolves to the finished resource + * @returns {Promise<Object>} resolves to the finished resource */ _postprocess: function (record, options) { var self = this; @@ -3954,7 +3963,7 @@ var BasicModel = AbstractModel.extend({ defs.push(this._fetchSpecialData(record, options)); - return $.when.apply($, defs).then(function () { + return Promise.all(defs).then(function () { return record; }); }, @@ -3975,7 +3984,7 @@ var BasicModel = AbstractModel.extend({ * @param {Object} [options] * @param {string} [options.viewType] current viewType. If not set, we will * assume main viewType from the record - * @returns {Deferred} + * @returns {Promise} */ _processX2ManyCommands: function (record, fieldName, commands, options) { var self = this; @@ -4076,10 +4085,10 @@ var BasicModel = AbstractModel.extend({ x2manyList._changes.push({operation: 'ADD', resID: res_id}); }); var def = self._readUngroupedList(x2manyList).then(function () { - return $.when( + return Promise.all([ self._fetchX2ManysBatched(x2manyList), self._fetchReferencesBatched(x2manyList) - ); + ]); }); defs.push(def); } @@ -4090,7 +4099,7 @@ var BasicModel = AbstractModel.extend({ defs.push(self._fetchNameGets(x2manyList, name)); }); - return $.when.apply($, defs); + return Promise.all(defs); }, /** * Reads data from server for all missing fields. @@ -4099,7 +4108,7 @@ var BasicModel = AbstractModel.extend({ * @param {Object} list a valid resource object * @param {interger[]} resIDs * @param {string[]} fieldNames to check and read if missing - * @returns {Deferred<Object>} + * @returns {Promise<Object>} */ _readMissingFields: function (list, resIDs, fieldNames) { var self = this; @@ -4128,7 +4137,7 @@ var BasicModel = AbstractModel.extend({ context: list.getContext(), }); } else { - def = $.when(_.map(missingIDs, function (id) { + def = Promise.resolve(_.map(missingIDs, function (id) { return {id:id}; })); } @@ -4174,7 +4183,7 @@ var BasicModel = AbstractModel.extend({ * * @param {Object} list valid resource object * @param {Object} [options] @see _load - * @returns {Deferred<Object>} resolves to the fetched group object + * @returns {Promise<Object>} resolves to the fetched group object */ _readGroup: function (list, options) { var self = this; @@ -4285,20 +4294,20 @@ var BasicModel = AbstractModel.extend({ }); } - return $.when.apply($, defs).then(function () { + return Promise.all(defs).then(function (groups) { if (!options || !options.onlyGroups) { // generate the res_ids of the main list, being the concatenation // of the fetched res_ids in each group - list.res_ids = _.flatten(_.map(arguments, function (group) { + list.res_ids = _.flatten(_.map(groups, function (group) { return group ? group.res_ids : []; })); } return list; }).then(function () { - var fetchingDefs = []; - fetchingDefs.push(self._fetchX2ManysSingleBatch(list)); - fetchingDefs.push(self._fetchReferencesSingleBatch(list)); - return $.when.apply($, fetchingDefs).then(function () { + return Promise.all([ + self._fetchX2ManysSingleBatch(list), + self._fetchReferencesSingleBatch(list) + ]).then(function () { return list; }); }); @@ -4309,11 +4318,11 @@ var BasicModel = AbstractModel.extend({ * instead of a /search_read. * * @param {Object} list a valid resource object - * @returns {Deferred<Object>} resolves to the fetched list object + * @returns {Promise<Object>} resolves to the fetched list object */ _readUngroupedList: function (list) { var self = this; - var def = $.when(); + var def = Promise.resolve(); // generate the current count and res_ids list by applying the changes list = this._applyX2ManyOperations(list); @@ -4375,7 +4384,7 @@ var BasicModel = AbstractModel.extend({ * @param {Object} [options] * @param {boolean} [options.keepChanges=false] if true, doesn't discard the * changes on the record before reloading it - * @returns {Deferred<string>} resolves to the id of the resource + * @returns {Promise<string>} resolves to the id of the resource */ _reload: function (id, options) { options = options || {}; @@ -4464,7 +4473,7 @@ var BasicModel = AbstractModel.extend({ * /search_read because the data may not be static (for ex, a list view). * * @param {Object} list - * @returns {Deferred} + * @returns {Promise} */ _searchReadUngroupedList: function (list) { var self = this; diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js index 27821081f96..2bc63f47073 100644 --- a/addons/web/static/src/js/views/basic/basic_renderer.js +++ b/addons/web/static/src/js/views/basic/basic_renderer.js @@ -61,13 +61,13 @@ var BasicRenderer = AbstractRenderer.extend({ * before notifying them). * * @param {string} recordID - * @return {Deferred} + * @return {Promise} */ commitChanges: function (recordID) { var defs = _.map(this.allFieldWidgets[recordID], function (widget) { return widget.commitChanges(); }); - return $.when.apply($, defs); + return Promise.all(defs); }, /** * Updates the internal state of the renderer to the new state. By default, @@ -86,7 +86,7 @@ var BasicRenderer = AbstractRenderer.extend({ * @param {string} id * @param {string[]} fields * @param {OdooEvent} ev - * @returns {Deferred<AbstractField[]>} resolved with the list of widgets + * @returns {Promise<AbstractField[]>} resolved with the list of widgets * that have been reset */ confirmChange: function (state, id, fields, ev) { @@ -118,7 +118,7 @@ var BasicRenderer = AbstractRenderer.extend({ // state before evaluation defs.push(this._updateAllModifiers(record)); - return $.when.apply($, defs).then(function () { + return Promise.all(defs).then(function () { return resetWidgets; }); }, @@ -295,7 +295,7 @@ var BasicRenderer = AbstractRenderer.extend({ * * @private * @param {AbstractField} widget - * @returns {boolean|Deferred<boolean>} @see AbstractField.isValid + * @returns {boolean|Promise<boolean>} @see AbstractField.isValid */ _canWidgetBeSaved: function (widget) { var modifiers = this._getEvaluatedModifiers(widget.__node, widget.record); @@ -386,6 +386,7 @@ var BasicRenderer = AbstractRenderer.extend({ * @param {Object} node the attrs coming from the arch */ _postProcessField: function (widget, node) { + this._handleAttributes(widget.$el, node); }, /** * Registers or updates the modifiers data associated to the given node. @@ -503,7 +504,7 @@ var BasicRenderer = AbstractRenderer.extend({ * Render the view * * @override - * @returns {Deferred} + * @returns {Promise} */ _render: function () { var oldAllFieldWidgets = this.allFieldWidgets; @@ -520,7 +521,7 @@ var BasicRenderer = AbstractRenderer.extend({ /** * Instantiates the appropriate AbstractField specialization for the given * node and prepares its rendering and addition to the DOM. Indeed, the - * rendering of the widget will be started and the associated deferred will + * rendering of the widget will be started and the associated promise will * be added to the 'defs' attribute. This is supposed to be created and * deleted by the calling code if necessary. * @@ -559,28 +560,31 @@ var BasicRenderer = AbstractRenderer.extend({ widget.__node = node; // TODO get rid of this if possible one day - // Prepare widget rendering and save the related deferred + // Prepare widget rendering and save the related promise var def = widget._widgetRenderAndInsert(function () {}); - var async = def.state() === 'pending'; - var $el = async ? $('<div>') : widget.$el; - if (async) { - this.defs.push(def); - } + var $el = $('<div>'); + + this.defs.push(def); // Update the modifiers registration by associating the widget and by // giving the modifiers options now (as the potential callback is // associated to new widget) var self = this; def.then(function () { - if (async) { - $el.replaceWith(widget.$el); - } + // when the caller of renderFieldWidget uses something like + // this.renderFieldWidget(...).addClass(...), the class is added on + // the temporary div and not on the actual element that will be + // rendered. As we do not return a promise and some callers cannot + // wait for this.defs, we copy those attributes to the final element. + widget.$el.attr($el.getAttributes()); + + $el.replaceWith(widget.$el); self._registerModifiers(node, record, widget, { callback: function (element, modifiers, record) { element.$el.toggleClass('o_field_empty', !!( - record.data.id - && (modifiers.readonly || mode === 'readonly') - && !element.widget.isSet() + record.data.id && + (modifiers.readonly || mode === 'readonly') && + !element.widget.isSet() )); }, keepBaseMode: !!options.keepBaseMode, @@ -618,10 +622,10 @@ var BasicRenderer = AbstractRenderer.extend({ * possible. * * @abstract - * @returns {Deferred} + * @returns {Promise} */ _renderView: function () { - return $.when(); + return Promise.resolve(); }, /** * Instantiate custom widgets @@ -637,22 +641,17 @@ var BasicRenderer = AbstractRenderer.extend({ this.widgets.push(widget); - // Prepare widget rendering and save the related deferred + // Prepare widget rendering and save the related promise var def = widget._widgetRenderAndInsert(function () {}); - var async = def.state() === 'pending'; - if (async) { - this.defs.push(def); - } - var $el = async ? $('<div>') : widget.$el; + this.defs.push(def); + var $el = $('<div>'); var self = this; def.then(function () { self._handleAttributes(widget.$el, node); self._registerModifiers(node, record, widget); widget.$el.addClass('o_widget'); - if (async) { - $el.replaceWith(widget.$el); - } + $el.replaceWith(widget.$el); }); return $el; @@ -709,7 +708,7 @@ var BasicRenderer = AbstractRenderer.extend({ * * @private * @param {Object} record - * @returns {Deferred} resolved once finished + * @returns {Promise} resolved once finished */ _updateAllModifiers: function (record) { var self = this; @@ -722,7 +721,7 @@ var BasicRenderer = AbstractRenderer.extend({ }); delete this.defs; - return $.when.apply($, defs); + return Promise.all(defs); }, //-------------------------------------------------------------------------- diff --git a/addons/web/static/src/js/views/basic/basic_view.js b/addons/web/static/src/js/views/basic/basic_view.js index 313bed44708..008bbcf8775 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -93,7 +93,7 @@ var BasicView = AbstractView.extend({ * * @override * @private - * @returns {Deferred} + * @returns {Promise} */ _loadData: function (model) { if (this.recordID) { @@ -195,7 +195,7 @@ var BasicView = AbstractView.extend({ } }); } - return $.when(def).then(function () { + return Promise.resolve(def).then(function () { return model.get(record.id); }); } diff --git a/addons/web/static/src/js/views/calendar/calendar_controller.js b/addons/web/static/src/js/views/calendar/calendar_controller.js index 0934ae462b3..fa41706de2b 100644 --- a/addons/web/static/src/js/views/calendar/calendar_controller.js +++ b/addons/web/static/src/js/views/calendar/calendar_controller.js @@ -143,7 +143,7 @@ var CalendarController = AbstractController.extend({ * * @private * @param {string} to either 'prev', 'next' or 'today' - * @returns {Deferred} + * @returns {Promise} */ _move: function (to) { this.model[to](); @@ -153,7 +153,7 @@ var CalendarController = AbstractController.extend({ * @private * @param {Object} record * @param {integer} record.id - * @returns {Deferred} + * @returns {Promise} */ _updateRecord: function (record) { return this.model.updateRecord(record).then(this.reload.bind(this, {})); @@ -388,18 +388,18 @@ var CalendarController = AbstractController.extend({ self.quick.destroy(); self.quick = null; self.reload(); + self.quickCreating = false; }) - .fail(function (error, errorEvent) { + .guardedCatch(function (result) { + var errorEvent = result.event; // This will occurs if there are some more fields required // Preventdefaulting the error event will prevent the traceback window errorEvent.preventDefault(); event.data.options.disableQuickCreate = true; event.data.data.on_save = self.quick.destroy.bind(self.quick); self._onOpenCreate(event.data); - }) - .always(function () { self.quickCreating = false; - }); + }) }, /** * Called when we want to open or close the sidebar. diff --git a/addons/web/static/src/js/views/calendar/calendar_model.js b/addons/web/static/src/js/views/calendar/calendar_model.js index aaf52057ad6..9d8cf04f30f 100644 --- a/addons/web/static/src/js/views/calendar/calendar_model.js +++ b/addons/web/static/src/js/views/calendar/calendar_model.js @@ -177,7 +177,7 @@ return AbstractModel.extend({ /** * @override * @param {any} params - * @returns {Deferred} + * @returns {Promise} */ load: function (params) { var self = this; @@ -199,15 +199,18 @@ return AbstractModel.extend({ // fields to display color, e.g.: user_id.partner_id this.fieldColor = params.fieldColor; - if (!this.preload_def) { - this.preload_def = $.Deferred(); - $.when( - this._rpc({model: this.modelName, method: 'check_access_rights', args: ["write", false]}), - this._rpc({model: this.modelName, method: 'check_access_rights', args: ["create", false]})) - .then(function (write, create) { - self.write_right = write; - self.create_right = create; - self.preload_def.resolve(); + if (!this.preloadPromise) { + this.preloadPromise = new Promise(function (resolve, reject) { + Promise.all([ + self._rpc({model: self.modelName, method: 'check_access_rights', args: ["write", false]}), + self._rpc({model: self.modelName, method: 'check_access_rights', args: ["create", false]}) + ]).then(function (result) { + var write = result[0]; + var create = result[1]; + self.write_right = write; + self.create_right = create; + resolve(); + }).guardedCatch(reject); }); } @@ -229,7 +232,7 @@ return AbstractModel.extend({ } }); - return this.preload_def.then(this._loadCalendar.bind(this)); + return this.preloadPromise.then(this._loadCalendar.bind(this)); }, /** * Move the current date range to the next period @@ -247,7 +250,7 @@ return AbstractModel.extend({ * @override * @param {Object} [params.context] * @param {Array} [params.domain] - * @returns {Deferred} + * @returns {Promise} */ reload: function (handle, params) { if (params.domain) { @@ -308,7 +311,7 @@ return AbstractModel.extend({ /** * @param {Object} record * @param {integer} record.id - * @returns {Deferred} + * @returns {Promise} */ updateRecord: function (record) { // Cannot modify actual name yet @@ -430,7 +433,7 @@ return AbstractModel.extend({ }, /** * @private - * @returns {Deferred} + * @returns {Promise} */ _loadCalendar: function () { var self = this; @@ -439,7 +442,7 @@ return AbstractModel.extend({ var defs = _.map(this.data.filters, this._loadFilter.bind(this)); - return $.when.apply($, defs).then(function () { + return Promise.all(defs).then(function () { return self._rpc({ model: self.modelName, method: 'search_read', @@ -450,10 +453,10 @@ return AbstractModel.extend({ .then(function (events) { self._parseServerData(events); self.data.data = _.map(events, self._recordToCalendarEvent.bind(self)); - return $.when( + return Promise.all([ self._loadColors(self.data, self.data.data), self._loadRecordsToFilters(self.data, self.data.data) - ); + ]); }); }); }, @@ -461,7 +464,7 @@ return AbstractModel.extend({ * @private * @param {any} element * @param {any} events - * @returns {Deferred} + * @returns {Promise} */ _loadColors: function (element, events) { if (this.fieldColor) { @@ -472,16 +475,16 @@ return AbstractModel.extend({ }); this.model_color = this.fields[fieldName].relation || element.model; } - return $.Deferred().resolve(); + return Promise.resolve(); }, /** * @private * @param {any} filter - * @returns {Deferred} + * @returns {Promise} */ _loadFilter: function (filter) { if (!filter.write_model) { - return $.when(); + return Promise.resolve(); } var field = this.fields[filter.fieldName]; @@ -540,7 +543,7 @@ return AbstractModel.extend({ * @private * @param {any} element * @param {any} events - * @returns {Deferred} + * @returns {Promise} */ _loadRecordsToFilters: function (element, events) { var self = this; @@ -608,7 +611,7 @@ return AbstractModel.extend({ to_read[model] = _.object(res); })); }); - return $.when.apply($, defs).then(function () { + return Promise.all(defs).then(function () { _.each(self.data.filters, function (filter) { if (filter.write_model) { return; diff --git a/addons/web/static/src/js/views/calendar/calendar_renderer.js b/addons/web/static/src/js/views/calendar/calendar_renderer.js index 0ea7d60b1ce..c66b7bcc665 100644 --- a/addons/web/static/src/js/views/calendar/calendar_renderer.js +++ b/addons/web/static/src/js/views/calendar/calendar_renderer.js @@ -85,7 +85,7 @@ var SidebarFilter = Widget.extend(FieldManagerMixin, { }); defs.push(def); } - return $.when.apply($, defs); + return Promise.all(defs); }, /** @@ -193,7 +193,7 @@ return AbstractRenderer.extend({ }, /** * @override - * @returns {Deferred} + * @returns {Promise} */ start: function () { this._initSidebar(); @@ -460,7 +460,7 @@ return AbstractRenderer.extend({ * * @override method from AbstractRenderer * @private - * @returns {Deferred} + * @returns {Promise} */ _render: function () { var $calendar = this.$calendar; @@ -504,7 +504,7 @@ return AbstractRenderer.extend({ this.$sidebar_container.toggleClass('o_sidebar_hidden', fullWidth); this.$sidebar.toggleClass('o_hidden', fullWidth); - this._renderFilters(); + var filterProm = this._renderFilters(); this.$calendar.appendTo('body'); if (scrollTop) { this.$calendar.fullCalendar('reinitView'); @@ -514,7 +514,7 @@ return AbstractRenderer.extend({ this._renderEvents(); this.$calendar.prependTo(this.$('.o_calendar_view')); - return this._super.apply(this, arguments); + return Promise.all([filterProm, this._super.apply(this, arguments)]); }, /** * Render all events @@ -529,25 +529,47 @@ return AbstractRenderer.extend({ * Render all filters * * @private + * @returns {Promise} resolved when all filters have been rendered */ _renderFilters: function () { - var self = this; _.each(this.filters || (this.filters = []), function (filter) { filter.destroy(); }); if (this.state.fullWidth) { return; } - _.each(this.state.filters, function (options) { + return this._renderFiltersOneByOne(); + }, + /** + * Renders each filter one by one, waiting for the first filter finished to + * be rendered and appended to render the next one. + * We need to do like this since render a filter is asynchronous, we don't + * know which one will be appened at first and we want tp force them to be + * rendered in order. + * + * @param {number} filterIndex if not set, 0 by default + * @returns {Promise} resolved when all filters have been rendered + */ + _renderFiltersOneByOne: function (filterIndex) { + filterIndex = filterIndex || 0; + var arrFilters = _.toArray(this.state.filters); + var prom; + if (filterIndex < arrFilters.length) { + var options = arrFilters[filterIndex]; if (!_.find(options.filters, function (f) {return f.display == null || f.display;})) { return; } - options.getColor = self.getColor.bind(self); - options.fields = self.state.fields; + + var self = this; + options.getColor = this.getColor.bind(this); + options.fields = this.state.fields; var filter = new SidebarFilter(self, options); - filter.appendTo(self.$sidebar); - self.filters.push(filter); - }); + prom = filter.appendTo(this.$sidebar).then(function () { + return self._renderFiltersOneByOne(filterIndex + 1); + }); + this.filters.push(filter); + } + return Promise.resolve(prom); }, //-------------------------------------------------------------------------- diff --git a/addons/web/static/src/js/views/control_panel/control_panel_controller.js b/addons/web/static/src/js/views/control_panel/control_panel_controller.js index 40b1c45b9c7..913c226ba85 100644 --- a/addons/web/static/src/js/views/control_panel/control_panel_controller.js +++ b/addons/web/static/src/js/views/control_panel/control_panel_controller.js @@ -63,14 +63,14 @@ var ControlPanelController = mvc.Controller.extend({ }, /** * @param {Object} state a ControlPanelModel state - * @returns {Deferred<Object>} the result of `getSearchState` + * @returns {Promise<Object>} the result of `getSearchState` */ importState: function (state) { var defs = []; this.model.importState(state); defs.push(this.getSearchQuery()); defs.push(this.renderer.updateState(this.model.get())); - return $.when(defs).then(function (defsResults) { + return Promise.all(defs).then(function (defsResults) { return defsResults[0]; }); }, @@ -127,7 +127,7 @@ var ControlPanelController = mvc.Controller.extend({ }, /** * @private - * @returns {Deferred} + * @returns {Promise} */ _reportNewQueryAndRender: function () { this.trigger_up('search', this.model.getQuery()); diff --git a/addons/web/static/src/js/views/control_panel/control_panel_model.js b/addons/web/static/src/js/views/control_panel/control_panel_model.js index e59dbfb1036..ad0d80de7a3 100644 --- a/addons/web/static/src/js/views/control_panel/control_panel_model.js +++ b/addons/web/static/src/js/views/control_panel/control_panel_model.js @@ -325,7 +325,7 @@ var ControlPanelModel = mvc.Model.extend({ * @param {string[]} [params.searchMenuTypes=[]] * @param {Object} [params.timeRanges] * @param {boolean} [params.withSearchBar] - * @returns {Deferred} + * @returns {Promise} */ load: function (params) { var self = this; @@ -337,11 +337,11 @@ var ControlPanelModel = mvc.Model.extend({ // and the info comming from arch put in params.groups (if any) // will be lost. This is not a problem because the state // won't be use elsewhere. - return $.when(); + return Promise.resolve(); } if (params.initialState) { this.importState(params.initialState); - return $.when(); + return Promise.resolve(); } else { var groups = params.groups || []; groups.forEach(function (group) { @@ -351,7 +351,7 @@ var ControlPanelModel = mvc.Model.extend({ this._createEmptyGroup('groupBy'); } this._createGroupOfTimeRanges(); - return $.when.apply($, self._loadSearchDefaults()).then(function () { + return Promise.all(self._loadSearchDefaults()).then(function () { return self._loadFavorites().then(function () { if (self.query.length === 0) { self._activateDefaultFilters(); @@ -910,7 +910,7 @@ var ControlPanelModel = mvc.Model.extend({ * if this.activateDefaultFavorite is true. * * @private - * @returns {Deferred} + * @returns {Promise} */ _loadFavorites: function () { var self = this; @@ -980,7 +980,7 @@ var ControlPanelModel = mvc.Model.extend({ * is asynchronous. * * @private - * @returns {Deferred[]} + * @returns {Promise[]} */ _loadSearchDefaults: function () { var self = this; @@ -1044,7 +1044,7 @@ var ControlPanelModel = mvc.Model.extend({ * * @private * @param {Object} favorite - * @returns {Deferred} + * @returns {Promise} */ _saveQuery: function (favorite) { var self = this; diff --git a/addons/web/static/src/js/views/control_panel/control_panel_renderer.js b/addons/web/static/src/js/views/control_panel/control_panel_renderer.js index fd9037cdf6f..a1da2925eb6 100644 --- a/addons/web/static/src/js/views/control_panel/control_panel_renderer.js +++ b/addons/web/static/src/js/views/control_panel/control_panel_renderer.js @@ -86,7 +86,7 @@ var ControlPanelRenderer = Renderer.extend({ var superDef = this._super.apply(this, arguments); var searchDef = this._renderSearch(); - return $.when(superDef, searchDef).then(function () { + return Promise.all([superDef, searchDef]).then(function () { self._setSearchMenusVisibility(); }); }, @@ -148,7 +148,7 @@ var ControlPanelRenderer = Renderer.extend({ * Update the state of the renderer state. It retriggers a full rerendering. * * @param {Object} state - * @returns {Deferred} + * @returns {Promise} */ updateState: function (state) { this.state = state; @@ -261,7 +261,7 @@ var ControlPanelRenderer = Renderer.extend({ * Renderer the search bar and the search menus * * @private - * @returns {Deferred} + * @returns {Promise} */ _renderSearch: function () { var defs = []; @@ -274,11 +274,11 @@ var ControlPanelRenderer = Renderer.extend({ if (this.withSearchBar) { defs.push(this._renderSearchBar()); } - return $.when(this, defs).then(this._focusSearchInput.bind(this)); + return Promise.all(defs).then(this._focusSearchInput.bind(this)); }, /** * @private - * @returns {Deferred} + * @returns {Promise} */ _renderSearchBar: function () { // TODO: might need a reload instead of a destroy/instantiate @@ -315,7 +315,7 @@ var ControlPanelRenderer = Renderer.extend({ * * @private * @param {string} menuType - * @returns {Deferred} + * @returns {Promise} */ _setupMenu: function (menuType) { var Menu; @@ -345,7 +345,7 @@ var ControlPanelRenderer = Renderer.extend({ * Instantiate the search menu determined by this.searchMenuTypes. * * @private - * @returns {Deferred[]} + * @returns {Promise[]} */ _setupMenus: function () { this.$subMenus = this._getSubMenusPlace(); diff --git a/addons/web/static/src/js/views/control_panel/search/filter_menu.js b/addons/web/static/src/js/views/control_panel/search/filter_menu.js index 04f333b37c4..4ded35cfecc 100644 --- a/addons/web/static/src/js/views/control_panel/search/filter_menu.js +++ b/addons/web/static/src/js/views/control_panel/search/filter_menu.js @@ -67,14 +67,14 @@ var FilterMenu = DropdownMenu.extend({ * Add a proposition inside the custom filter edition menu. * * @private - * @returns {$.Deferred} + * @returns {Promise} */ _appendProposition: function () { // make modern sear_filters code!!! It works but... var prop = new search_filters.ExtendedSearchProposition(this, this.fields); this.propositions.push(prop); this.$('.o_apply_filter').prop('disabled', false); - prop.insertBefore(this.$addFilterMenu); + return prop.insertBefore(this.$addFilterMenu); }, /** * Confirm a filter proposition, creates it and add it to the menu. diff --git a/addons/web/static/src/js/views/control_panel/search/search_bar.js b/addons/web/static/src/js/views/control_panel/search/search_bar.js index 77d6d7fa5e9..4c2e71a378f 100644 --- a/addons/web/static/src/js/views/control_panel/search/search_bar.js +++ b/addons/web/static/src/js/views/control_panel/search/search_bar.js @@ -46,7 +46,7 @@ var SearchBar = Widget.extend({ defs.push(self._renderFacet(facet)); }); defs.push(this._setupAutoCompletion()); - return $.when.apply($, defs); + return Promise.all(defs); }, //-------------------------------------------------------------------------- @@ -116,18 +116,21 @@ var SearchBar = Widget.extend({ * @param {Function} callback */ _getAutoCompleteSources: function (req, callback) { - var defs = _.invoke(this.autoCompleteSources, 'getAutocompletionValues', req.term); - $.when.apply($, defs).then(function () { - callback(_(arguments).chain() + var defs = this.autoCompleteSources.map(function (source) { + return source.getAutocompletionValues(req.term); + }); + Promise.all(defs).then(function (result) { + var resultCleaned = _(result).chain() .compact() .flatten(true) - .value()); - }); + .value(); + callback(resultCleaned); + }); }, /** * @private * @param {Object} facet - * @returns {Deferred} + * @returns {Promise} */ _renderFacet: function (facet) { var searchFacet = new SearchFacet(this, facet); @@ -136,7 +139,7 @@ var SearchBar = Widget.extend({ }, /** * @private - * @returns {Deferred} + * @returns {Promise} */ _setupAutoCompletion: function () { var self = this; diff --git a/addons/web/static/src/js/views/control_panel/search/search_bar_autocomplete_sources.js b/addons/web/static/src/js/views/control_panel/search/search_bar_autocomplete_sources.js index f1ac0ee0f2e..8adc385a789 100644 --- a/addons/web/static/src/js/views/control_panel/search/search_bar_autocomplete_sources.js +++ b/addons/web/static/src/js/views/control_panel/search/search_bar_autocomplete_sources.js @@ -32,7 +32,7 @@ var FilterInterface = Class.extend(mixins.EventDispatcherMixin, { * label. They will be used by search_bar in @_onAutoCompleteSelected. * * @param {string} value value to getAutocompletionValues - * @returns {Deferred<null|Array>} + * @returns {Promise<null|Array>} */ getAutocompletionValues: function (value) { var result; @@ -46,7 +46,7 @@ var FilterInterface = Class.extend(mixins.EventDispatcherMixin, { }, }]; } - return $.when(result); + return Promise.resolve(result); }, }); @@ -83,7 +83,7 @@ var Field = FilterInterface.extend(ServicesMixin, { * @override */ getAutocompletionValues: function (value) { - return $.when([{ + return Promise.resolve([{ label: this._getAutocompletionLabel(value), facet: this._getFacetValue(value), }]); @@ -194,7 +194,7 @@ var CharField = Field.extend({ * @override */ getAutocompletionValues: function (value) { - if (_.isEmpty(value)) { return $.when(null); } + if (_.isEmpty(value)) { return Promise.resolve(null); } return this._super.apply(this, arguments); }, }); @@ -210,7 +210,7 @@ var NumberField = Field.extend({ */ getAutocompletionValues: function (value) { var val = this.parse(value); - if (isNaN(val)) { return $.when(); } + if (isNaN(val)) { return Promise.resolve(); } return this._super.apply(this, arguments); }, }); @@ -261,8 +261,8 @@ var SelectionField = Field.extend({ facet: self._getFacetValue(sel) }; }).value(); - if (_.isEmpty(results)) { return $.when(null); } - return $.when.call(null, [{ + if (_.isEmpty(results)) { return Promise.resolve(null); } + return Promise.resolve([{ label: _.escape(this.attrs.string) }].concat(results)); }, @@ -310,15 +310,15 @@ var DateField = Field.extend({ t = (this.attrs && this.attrs.type === 'datetime') ? 'datetime' : 'date'; v = field_utils.parse[t](value, {type: t}, {timezone: true}); } catch (e) { - return $.when(null); + return Promise.resolve(null); } var m = moment(v, t === 'datetime' ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'); - if (!m.isValid()) { return $.when(null); } + if (!m.isValid()) { return Promise.resolve(null); } var dateString = field_utils.format[t](m, {type: t}); var label = this._getAutocompletionLabel(dateString); var facet = this._getFacetValue(dateString, m.toDate()); - return $.when([{ + return Promise.resolve([{ label: label, facet: facet, }]); @@ -379,9 +379,9 @@ var ManyToOneField = CharField.extend({ * @override */ getAutocompletionValues: function (value) { - if (_.isEmpty(value)) { return $.when(null); } + if (_.isEmpty(value)) { return Promise.resolve(null); } var label = this._getAutocompletionLabel(value); - return $.when([{ + return Promise.resolve([{ label: label, facet: this._getFacetValue(value), expand: this._expand.bind(this), diff --git a/addons/web/static/src/js/views/control_panel/search/search_filters.js b/addons/web/static/src/js/views/control_panel/search/search_filters.js index 80a9019c02d..ae0ed12772e 100644 --- a/addons/web/static/src/js/views/control_panel/search/search_filters.js +++ b/addons/web/static/src/js/views/control_panel/search/search_filters.js @@ -40,7 +40,9 @@ var ExtendedSearchProposition = Widget.extend({ this.value = null; }, start: function () { - return this._super().done(this.proxy('changed')); + var parent = this._super(); + parent.then(this.proxy('changed')); + return parent; }, changed: function () { var nval = this.$(".o_searchview_extended_prop_field").val(); @@ -234,10 +236,10 @@ var DateTime = Field.extend({ return str; }, start: function () { - return $.when( + return Promise.all([ this._super.apply(this, arguments), this._create_new_widget("datewidget_0") - ); + ]); }, _create_new_widget: function (name) { this[name] = new (this._get_widget_class())(this); diff --git a/addons/web/static/src/js/views/field_manager_mixin.js b/addons/web/static/src/js/views/field_manager_mixin.js index bc61e92a89a..99aabc6e66d 100644 --- a/addons/web/static/src/js/views/field_manager_mixin.js +++ b/addons/web/static/src/js/views/field_manager_mixin.js @@ -41,7 +41,7 @@ var FieldManagerMixin = { * @param {string} dataPointID * @param {Object} changes * @param {OdooEvent} event - * @returns {Deferred} resolves when the change has been done, and the UI + * @returns {Promise} resolves when the change has been done, and the UI * updated */ _applyChanges: function (dataPointID, changes, event) { @@ -66,10 +66,10 @@ var FieldManagerMixin = { * @param {string} id basicModel Id for the changed record * @param {string[]} fields the fields (names) that have been changed * @param {OdooEvent} event the event that triggered the change - * @returns {Deferred} + * @returns {Promise} */ _confirmChange: function (id, fields, event) { - return $.when(); + return Promise.resolve(); }, /** * This method will be called whenever a save has been triggered by a change @@ -79,10 +79,10 @@ var FieldManagerMixin = { * @see _onFieldChanged * @abstract * @param {string} id The basicModel ID for the saved record - * @returns {Deferred} + * @returns {Promise} */ _confirmSave: function (id) { - return $.when(); + return Promise.resolve(); }, //-------------------------------------------------------------------------- @@ -103,9 +103,9 @@ var FieldManagerMixin = { // subrecord's form view), otherwise it bubbles up to the main form view // but its model doesn't have any data related to the given dataPointID event.stopPropagation(); - this._applyChanges(event.data.dataPointID, event.data.changes, event) - .done(event.data.onSuccess || function () {}) - .fail(event.data.onFailure || function () {}); + return this._applyChanges(event.data.dataPointID, event.data.changes, event) + .then(event.data.onSuccess || function () {}) + .guardedCatch(event.data.onFailure || function () {}); }, /** * Some widgets need to trigger a reload of their data. For example, a diff --git a/addons/web/static/src/js/views/form/form_controller.js b/addons/web/static/src/js/views/form/form_controller.js index d0400ef041f..5616a5099bc 100644 --- a/addons/web/static/src/js/views/form/form_controller.js +++ b/addons/web/static/src/js/views/form/form_controller.js @@ -84,7 +84,7 @@ var FormController = BasicController.extend({ * @todo make record creation a basic controller feature * @param {string} [parentID] if given, the parentID will be used as parent * for the new record. - * @returns {Deferred} + * @returns {Promise} */ createRecord: function (parentID) { var self = this; @@ -165,20 +165,23 @@ var FormController = BasicController.extend({ * @override method from BasicController * @param {jQueryElement} $node * @param {Object} options + * @returns {Promise} */ renderPager: function ($node, options) { options = _.extend({}, options, { validate: this.canBeDiscarded.bind(this), }); - this._super($node, options); + return this._super($node, options); }, /** * Instantiate and render the sidebar if a sidebar is requested * Sets this.sidebar * @param {jQuery} [$node] a jQuery node where the sidebar should be * inserted + * @return {Promise} **/ renderSidebar: function ($node) { + var self = this; if (this.hasSidebar) { var otherItems = []; if (this.is_action_enabled('delete')) { @@ -203,11 +206,12 @@ var FormController = BasicController.extend({ }, actions: _.extend(this.toolbarActions, {other: otherItems}), }); - this.sidebar.appendTo($node); - - // Show or hide the sidebar according to the view mode - this._updateSidebar(); + return this.sidebar.appendTo($node).then(function() { + // Show or hide the sidebar according to the view mode + self._updateSidebar(); + }); } + return Promise.resolve(); }, /** * Show a warning message if the user modified a translated field. For each @@ -296,7 +300,7 @@ var FormController = BasicController.extend({ * @private * @override method from field manager mixin * @param {string} id - id of the previously changed record - * @returns {Deferred} + * @returns {Promise} */ _confirmSave: function (id) { if (id === this.handle) { @@ -393,14 +397,19 @@ var FormController = BasicController.extend({ * @override * @private * @param {Object} state - * @returns {Deferred} + * @returns {Promise} */ _update: function () { - var title = this.getTitle(); - this._setTitle(title); - this._updateButtons(); - this._updateSidebar(); - return this._super.apply(this, arguments).then(this.autofocus.bind(this)); + var self = this; + + return this._super.apply(this, arguments).then(function() { + var title = self.getTitle(); + self._setTitle(title); + self._updateButtons(); + self._updateSidebar(); + + self.autofocus(); + }); }, /** * @private @@ -470,13 +479,11 @@ var FormController = BasicController.extend({ } var attrs = ev.data.attrs; if (attrs.confirm) { - var d = $.Deferred(); - Dialog.confirm(this, attrs.confirm, { - confirm_callback: saveAndExecuteAction, - }).on("closed", null, function () { - d.resolve(); + def = new Promise(function (resolve, reject) { + Dialog.confirm(this, attrs.confirm, { + confirm_callback: saveAndExecuteAction, + }).on("closed", null, resolve); }); - def = d.promise(); } else if (attrs.special === 'cancel') { def = this._callButtonAction(attrs, ev.data.record); } else if (!attrs.special || attrs.special === 'save') { @@ -484,7 +491,7 @@ var FormController = BasicController.extend({ def = saveAndExecuteAction(); } - def.always(this._enableButtons.bind(this)); + def.then(this._enableButtons.bind(this)).guardedCatch(this._enableButtons.bind(this)); }, /** * Called when the user wants to create a new record -> @see createRecord @@ -659,9 +666,7 @@ var FormController = BasicController.extend({ ev.stopPropagation(); // Prevent x2m lines to be auto-saved var self = this; this._disableButtons(); - this.saveRecord().always(function () { - self._enableButtons(); - }); + this.saveRecord().then(this._enableButtons.bind(this)).guardedCatch(this._enableButtons.bind(this)); }, /** * Called when user swipes left. Move to next record. diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js index ca26d7e90ad..97ab575cbc3 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -251,7 +251,7 @@ var FormRenderer = BasicRenderer.extend({ * @param {string} [params.mode] new mode, either 'edit' or 'readonly' * @param {string[]} [params.fieldNames] if given, the renderer will only * update the fields in this list - * @returns {Deferred} + * @returns {Promise} */ updateState: function (state, params) { this.mode = (params && 'mode' in params) ? params.mode : this.mode; @@ -342,8 +342,8 @@ var FormRenderer = BasicRenderer.extend({ * @private */ _postProcessField: function (widget, node) { + this._super.apply(this, arguments); this._setIDForLabel(widget, this._getIDForLabel(node.attrs.name)); - this._handleAttributes(widget.$el, node); if (JSON.parse(node.attrs.default_focus || "0")) { this.defaultFocusField = widget; } @@ -355,7 +355,14 @@ var FormRenderer = BasicRenderer.extend({ */ _renderButtonBox: function (node) { var self = this; - var $result = $('<' + node.tag + '>', { 'class': 'o_not_full' }); + var $result = $('<' + node.tag + '>', {class: 'o_not_full'}); + + // The rendering of buttons may be async (see renderFieldWidget), so we + // must wait for the buttons to be ready (and their modifiers to be + // applied) before manipulating them, as we check if they are visible or + // not. To do so, we extract from this.defs the promises corresponding + // to the buttonbox buttons, and wait for them to be resolved. + var nextDefIndex = this.defs.length; var buttons = _.map(node.children, function (child) { if (child.tag === 'button') { return self._renderStatButton(child); @@ -363,48 +370,62 @@ var FormRenderer = BasicRenderer.extend({ return self._renderNode(child); } }); - var buttons_partition = _.partition(buttons, function ($button) { - return $button.is('.o_invisible_modifier'); + + // At this point, each button is an empty div that will be replaced by + // the real $el of the button when it is ready (with replaceWith). + // However, this only works if the empty div is appended somewhere, so + // we here append them into a wrapper, and unwrap them once they have + // been replaced. + var $tempWrapper = $('<div>'); + _.each(buttons, function ($button) { + $button.appendTo($tempWrapper); }); - var invisible_buttons = buttons_partition[0]; - var visible_buttons = buttons_partition[1]; + var defs = this.defs.slice(nextDefIndex); + Promise.all(defs).then(function () { + buttons = $tempWrapper.children(); + var buttons_partition = _.partition(buttons, function (button) { + return $(button).is('.o_invisible_modifier'); + }); + var invisible_buttons = buttons_partition[0]; + var visible_buttons = buttons_partition[1]; - // Get the unfolded buttons according to window size - var nb_buttons = [2, 2, 4, 6][config.device.size_class] || 7; - var unfolded_buttons = visible_buttons.slice(0, nb_buttons).concat(invisible_buttons); + // Get the unfolded buttons according to window size + var nb_buttons = [2, 2, 4, 6][config.device.size_class] || 7; + var unfolded_buttons = visible_buttons.slice(0, nb_buttons).concat(invisible_buttons); - // Get the folded buttons - var folded_buttons = visible_buttons.slice(nb_buttons); - if (folded_buttons.length === 1) { - unfolded_buttons = buttons; - folded_buttons = []; - } + // Get the folded buttons + var folded_buttons = visible_buttons.slice(nb_buttons); + if (folded_buttons.length === 1) { + unfolded_buttons = buttons; + folded_buttons = []; + } - // Toggle class to tell if the button box is full (CSS requirement) - var full = (visible_buttons.length > nb_buttons); - $result.toggleClass('o_full', full).toggleClass('o_not_full', !full); + // Toggle class to tell if the button box is full (CSS requirement) + var full = (visible_buttons.length > nb_buttons); + $result.toggleClass('o_full', full).toggleClass('o_not_full', !full); - // Add the unfolded buttons - _.each(unfolded_buttons, function ($button) { - $button.appendTo($result); - }); + // Add the unfolded buttons + _.each(unfolded_buttons, function (button) { + $(button).appendTo($result); + }); - // Add the dropdown with folded buttons if any - if (folded_buttons.length) { - $result.append(dom.renderButton({ - attrs: { - class: 'oe_stat_button o_button_more dropdown-toggle', - 'data-toggle': 'dropdown', - }, - text: _t("More"), - })); + // Add the dropdown with folded buttons if any + if (folded_buttons.length) { + $result.append(dom.renderButton({ + attrs: { + 'class': 'oe_stat_button o_button_more dropdown-toggle', + 'data-toggle': 'dropdown', + }, + text: _t("More"), + })); - var $dropdown = $("<div>", {'class': "dropdown-menu o_dropdown_more", role: "menu"}); - _.each(folded_buttons, function ($button) { - $button.addClass('dropdown-item').appendTo($dropdown); - }); - $dropdown.appendTo($result); - } + var $dropdown = $("<div>", {class: "dropdown-menu o_dropdown_more", role: "menu"}); + _.each(folded_buttons, function (button) { + $(button).addClass('dropdown-item').appendTo($dropdown); + }); + $dropdown.appendTo($result); + } + }); this._handleAttributes($result, node); this._registerModifiers(node, this.state, $result); @@ -477,6 +498,7 @@ var FormRenderer = BasicRenderer.extend({ _renderInnerGroup: function (node) { var self = this; var $result = $('<table/>', {class: 'o_group o_inner_group'}); + var $tbody = $('<tbody />').appendTo($result); this._handleAttributes($result, node); this._registerModifiers(node, this.state, $result); @@ -490,7 +512,7 @@ var FormRenderer = BasicRenderer.extend({ var rows = []; var $currentRow = $('<tr/>'); var currentColspan = 0; - _.each(node.children, function (child) { + node.children.forEach(function (child) { if (child.tag === 'newline') { rows.push($currentRow); $currentRow = $('<tr/>'); @@ -537,7 +559,7 @@ var FormRenderer = BasicRenderer.extend({ var $el = $(el); $el.css('width', ((parseInt($el.attr('colspan'), 10) || 1) * nonLabelColSize) + '%'); }); - $result.append($tr); + $tbody.append($tr); }); return $result; @@ -575,7 +597,7 @@ var FormRenderer = BasicRenderer.extend({ * * For fields, it will return the $el of the field widget. Note that this * method is synchronous, field widgets are instantiated and appended, but - * if they are asynchronous, they register their deferred in this.defs, and + * if they are asynchronous, they register their promises in this.defs, and * the _renderView method will properly wait. * * @private @@ -705,7 +727,8 @@ var FormRenderer = BasicRenderer.extend({ if (node.attrs.class) { $result.addClass(node.attrs.class); } - $result.append(_.map(node.children, this._renderNode.bind(this))); + var allNodes = node.children.map(this._renderNode.bind(this)); + $result.append(allNodes); return $result; }, /** @@ -870,7 +893,7 @@ var FormRenderer = BasicRenderer.extend({ _renderTagSheet: function (node) { this.has_sheet = true; var $sheet = $('<div>', {class: 'clearfix o_form_sheet'}); - $sheet.append(_.map(node.children, this._renderNode.bind(this))); + $sheet.append(node.children.map(this._renderNode.bind(this))); return $sheet; }, /** @@ -885,12 +908,12 @@ var FormRenderer = BasicRenderer.extend({ }, /** * Main entry point for the rendering. From here, we call _renderNode on - * the root of the arch, then, when every deferred (from the field widgets) + * the root of the arch, then, when every promise (from the field widgets) * are done, it will resolves itself. * * @private * @override method from BasicRenderer - * @returns {Deferred} + * @returns {Promise} */ _renderView: function () { var self = this; @@ -901,17 +924,17 @@ var FormRenderer = BasicRenderer.extend({ var $form = this._renderNode(this.arch).addClass(this.className); delete this.defs; - return $.when.apply($, defs).then(function () { + return Promise.all(defs).then(function () { self._updateView($form.contents()); if (self.state.res_id in self.alertFields) { self.displayTranslationAlert(); } - }, function () { - $form.remove(); }).then(function(){ if (self.lastActivatedFieldIndex >= 0) { self._activateNextFieldWidget(self.state, self.lastActivatedFieldIndex); } + }).guardedCatch(function () { + $form.remove(); }); }, /** diff --git a/addons/web/static/src/js/views/form/form_view.js b/addons/web/static/src/js/views/form/form_view.js index 5a00350c68f..0703d9713d5 100644 --- a/addons/web/static/src/js/views/form/form_view.js +++ b/addons/web/static/src/js/views/form/form_view.js @@ -93,7 +93,7 @@ var FormView = BasicView.extend({ * * @private * @param {Widget} parent the parent of the model, if it has to be created - * @returns {Deferred} + * @returns {Promise} */ _loadSubviews: function (parent) { var self = this; @@ -145,7 +145,7 @@ var FormView = BasicView.extend({ } }); } - return $.when.apply($, defs); + return Promise.all(defs); }, /** * We set here the limit for the number of records fetched (in one page). diff --git a/addons/web/static/src/js/views/graph/graph_model.js b/addons/web/static/src/js/views/graph/graph_model.js index 4c359a4778f..91b017524d8 100644 --- a/addons/web/static/src/js/views/graph/graph_model.js +++ b/addons/web/static/src/js/views/graph/graph_model.js @@ -50,7 +50,7 @@ return AbstractModel.extend({ * @param {string[]} params.domain * @param {Object} params.intervalMapping object linking fieldNames with intervals. * this could be useful to simplify the code. For now this parameter is not used. - * @returns {Deferred} The deferred does not return a handle, we don't need + * @returns {Promise} The promise does not return a handle, we don't need * to keep track of various entities. */ load: function (params) { @@ -92,7 +92,7 @@ return AbstractModel.extend({ * @param {string[]} [params.groupBy] * @param {string} [params.mode] one of 'bar', 'pie', 'line' * @param {string} [params.measure] a valid field name - * @returns {Deferred} + * @returns {Promise} */ reload: function (handle, params) { if ('context' in params) { @@ -130,7 +130,7 @@ return AbstractModel.extend({ } if ('mode' in params) { this.chart.mode = params.mode; - return $.when(); + return Promise.resolve(); } return this._loadGraph(); }, @@ -145,7 +145,7 @@ return AbstractModel.extend({ * in the field list, because they can be defined with an aggregation * function, such as my_date:week * - * @returns {Deferred} + * @returns {Promise} */ _loadGraph: function () { var groupedBy = this.chart.groupedBy; @@ -186,7 +186,7 @@ return AbstractModel.extend({ }).then(this._processData.bind(this, 'comparisonData'))); } - return $.when.apply($, defs); + return Promise.all(defs); }, /** * Since read_group is insane and returns its result on different keys diff --git a/addons/web/static/src/js/views/graph/graph_renderer.js b/addons/web/static/src/js/views/graph/graph_renderer.js index 9353301ca09..6311df5d8b2 100644 --- a/addons/web/static/src/js/views/graph/graph_renderer.js +++ b/addons/web/static/src/js/views/graph/graph_renderer.js @@ -99,7 +99,7 @@ return AbstractRenderer.extend({ * * @override * @private - * @returns {Deferred} The _super deferred is actually resolved immediately + * @returns {Promise} The _super promise is actually resolved immediately */ _render: function () { if (this.to_remove) { diff --git a/addons/web/static/src/js/views/kanban/kanban_column.js b/addons/web/static/src/js/views/kanban/kanban_column.js index 2828ca491ca..b4aecd7a236 100644 --- a/addons/web/static/src/js/views/kanban/kanban_column.js +++ b/addons/web/static/src/js/views/kanban/kanban_column.js @@ -97,10 +97,7 @@ var KanbanColumn = Widget.extend({ this.$header = this.$('.o_kanban_header'); for (var i = 0; i < this.data_records.length; i++) { - var def = this._addRecord(this.data_records[i]); - if (def.state() === 'pending') { - defs.push(def); - } + defs.push(this._addRecord(this.data_records[i])); } if (!config.device.isMobile) { @@ -170,7 +167,7 @@ var KanbanColumn = Widget.extend({ this.$('.o_kanban_load_more').html(QWeb.render('KanbanView.LoadMore', {widget: this})); } - return $.when.apply($, defs); + return Promise.all(defs); }, /** * Called when a record has been quick created, as a new column is rendered @@ -193,7 +190,7 @@ var KanbanColumn = Widget.extend({ /** * Adds the quick create record to the top of the column. * - * @returns {Deferred} + * @returns {Promise} */ addQuickCreate: function () { if (this.folded) { @@ -205,7 +202,7 @@ var KanbanColumn = Widget.extend({ } if (this.quickCreateWidget) { - return $.Deferred().reject(); + return Promise.reject(); } this.trigger_up('close_quick_create'); // close other quick create widgets this.trigger_up('start_quick_create'); @@ -245,7 +242,7 @@ var KanbanColumn = Widget.extend({ * @param {Object} [options] * @param {string} [options.position] * 'before' to add at the top, add at the bottom by default - * @return {Deferred} + * @return {Promise} */ _addRecord: function (recordState, options) { var record = new this.KanbanRecord(this, recordState, this.record_options); diff --git a/addons/web/static/src/js/views/kanban/kanban_controller.js b/addons/web/static/src/js/views/kanban/kanban_controller.js index 3b41b26e015..58174c7812d 100644 --- a/addons/web/static/src/js/views/kanban/kanban_controller.js +++ b/addons/web/static/src/js/views/kanban/kanban_controller.js @@ -76,6 +76,7 @@ var KanbanController = BasicController.extend({ /** * @param {jQueryElement} $node + * @returns {Promise} */ renderButtons: function ($node) { if (this.hasButtons && this.is_action_enabled('create')) { @@ -86,8 +87,9 @@ var KanbanController = BasicController.extend({ this.$buttons.on('click', 'button.o-kanban-button-new', this._onButtonNew.bind(this)); this.$buttons.on('keydown', this._onButtonsKeyDown.bind(this)); this._updateButtons(); - this.$buttons.appendTo($node); + return Promise.resolve(this.$buttons.appendTo($node)); } + return Promise.resolve(); }, /** * Override to add the domain coming from the searchPanel (if any) to the @@ -110,7 +112,7 @@ var KanbanController = BasicController.extend({ params.domain = this.controlPanelDomain.concat(this.searchPanelDomain); var superProm = this._super.apply(this, arguments); var searchPanelProm = this._updateSearchPanel(); - return $.when(superProm, searchPanelProm).then(function () { + return Promise.all([superProm, searchPanelProm]).then(function () { // searchPanel has been re-rendered, so re-render the view return self.renderer.render(); }); @@ -124,7 +126,7 @@ var KanbanController = BasicController.extend({ * @override method comes from field manager mixin * @private * @param {string} id local id from the basic record data - * @returns {Deferred} + * @returns {Promise} */ _confirmSave: function (id) { var data = this.model.get(this.handle, {raw: true}); @@ -197,7 +199,7 @@ var KanbanController = BasicController.extend({ /** * @param {number[]} ids * @private - * @returns {Deferred} + * @returns {Promise} */ _resequenceColumns: function (ids) { var state = this.model.get(this.handle, {raw: true}); @@ -212,7 +214,7 @@ var KanbanController = BasicController.extend({ * @private * @param {string} column_id * @param {string[]} ids - * @returns {Deferred} + * @returns {Promise} */ _resequenceRecords: function (column_id, ids) { var self = this; @@ -246,7 +248,7 @@ var KanbanController = BasicController.extend({ }, /** * @private - * @returns {$.Promise} + * @returns {Promise} */ _updateSearchPanel: function () { return this._searchPanel.update({searchDomain: this.controlPanelDomain}); @@ -296,7 +298,7 @@ var KanbanController = BasicController.extend({ self.renderer.updateColumn(db_id, data); }); }); - }).fail(this.reload.bind(this, {})); + }).guardedCatch(this.reload.bind(this)); }, /** * @private @@ -400,7 +402,7 @@ var KanbanController = BasicController.extend({ var relatedModelName = state.fields[state.groupedBy[0]].relation; this.model .deleteRecords([column.db_id], relatedModelName) - .done(function () { + .then(function () { self.update({}, {reload: !column.isEmpty()}); }); }, @@ -415,11 +417,12 @@ var KanbanController = BasicController.extend({ var self = this; this.model.loadColumnRecords(ev.data.columnID).then(function (dbID) { var data = self.model.get(dbID); - self.renderer.updateColumn(dbID, data); - self._updateEnv(); - if (ev.data.onSuccess) { - ev.data.onSuccess(); - } + return self.renderer.updateColumn(dbID, data).then(function() { + self._updateEnv(); + if (ev.data.onSuccess) { + ev.data.onSuccess(); + } + }); }); }, /** @@ -470,8 +473,8 @@ var KanbanController = BasicController.extend({ this.model.createRecordInGroup(column.db_id, values) .then(update) - .fail(function (error, ev) { - ev.preventDefault(); + .guardedCatch(function (reason) { + reason.event.preventDefault(); var columnState = self.model.get(column.db_id, {raw: true}); var context = columnState.getContext(); var state = self.model.get(self.handle, {raw: true}); @@ -538,11 +541,15 @@ var KanbanController = BasicController.extend({ * * @private * @param {OdooEvent} ev + * @param {function} [ev.data.onSuccess] callback to execute after applying + * changes */ _onUpdateRecord: function (ev) { + var onSuccess = ev.data.onSuccess; + delete ev.data.onSuccess; var changes = _.clone(ev.data); ev.data.force_save = true; - this._applyChanges(ev.target.db_id, changes, ev); + this._applyChanges(ev.target.db_id, changes, ev).then(onSuccess); }, /** * Allow the user to archive/restore all the records of a column. diff --git a/addons/web/static/src/js/views/kanban/kanban_model.js b/addons/web/static/src/js/views/kanban/kanban_model.js index bfb6f5e0cc9..8b1135d3f15 100644 --- a/addons/web/static/src/js/views/kanban/kanban_model.js +++ b/addons/web/static/src/js/views/kanban/kanban_model.js @@ -20,7 +20,7 @@ var KanbanModel = BasicModel.extend({ * * @param {string} groupID localID of the group * @param {integer} resId id of the record - * @returns {Deferred<string>} resolves to the local id of the new record + * @returns {Promise<string>} resolves to the local id of the new record */ addRecordToGroup: function (groupID, resId) { var self = this; @@ -52,7 +52,7 @@ var KanbanModel = BasicModel.extend({ * * @param {string} name * @param {string} parentID localID of the parent of the group - * @returns {Deferred<string>} resolves to the local id of the new group + * @returns {Promise<string>} resolves to the local id of the new group */ createGroup: function (name, parentID) { var self = this; @@ -60,7 +60,7 @@ var KanbanModel = BasicModel.extend({ var groupBy = parent.groupedBy[0]; var groupByField = parent.fields[groupBy]; if (!groupByField || groupByField.type !== 'many2one') { - return $.Deferred().reject(); // only supported when grouped on m2o + return Promise.reject(); // only supported when grouped on m2o } return this._rpc({ model: groupByField.relation, @@ -99,7 +99,7 @@ var KanbanModel = BasicModel.extend({ * * @param {string} groupID * @param {Object} values - * @returns {Deferred} resolved with the local id of the created record + * @returns {Promise} resolved with the local id of the created record */ createRecordInGroup: function (groupID, values) { var self = this; @@ -187,7 +187,7 @@ var KanbanModel = BasicModel.extend({ * Opens a given group and loads its <limit> first records * * @param {string} groupID - * @returns {Deferred} + * @returns {Promise} */ loadColumnRecords: function (groupID) { var dataPoint = this.localData[groupID]; @@ -198,7 +198,7 @@ var KanbanModel = BasicModel.extend({ * Load more records in a group. * * @param {string} groupID localID of the group - * @returns {Deferred<string>} resolves to the localID of the group + * @returns {Promise<string>} resolves to the localID of the group */ loadMore: function (groupID) { var group = this.localData[groupID]; @@ -213,7 +213,7 @@ var KanbanModel = BasicModel.extend({ * @param {string} recordID localID of the record * @param {string} groupID localID of the new group of the record * @param {string} parentID localID of the parent - * @returns {Deferred<string[]>} resolves to a pair [oldGroupID, newGroupID] + * @returns {Promise<string[]>} resolves to a pair [oldGroupID, newGroupID] */ moveRecord: function (recordID, groupID, parentID) { var self = this; @@ -330,7 +330,7 @@ var KanbanModel = BasicModel.extend({ /** * @private * @param {Object} dataPoint - * @returns {Deferred<Object>} + * @returns {Promise<Object>} */ _readProgressBarGroup: function (list, options) { var self = this; @@ -345,7 +345,8 @@ var KanbanModel = BasicModel.extend({ context: list.context, }, }); - return $.when(groupsDef, progressBarDef).then(function (a, data) { + return Promise.all([groupsDef, progressBarDef]).then(function (results) { + var data = results[1]; _.each(list.data, function (groupID) { var group = self.localData[groupID]; group.progressBarValues = _.extend({ @@ -365,13 +366,13 @@ var KanbanModel = BasicModel.extend({ * * @private * @param {Object} list a list of groups - * @returns {Deferred} + * @returns {Promise} */ _readTooltipFields: function (list) { var self = this; var groupedByField = list.fields[list.groupedBy[0].split(':')[0]]; if (groupedByField.type !== 'many2one') { - return $.when(); + return Promise.resolve(); } var groupIds = _.reduce(list.data, function (groupIds, id) { var res_id = self.get(id, {raw: true}).res_id; @@ -400,16 +401,16 @@ var KanbanModel = BasicModel.extend({ }); }); } - return $.when(); + return Promise.resolve(); }, /** - * Reloads all progressbar data. This is done after given deferred and - * insures that the given deferred's result is not lost. + * Reloads all progressbar data. This is done after given promise and + * insures that the given promise's result is not lost. * * @private * @param {string} recordID - * @param {Deferred} def - * @returns {Deferred} + * @param {Promise} def + * @returns {Promise} */ _reloadProgressBarGroupFromRecord: function (recordID, def) { var element = this.localData[recordID]; diff --git a/addons/web/static/src/js/views/kanban/kanban_record.js b/addons/web/static/src/js/views/kanban/kanban_record.js index 3dc1d6788bf..116de142b0c 100644 --- a/addons/web/static/src/js/views/kanban/kanban_record.js +++ b/addons/web/static/src/js/views/kanban/kanban_record.js @@ -62,7 +62,7 @@ var KanbanRecord = Widget.extend({ * @override */ start: function () { - return $.when(this._super.apply(this, arguments), this._render()); + return Promise.all([this._super.apply(this, arguments), this._render()]); }, /** * Called each time the record is attached to the DOM. @@ -85,7 +85,7 @@ var KanbanRecord = Widget.extend({ * Re-renders the record with a new state * * @param {Object} state - * @returns {Deferred} + * @returns {Promise} */ update: function (state) { // detach the widgets because the record will empty its $el, which will @@ -147,7 +147,7 @@ var KanbanRecord = Widget.extend({ } if (typeof variable === 'string') { var index = 0; - for (var i = 0 ; i < variable.length ; i++) { + for (var i = 0; i < variable.length; i++) { index += variable.charCodeAt(i); } return index % NB_KANBAN_RECORD_COLORS; @@ -195,7 +195,7 @@ var KanbanRecord = Widget.extend({ field = options.preview_image; var unique = this.record.__last_update && this.record.__last_update.value.replace(/[^0-9]/g, ''); var session = this.getSession(); - url = session.url('/web/image', {model: model, field: field, id: id, unique: unique}); + url = session.url('/web/image', { model: model, field: field, id: id, unique: unique }); if (cache !== undefined) { // Set the cache duration in seconds. url += '&cache=' + parseInt(cache, 10); @@ -300,6 +300,7 @@ var KanbanRecord = Widget.extend({ // field's widgets point of view // that dict being shared between records, we don't modify it // in place + var self = this; var attrs = Object.create(null); _.each(this.fieldsInfo[field_name], function (value, key) { if (_.str.startsWith(key, 't-att-')) { @@ -308,13 +309,13 @@ var KanbanRecord = Widget.extend({ } attrs[key] = value; }); - var options = _.extend({}, this.options, {attrs: attrs}); + var options = _.extend({}, this.options, { attrs: attrs }); var widget = new Widget(this, field_name, this.state, options); var def = widget.replace($field); - if (def.state() === 'pending') { - this.defs.push(def); - } - this._setFieldDisplay(widget.$el, field_name); + this.defs.push(def); + def.then(function () { + self._setFieldDisplay(widget.$el, field_name); + }); return widget; }, _processWidgets: function () { @@ -324,23 +325,23 @@ var KanbanRecord = Widget.extend({ var Widget = widgetRegistry.get($field.attr('name')); var widget = new Widget(self, self.state); - var def = widget._widgetRenderAndInsert(function () {}); - if (def.state() === 'pending') { - self.defs.push(def); - } - widget.$el.addClass('o_widget'); - $field.replaceWith(widget.$el); + var def = widget._widgetRenderAndInsert(function () { }); + self.defs.push(def); + def.then(function () { + $field.replaceWith(widget.$el); + widget.$el.addClass('o_widget'); + }); }); }, /** * Renders the record * - * @returns {Deferred} + * @returns {Promise} */ _render: function () { this.defs = []; this._replaceElement(this.qweb.render('kanban-box', this.qweb_context)); - this.$el.addClass('o_kanban_record').attr("tabindex",0); + this.$el.addClass('o_kanban_record').attr("tabindex", 0); this.$el.attr('role', 'article'); this.$el.data('record', this); if (this.$el.hasClass('oe_kanban_global_click') || @@ -357,9 +358,9 @@ var KanbanRecord = Widget.extend({ this._attachTooltip(); // We use boostrap tooltips for better and faster display - this.$('span.o_tag').tooltip({delay: {'show': 50}}); + this.$('span.o_tag').tooltip({ delay: { 'show': 50 } }); - return $.when.apply(this, this.defs); + return Promise.all(this.defs); }, /** * Sets particular classnames on a field $el according to the @@ -419,7 +420,7 @@ var KanbanRecord = Widget.extend({ var colorHelp = _.str.sprintf(_t("Card color: %s"), this._getColorname(this.recordData[color_field])); var colorClass = this._getColorClassname(this.recordData[color_field]); this.$el.addClass(colorClass); - this.$el.prepend('<span title="' + colorHelp + '" aria-label="' + colorHelp +'" role="img" class="oe_kanban_color_help"/>'); + this.$el.prepend('<span title="' + colorHelp + '" aria-label="' + colorHelp + '" role="img" class="oe_kanban_color_help"/>'); } }, /** @@ -454,7 +455,7 @@ var KanbanRecord = Widget.extend({ r.raw_value = value.toDate(); } else if (r.type === 'one2many' || r.type === 'many2many') { r.raw_value = value.count ? value.res_ids : []; - } else if (r.type === 'many2one' ) { + } else if (r.type === 'many2one') { r.raw_value = value && value.res_id || false; } else { r.raw_value = value; @@ -554,13 +555,13 @@ var KanbanRecord = Widget.extend({ switch (type) { case 'edit': - this.trigger_up('open_record', {id: this.db_id, mode: 'edit'}); + this.trigger_up('open_record', { id: this.db_id, mode: 'edit' }); break; case 'open': - this.trigger_up('open_record', {id: this.db_id}); + this.trigger_up('open_record', { id: this.db_id }); break; case 'delete': - this.trigger_up('kanban_record_delete', {id: this.db_id, record: this}); + this.trigger_up('kanban_record_delete', { id: this.db_id, record: this }); break; case 'action': case 'object': diff --git a/addons/web/static/src/js/views/kanban/kanban_record_quick_create.js b/addons/web/static/src/js/views/kanban/kanban_record_quick_create.js index 741cc6a898a..e69914acd49 100644 --- a/addons/web/static/src/js/views/kanban/kanban_record_quick_create.js +++ b/addons/web/static/src/js/views/kanban/kanban_record_quick_create.js @@ -47,14 +47,14 @@ var RecordQuickCreate = Widget.extend({ */ willStart: function () { var self = this; - var def1 = this._super.apply(this, arguments); - var def2; + var superWillStart = this._super.apply(this, arguments); + var viewsLoaded; if (this.formViewRef) { var views = [[false, 'form']]; var context = _.extend({}, this.context, { form_view_ref: this.formViewRef, }); - def2 = this.loadViews(this.model, context, views); + viewsLoaded = this.loadViews(this.model, context, views); } else { var fieldsView = {}; fieldsView.arch = '<form>' + @@ -65,9 +65,9 @@ var RecordQuickCreate = Widget.extend({ }; fieldsView.fields = fields; fieldsView.viewFields = fields; - def2 = $.when({form: fieldsView}); + viewsLoaded = Promise.resolve({form: fieldsView}); } - def2 = def2.then(function (fieldsViews) { + viewsLoaded = viewsLoaded.then(function (fieldsViews) { var formView = new QuickCreateFormView(fieldsViews.form, { context: self.context, modelName: self.model, @@ -78,7 +78,7 @@ var RecordQuickCreate = Widget.extend({ return self.controller.appendTo(document.createDocumentFragment()); }); }); - return $.when(def1, def2); + return Promise.all([superWillStart, viewsLoaded]); }, /** * @override @@ -113,7 +113,7 @@ var RecordQuickCreate = Widget.extend({ * have been made yet * * @private - * @returns {Deferred} + * @returns {Promise} */ cancel: function () { var self = this; @@ -129,7 +129,6 @@ var RecordQuickCreate = Widget.extend({ //-------------------------------------------------------------------------- /** - * @override * @private * @param {Object} [options] * @param {boolean} [options.openRecord] set to true to directly open the @@ -139,7 +138,7 @@ var RecordQuickCreate = Widget.extend({ var self = this; if (this._disabled) { // don't do anything if we are already creating a record - return $.Deferred(); + return; } // disable the widget to prevent the user from creating multiple records // with the current values ; if the create works, the widget will be @@ -157,13 +156,13 @@ var RecordQuickCreate = Widget.extend({ } else { self._enableQuickCreate(); } - }).fail(this._enableQuickCreate.bind(this)); + }).guardedCatch(this._enableQuickCreate.bind(this)); }, /** * Notifies the environment that the quick creation must be cancelled * * @private - * @returns {Deferred} + * @returns {Promise} */ _cancel: function () { this.trigger_up('cancel_quick_create'); diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer.js b/addons/web/static/src/js/views/kanban/kanban_renderer.js index 252b4587af6..eda15f706d3 100644 --- a/addons/web/static/src/js/views/kanban/kanban_renderer.js +++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js @@ -148,7 +148,7 @@ var KanbanRenderer = BasicRenderer.extend({ /** * Displays the quick create record in the first column. * - * @returns {Deferred} + * @returns {Promise} */ addQuickCreate: function () { return this.widgets[0].addQuickCreate(); @@ -187,7 +187,7 @@ var KanbanRenderer = BasicRenderer.extend({ * @param {boolean} [options.openQuickCreate] if true, directly opens the * QuickCreate widget in the updated column * - * @returns {Deferred} + * @returns {Promise} */ updateColumn: function (localID, columnState, options) { var self = this; @@ -204,7 +204,7 @@ var KanbanRenderer = BasicRenderer.extend({ if (options && options.openQuickCreate) { def = newColumn.addQuickCreate(); } - return $.when(def).then(function () { + return Promise.resolve(def).then(function () { newColumn.$el.insertAfter(column.$el); self._toggleNoContentHelper(); // When a record has been quick created, the new column directly @@ -221,7 +221,7 @@ var KanbanRenderer = BasicRenderer.extend({ * Updates a given record with its new state. * * @param {Object} recordState - * @returns {Deferred} + * @returns {Promise} */ updateRecord: function (recordState) { var isGrouped = !!this.state.groupedBy.length; @@ -242,7 +242,7 @@ var KanbanRenderer = BasicRenderer.extend({ if (record) { return record.update(recordState); } - return $.when(); + return Promise.resolve(); }, /** * @override @@ -295,10 +295,13 @@ var KanbanRenderer = BasicRenderer.extend({ * @param {integer} nbDivs the number of divs to append */ _renderGhostDivs: function (fragment, nbDivs) { + var ghostDefs = []; for (var $ghost, i = 0; i < nbDivs; i++) { $ghost = $('<div>').addClass('o_kanban_record o_kanban_ghost'); - $ghost.appendTo(fragment); + var def = $ghost.appendTo(fragment); + ghostDefs.push(def); } + return Promise.all(ghostDefs); }, /** * Renders an grouped kanban view in a fragment. @@ -321,10 +324,7 @@ var KanbanRenderer = BasicRenderer.extend({ def = column.appendTo(fragment); self.widgets.push(column); } - if (def.state() === 'pending') { - self.defs.push(def); - } - + self.defs.push(def); }); // remove previous sorting @@ -359,13 +359,13 @@ var KanbanRenderer = BasicRenderer.extend({ this.quickCreate = new ColumnQuickCreate(this, { examples: this.examples, }); - this.quickCreate.appendTo(fragment).then(function () { + this.defs.push(this.quickCreate.appendTo(fragment).then(function () { // Open it directly if there is no column yet if (!self.state.data.length) { self.quickCreate.toggleFold(); self._renderExampleBackground(fragment); } - }); + })); } } }, @@ -382,13 +382,14 @@ var KanbanRenderer = BasicRenderer.extend({ var kanbanRecord = new KanbanRecord(self, record, self.recordOptions); self.widgets.push(kanbanRecord); var def = kanbanRecord.appendTo(fragment); - if (def.state() === 'pending') { - self.defs.push(def); - } + self.defs.push(def); }); // append ghost divs to ensure that all kanban records are left aligned - this._renderGhostDivs(fragment, 6); + var prom = Promise.all(self.defs).then(function () { + return self._renderGhostDivs(fragment, 6); + }); + this.defs.push(prom); }, /** * @override @@ -398,28 +399,31 @@ var KanbanRenderer = BasicRenderer.extend({ var self = this; var oldWidgets = this.widgets; this.widgets = []; - this.$el.empty(); + // render the kanban view var isGrouped = !!this.state.groupedBy.length; - this.$el.toggleClass('o_kanban_grouped', isGrouped); - this.$el.toggleClass('o_kanban_ungrouped', !isGrouped); var fragment = document.createDocumentFragment(); - // render the kanban view - this.defs = []; + var defs = []; + this.defs = defs; if (isGrouped) { this._renderGrouped(fragment); } else { this._renderUngrouped(fragment); } - this.$el.append(fragment); - this._toggleNoContentHelper(); - var defs = this.defs; + delete this.defs; + return this._super.apply(this, arguments).then(function () { - _.invoke(oldWidgets, 'destroy'); - if (self._isInDom) { - _.invoke(self.widgets, 'on_attach_callback'); - } - return $.when.apply(null, defs); + return Promise.all(defs).then(function () { + self.$el.empty(); + self.$el.toggleClass('o_kanban_grouped', isGrouped); + self.$el.toggleClass('o_kanban_ungrouped', !isGrouped); + self.$el.append(fragment); + self._toggleNoContentHelper(); + _.invoke(oldWidgets, 'destroy'); + if (self._isInDom) { + _.invoke(self.widgets, 'on_attach_callback'); + } + }); }); }, /** diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js b/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js index a9576e9836f..eae16968833 100644 --- a/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js +++ b/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js @@ -75,7 +75,7 @@ KanbanRenderer.include({ /** * Displays the quick create record in the active column * - * @returns {Deferred} + * @returns {Promise} */ addQuickCreate: function () { return this.widgets[this.activeColumnIndex].addQuickCreate(); @@ -260,26 +260,26 @@ KanbanRenderer.include({ * @private * @param {integer} moveToIndex index of the column to move to * @param {boolean} [animate=false] set to true to animate - * @returns {Deferred} resolved when the new current group has been loaded + * @returns {Promise} resolved when the new current group has been loaded * and displayed */ _moveToGroup: function (moveToIndex, animate) { var self = this; if (moveToIndex < 0 || moveToIndex >= this.widgets.length) { this._layoutUpdate(animate); - return $.when(); + return Promise.resolve(); } - var def = $.Deferred(); this.activeColumnIndex = moveToIndex; var column = this.widgets[this.activeColumnIndex]; - this.trigger_up('kanban_load_records', { - columnID: column.db_id, - onSuccess: function () { - self._layoutUpdate(animate); - def.resolve(); - }, + return new Promise(function (resolve) { + self.trigger_up('kanban_load_records', { + columnID: column.db_id, + onSuccess: function () { + self._layoutUpdate(animate); + resolve(); + }, + }); }); - return def; }, /** @@ -287,22 +287,23 @@ KanbanRenderer.include({ * @private */ _renderGrouped: function (fragment) { - var result = this._super.apply(this, arguments); - var data = []; - _.each(this.state.data, function (group) { - if (!group.value) { - group = _.extend({}, group, {value: _t('Undefined')}); - data.unshift(group); - } - else { - data.push(group); - } - }); + var self = this; + this._super.apply(this, arguments); + this.defs.push(Promise.all(this.defs).then(function () { + var data = []; + _.each(self.state.data, function (group) { + if (!group.value) { + group = _.extend({}, group, {value: _t('Undefined')}); + data.unshift(group); + } else { + data.push(group); + } + }); - $(qweb.render('KanbanView.MobileTabs', { - data: data, - })).prependTo(fragment); - return result; + $(qweb.render('KanbanView.MobileTabs', { + data: data, + })).prependTo(fragment); + })); }, /** diff --git a/addons/web/static/src/js/views/kanban/kanban_view.js b/addons/web/static/src/js/views/kanban/kanban_view.js index 37f7fdd42ef..49ba9b74034 100644 --- a/addons/web/static/src/js/views/kanban/kanban_view.js +++ b/addons/web/static/src/js/views/kanban/kanban_view.js @@ -139,7 +139,7 @@ var KanbanView = BasicView.extend({ /** * @private * @param {Widget} parent - * @returns {$.Promise} resolved when the searchPanel is ready + * @returns {Promise} resolved when the searchPanel is ready */ _createSearchPanel: function (parent) { var self = this; diff --git a/addons/web/static/src/js/views/kanban/quick_create_form_view.js b/addons/web/static/src/js/views/kanban/quick_create_form_view.js index af769d62095..8725c3104de 100644 --- a/addons/web/static/src/js/views/kanban/quick_create_form_view.js +++ b/addons/web/static/src/js/views/kanban/quick_create_form_view.js @@ -71,7 +71,7 @@ var QuickCreateFormController = FormController.extend({ */ commitChanges: function () { var mutexDef = this.mutex.getUnlockedDef(); - return $.when(mutexDef, this.renderer.commitChanges(this.handle)); + return Promise.all([mutexDef, this.renderer.commitChanges(this.handle)]); }, /** * @returns {Object} the changes done on the current record diff --git a/addons/web/static/src/js/views/kanban/search_panel.js b/addons/web/static/src/js/views/kanban/search_panel.js index b906d02b89a..d06eacbdb07 100644 --- a/addons/web/static/src/js/views/kanban/search_panel.js +++ b/addons/web/static/src/js/views/kanban/search_panel.js @@ -55,7 +55,7 @@ var SearchPanel = Widget.extend({ var loadProm = this._fetchCategories().then(function () { return self._fetchFilters().then(self._applyDefaultFilterValues.bind(self)); }); - return $.when(loadProm, this._super.apply(this, arguments)); + return Promise.all([loadProm, this._super.apply(this, arguments)]); }, /** * @override @@ -82,18 +82,18 @@ var SearchPanel = Widget.extend({ * * @param {Object} params * @param {Array[]} params.searchDomain domain coming from controlPanel - * @returns {$.Promise} + * @returns {Promise} */ update: function (params) { var currentSearchDomainStr = JSON.stringify(this.searchDomain); var newSearchDomainStr = JSON.stringify(params.searchDomain); - var def; + var filtersProm; if (this.needReload || (currentSearchDomainStr !== newSearchDomainStr)) { this.needReload = false; this.searchDomain = params.searchDomain; - def = this._fetchFilters(); + filtersProm = this._fetchFilters(); } - return $.when(def).then(this._render.bind(this)); + return Promise.resolve(filtersProm).then(this._render.bind(this)); }, //-------------------------------------------------------------------------- @@ -231,21 +231,21 @@ var SearchPanel = Widget.extend({ * Fetch values for each category. This is done only once, at startup. * * @private - * @returns {$.Promise} resolved when all categories have been fetched + * @returns {Promise} resolved when all categories have been fetched */ _fetchCategories: function () { var self = this; - var defs = Object.keys(this.categories).map(function (categoryId) { + var proms = Object.keys(this.categories).map(function (categoryId) { var category = self.categories[categoryId]; var field = self.fields[category.fieldName]; - var def; + var categoriesProm; if (field.type === 'selection') { var values = field.selection.map(function (value) { return {id: value[0], display_name: value[1]}; }); - def = $.when(values); + categoriesProm = Promise.resolve(values); } else { - def = self._rpc({ + categoriesProm = self._rpc({ method: 'search_panel_select_range', model: self.model, args: [category.fieldName], @@ -254,18 +254,18 @@ var SearchPanel = Widget.extend({ return result.values; }); } - return def.then(function (values) { + return categoriesProm.then(function (values) { self._createCategoryTree(categoryId, values); }); }); - return $.when.apply($, defs); + return Promise.all(proms); }, /** * Fetch values for each filter. This is done at startup, and at each reload * (when the controlPanel or searchPanel domain changes). * * @private - * @returns {$.Promise} resolved when all filters have been fetched + * @returns {Promise} resolved when all filters have been fetched */ _fetchFilters: function () { var self = this; @@ -276,7 +276,7 @@ var SearchPanel = Widget.extend({ }); var categoryDomain = this._getCategoryDomain(); var filterDomain = this._getFilterDomain(); - var defs = Object.keys(this.filters).map(function (filterId) { + var proms = Object.keys(this.filters).map(function (filterId) { var filter = self.filters[filterId]; return self._rpc({ method: 'search_panel_select_multi_range', @@ -294,7 +294,7 @@ var SearchPanel = Widget.extend({ self._createFilterTree(filterId, values); }); }); - return $.when.apply($, defs); + return Promise.all(proms); }, /** * Compute and return the domain based on the current active categories. diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js index dfa7d272a49..055cd8177e3 100644 --- a/addons/web/static/src/js/views/list/list_controller.js +++ b/addons/web/static/src/js/views/list/list_controller.js @@ -65,7 +65,7 @@ var ListController = BasicController.extend({ * this method should be private, most of the code in the sidebar should be * moved to the controller, and we should not use the getParent method... * - * @returns {Deferred<array[]>} a deferred that resolve to the active domain + * @returns {Promise<array[]>} a promise that resolve to the active domain */ getActiveDomain: function () { // TODO: this method should be synchronous... @@ -73,9 +73,9 @@ var ListController = BasicController.extend({ if (this.$('thead .o_list_record_selector input').prop('checked')) { var searchQuery = this._controlPanel ? this._controlPanel.getSearchQuery() : {}; var record = self.model.get(self.handle, {raw: true}); - return $.when(record.getDomain().concat(searchQuery.domain || [])); + return Promise.all(record.getDomain().concat(searchQuery.domain || [])); } else { - return $.Deferred().resolve(); + return Promise.resolve(); } }, /* @@ -143,6 +143,7 @@ var ListController = BasicController.extend({ * main buttons) * * @param {jQuery Node} $node + * @returns {Promise} */ renderSidebar: function ($node) { var self = this; @@ -180,10 +181,11 @@ var ListController = BasicController.extend({ }, actions: _.extend(this.toolbarActions, {other: other}), }); - this.sidebar.appendTo($node); - - this._toggleSidebar(); + return this.sidebar.appendTo($node).then(function() { + self._toggleSidebar(); + }); } + return Promise.resolve(); }, /** * Overrides to update the list of selected records @@ -243,10 +245,11 @@ var ListController = BasicController.extend({ }); }).then(function (recordID) { var state = self.model.get(self.handle); - self.renderer.updateState(state, {}); - self.renderer.editRecord(recordID); - self._updatePager(); - }).always(this._enableButtons.bind(this)); + self.renderer.updateState(state, {}) + .then(function () { + self.renderer.editRecord(recordID); + }).then(self._updatePager.bind(self)); + }).then(this._enableButtons.bind(this)).guardedCatch(this._enableButtons.bind(this)); }, /** * Archive the current selection @@ -254,11 +257,11 @@ var ListController = BasicController.extend({ * @private * @param {string[]} ids * @param {boolean} archive - * @returns {Deferred} + * @returns {Promise} */ _archive: function (ids, archive) { if (ids.length === 0) { - return $.when(); + return Promise.resolve(); } return this.model .toggleActive(ids, !archive, this.handle) @@ -298,7 +301,7 @@ var ListController = BasicController.extend({ * @override * @param {string} id a basicmodel valid resource handle. It is supposed to * be a record from the list view. - * @returns {Deferred} + * @returns {Promise} */ _confirmSave: function (id) { var state = this.model.get(this.handle); @@ -313,13 +316,13 @@ var ListController = BasicController.extend({ * @override * @private * @param {string} [recordID] - default to main recordID - * @returns {Deferred} + * @returns {Promise} */ _discardChanges: function (recordID) { if ((recordID || this.handle) === this.handle) { recordID = this.renderer.getEditableRecordID(); if (recordID === null) { - return $.when(); + return Promise.resolve(); } } var self = this; @@ -343,7 +346,7 @@ var ListController = BasicController.extend({ * @private * @param {string} mode * @param {string} [recordID] - default to main recordID - * @returns {Deferred} + * @returns {Promise} */ _setMode: function (mode, recordID) { if ((recordID || this.handle) !== this.handle) { @@ -373,11 +376,13 @@ var ListController = BasicController.extend({ }, /** * @override - * @returns {Deferred} + * @returns {Promise} */ _update: function () { - this._toggleSidebar(); - return this._super.apply(this, arguments); + var self = this; + return this._super.apply(this, arguments).then(function() { + self._toggleSidebar(); + }); }, /** * This helper simply makes sure that the control panel buttons matches the @@ -471,7 +476,7 @@ var ListController = BasicController.extend({ var record = self.model.get(self.handle); var editedRecord = record.data[ev.data.index]; self._setMode('edit', editedRecord.id) - .done(ev.data.onSuccess); + .then(ev.data.onSuccess); }, }); }, @@ -523,8 +528,8 @@ var ListController = BasicController.extend({ _onSaveLine: function (ev) { var recordID = ev.data.recordID; this.saveRecord(recordID) - .done(ev.data.onSuccess) - .fail(ev.data.onFailure); + .then(ev.data.onSuccess) + .guardedCatch(ev.data.onFailure); }, /** * When the current selection changes (by clicking on the checkboxes on the diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js index ab5e5036dfa..4619cb86e66 100644 --- a/addons/web/static/src/js/views/list/list_editable_renderer.js +++ b/addons/web/static/src/js/views/list/list_editable_renderer.js @@ -93,7 +93,7 @@ ListRenderer.include({ }, /** * @override - * @returns {Deferred} + * @returns {Promise} */ start: function () { // deliberately use the 'editable' attribute instead of '_isEditable' @@ -171,7 +171,7 @@ ListRenderer.include({ * @param {string} id * @param {string[]} fields * @param {OdooEvent} ev - * @returns {Deferred<AbstractField[]>} resolved with the list of widgets + * @returns {Promise<AbstractField[]>} resolved with the list of widgets * that have been reset */ confirmUpdate: function (state, id, fields, ev) { @@ -180,7 +180,7 @@ ListRenderer.include({ // store the cursor position to restore it once potential onchanges have // been applied var currentRowID, currentWidget, focusedElement, selectionRange; - if (self.currentRow !== null) { + if (this.currentRow !== null) { currentRowID = this.state.data[this.currentRow].id; currentWidget = this.allFieldWidgets[currentRowID][this.currentFieldIndex]; if (currentWidget) { @@ -212,10 +212,14 @@ ListRenderer.include({ }); var newRowIndex = _.findIndex(state.data, {id: id}); var $lastRow = $row; - _.each(state.data, function (record, index) { + var defs = []; + self.defs = defs; + state.data.forEach(function (record, index) { if (index === newRowIndex) { return; } + // FIXME: as we are manipulating the DOM directly, it will + // flicker if there is an async widget in the row var $newRow = self._renderRow(record); if (index < newRowIndex) { $newRow.insertBefore($row); @@ -224,20 +228,23 @@ ListRenderer.include({ $lastRow = $newRow; } }); - if (self.currentRow !== null) { - self.currentRow = newRowIndex; - return self._selectCell(newRowIndex, self.currentFieldIndex, {force: true}).then(function () { - // restore the cursor position - currentRowID = self.state.data[newRowIndex].id; - currentWidget = self.allFieldWidgets[currentRowID][self.currentFieldIndex]; - if (currentWidget) { - focusedElement = currentWidget.getFocusableElement().get(0); - if (selectionRange) { - dom.setSelectionRange(focusedElement, selectionRange); - } + delete self.defs; + return Promise.all(defs).then(function () { + if (self.currentRow !== null) { + self.currentRow = newRowIndex; + return self._selectCell(newRowIndex, self.currentFieldIndex, {force: true}); + } + }).then(function () { + // restore the cursor position + currentRowID = self.state.data[newRowIndex].id; + currentWidget = self.allFieldWidgets[currentRowID][self.currentFieldIndex]; + if (currentWidget) { + focusedElement = currentWidget.getFocusableElement().get(0); + if (selectionRange) { + dom.setSelectionRange(focusedElement, selectionRange); } - }); - } + } + }); }); }, /** @@ -294,7 +301,7 @@ ListRenderer.include({ * * @param {string} recordID * @param {string} mode - * @returns {Deferred} + * @returns {Promise} */ setRowMode: function (recordID, mode) { var self = this; @@ -321,7 +328,7 @@ ListRenderer.include({ } if (rowIndex < 0) { - return $.when(); + return Promise.resolve(); } var editMode = (mode === 'edit'); @@ -350,7 +357,7 @@ ListRenderer.include({ options.mode = editMode ? 'edit' : 'readonly'; // Switch each cell to the new mode; note: the '_renderBodyCell' - // function might fill the 'this.defs' variables with multiple deferred + // function might fill the 'this.defs' variables with multiple promise // so we create the array and delete it after the rendering. var defs = []; this.defs = defs; @@ -385,7 +392,7 @@ ListRenderer.include({ $row.toggleClass('o_selected_row', editMode); $row.find('.o_list_record_selector input').prop('disabled', !record.res_id); - return $.when.apply($, defs).then(function () { + return Promise.all(defs).then(function () { // necessary to trigger resize on fieldtexts core.bus.trigger('DOM_updated'); }); @@ -399,27 +406,32 @@ ListRenderer.include({ * prevent subsequent editions. These edits would be lost, because the list * view only saves records when unselecting a row. * - * @returns {Deferred} The deferred resolves if the row was unselected (and + * @returns {Promise} The promise resolves if the row was unselected (and * possibly removed). If may be rejected, when the row is dirty and the * user refuses to discard its changes. */ unselectRow: function () { + var self = this; // Protect against calling this method when no row is selected if (this.currentRow === null) { - return $.when(); + return Promise.resolve(); } var record = this.state.data[this.currentRow]; var recordWidgets = this.allFieldWidgets[record.id]; toggleWidgets(true); - var def = $.Deferred(); - this.trigger_up('save_line', { - recordID: record.id, - onSuccess: def.resolve.bind(def), - onFailure: def.reject.bind(def), + var prom = new Promise(function (resolve, reject) { + self.trigger_up('save_line', { + recordID: record.id, + onSuccess: resolve, + onFailure: reject, + }); }); - return def.fail(toggleWidgets.bind(null, false)); + prom.guardedCatch(function() { + toggleWidgets(false); + }); + return prom; function toggleWidgets(disabled) { _.each(recordWidgets, function (widget) { @@ -511,7 +523,7 @@ ListRenderer.include({ }, /** * @override - * @returns {Deferred} + * @returns {Promise} */ _render: function () { this.currentRow = null; @@ -524,10 +536,9 @@ ListRenderer.include({ * widget. * * @override - * @returns {jQueryElement} */ _renderBody: function () { - var $body = this._super(); + var $body = this._super.apply(this, arguments); if (this.hasHandle) { $body.sortable({ axis: 'y', @@ -568,15 +579,13 @@ ListRenderer.include({ * on the first real column. * * @override - * @returns {jQueryElement} + * @returns {jQueryElement[]} */ _renderRows: function () { - var self = this; var $rows = this._super(); - if (this.addCreateLine) { var $tr = $('<tr>'); - var colspan = self._getNumberOfCols(); + var colspan = this._getNumberOfCols(); if (this.handleField) { colspan = colspan - 1; @@ -589,7 +598,7 @@ ListRenderer.include({ $tr.append($td); $rows.push($tr); - _.each(self.creates, function (create, index) { + _.each(this.creates, function (create, index) { var $a = $('<a href="#" role="button">') .attr('data-context', create.context) .text(create.string); @@ -604,7 +613,7 @@ ListRenderer.include({ /** * @override * @private - * @returns {Deferred} this deferred is resolved immediately + * @returns {Promise} this promise is resolved immediately */ _renderView: function () { var self = this; @@ -692,13 +701,13 @@ ListRenderer.include({ * @param {boolean} [options.force=false] if true, force selecting the cell * even if seems to be already the selected one (useful after a re- * rendering, to reset the focus on the correct field) - * @return {Deferred} fails if no cell could be selected + * @return {Promise} fails if no cell could be selected */ _selectCell: function (rowIndex, fieldIndex, options) { options = options || {}; // Do nothing if the user tries to select current cell if (!options.force && rowIndex === this.currentRow && fieldIndex === this.currentFieldIndex) { - return $.when(); + return Promise.resolve(); } var wrap = options.wrap === undefined ? true : options.wrap; @@ -707,7 +716,7 @@ ListRenderer.include({ return this._selectRow(rowIndex).then(function () { var record = self.state.data[rowIndex]; if (fieldIndex >= (self.allFieldWidgets[record.id] || []).length) { - return $.Deferred().reject(); + return Promise.reject(); } // _activateFieldWidget might trigger an onchange, // which requires currentFieldIndex to be set @@ -721,7 +730,7 @@ ListRenderer.include({ }); if (fieldIndex < 0) { self.currentFieldIndex = oldFieldIndex; - return $.Deferred().reject(); + return Promise.reject(); } self.currentFieldIndex = fieldIndex; }); @@ -730,12 +739,12 @@ ListRenderer.include({ * Activates the row at the given row index. * * @param {integer} rowIndex - * @returns {Deferred} + * @returns {Promise} */ _selectRow: function (rowIndex) { // Do nothing if already selected if (rowIndex === this.currentRow) { - return $.when(); + return Promise.resolve(); } // To select a row, the currently selected one must be unselected first @@ -745,15 +754,15 @@ ListRenderer.include({ // The row to selected doesn't exist anymore (probably because // an onchange triggered when unselecting the previous one // removes rows) - return $.Deferred().reject(); + return Promise.reject(); } // Notify the controller we want to make a record editable - var def = $.Deferred(); - self.trigger_up('edit_line', { - index: rowIndex, - onSuccess: def.resolve.bind(def), + return new Promise(function (resolve) { + self.trigger_up('edit_line', { + index: rowIndex, + onSuccess: resolve, + }); }); - return def; }); }, @@ -778,7 +787,7 @@ ListRenderer.include({ // but we do want to unselect current row var self = this; this.unselectRow().then(function () { - self.trigger_up('add_record', {context: ev.currentTarget.dataset.context && [ev.currentTarget.dataset.context]}); // TODO write a test, the deferred was not considered + self.trigger_up('add_record', {context: ev.currentTarget.dataset.context && [ev.currentTarget.dataset.context]}); // TODO write a test, the promise was not considered }); }, /** @@ -864,12 +873,13 @@ ListRenderer.include({ * @param {OdooEvent} ev */ _onNavigationMove: function (ev) { + var self = this; ev.stopPropagation(); // stop the event, the action is done by this renderer switch (ev.data.direction) { case 'previous': if (this.currentFieldIndex > 0) { this._selectCell(this.currentRow, this.currentFieldIndex - 1, {wrap: false}) - .fail(this._moveToPreviousLine.bind(this)); + .guardedCatch(this._moveToPreviousLine.bind(this)); } else { this._moveToPreviousLine(); } @@ -882,7 +892,7 @@ ListRenderer.include({ if (column.attrs.name === lastWidget.name) { if (this.currentRow + 1 < this.state.data.length) { this._selectCell(this.currentRow+1, 0, {wrap:false}) - .fail(this._moveToNextLine.bind(this)); + .guardedCatch(this._moveToNextLine.bind(this)); } else { var currentRowData = this.state.data[this.currentRow]; if (currentRowData.isDirty(currentRowData.id)) { @@ -895,7 +905,7 @@ ListRenderer.include({ } else { if (this.currentFieldIndex + 1 < this.columns.length) { this._selectCell(this.currentRow, this.currentFieldIndex + 1, {wrap: false}) - .fail(this._moveToNextLine.bind(this)); + .guardedCatch(this._moveToNextLine.bind(this)); } else { this._moveToNextLine(); } @@ -911,8 +921,10 @@ ListRenderer.include({ ev.data.originalEvent.stopPropagation(); this.trigger_up('discard_changes', { recordID: ev.target.dataPointID, + onSuccess: function () { + self.$('.o_field_x2many_list_row_add a:first').focus(); + } }); - this.$('.o_field_x2many_list_row_add a:first').focus(); break; } }, diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js index ee081aa3c5b..760ab9ed7a8 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -250,9 +250,10 @@ var ListRenderer = BasicRenderer.extend({ * @returns {jQueryElement} a jquery element <tbody> */ _renderBody: function () { + var self = this; var $rows = this._renderRows(); while ($rows.length < 4) { - $rows.push(this._renderEmptyRow()); + $rows.push(self._renderEmptyRow()); } return $('<tbody>').append($rows); }, @@ -306,7 +307,6 @@ var ListRenderer = BasicRenderer.extend({ } if (node.attrs.widget || (options && options.renderWidgets)) { var $el = this._renderFieldWidget(node, record, _.pick(options, 'mode')); - this._handleAttributes($el, node); return $td.append($el); } var name = node.attrs.name; @@ -395,7 +395,7 @@ var ListRenderer = BasicRenderer.extend({ * * @private * @param {Object} group - * @returns {JQueryElement} the pager's $el + * @returns {jQueryElement} the pager's $el */ _renderGroupPager: function (group) { var pager = new Pager(this, group.count, group.offset + 1, group.limit); @@ -416,9 +416,14 @@ var ListRenderer = BasicRenderer.extend({ // register the pager so that it can be destroyed on next rendering this.pagers.push(pager); - var fragment = document.createDocumentFragment(); - pager.appendTo(fragment); // starts the pager - return pager.$el; + var pagerProm = pager._widgetRenderAndInsert(function () {}); // start the pager + this.defs.push(pagerProm); + var $el = $('<div>'); + pagerProm.then(function () { + $el.replaceWith(pager.$el); + }); + + return $el; }, /** * Render the row that represent a group @@ -514,10 +519,9 @@ var ListRenderer = BasicRenderer.extend({ * with the name of each fields * * @private - * @param {boolean} isGrouped * @returns {jQueryElement} a <thead> element */ - _renderHeader: function (isGrouped) { + _renderHeader: function () { var $tr = $('<tr>') .append(_.map(this.columns, this._renderHeaderCell.bind(this))); if (this.hasSelectors) { @@ -585,11 +589,9 @@ var ListRenderer = BasicRenderer.extend({ */ _renderRow: function (record) { var self = this; - this.defs = []; // TODO maybe wait for those somewhere ? - var $cells = _.map(this.columns, function (node, index) { + var $cells = this.columns.map(function (node, index) { return self._renderBodyCell(record, node, index, { mode: 'readonly' }); }); - delete this.defs; var $tr = $('<tr/>', { class: 'o_data_row' }) .data('id', record.id) @@ -608,7 +610,7 @@ var ListRenderer = BasicRenderer.extend({ * @returns {jQueryElement[]} a list of <tr> */ _renderRows: function () { - return _.map(this.state.data, this._renderRow.bind(this)); + return this.state.data.map(this._renderRow.bind(this)); }, /** * A 'selector' is the small checkbox on the left of a record in a list @@ -638,52 +640,60 @@ var ListRenderer = BasicRenderer.extend({ * * @override * @private - * returns {Deferred} this deferred is resolved immediately + * @returns {Promise} resolved when the view has been rendered */ _renderView: function () { var self = this; - this.$el - .removeClass('table-responsive') - .empty(); - - // destroy the previously instantiated pagers, if any - _.invoke(this.pagers, 'destroy'); + var oldPagers = this.pagers; this.pagers = []; - var displayNoContentHelper = !this._hasContent() && !!this.noContentHelp; // display the no content helper if there is no data to display + var displayNoContentHelper = !this._hasContent() && !!this.noContentHelp; if (displayNoContentHelper) { + // destroy the previously instantiated pagers, if any + _.invoke(oldPagers, 'destroy'); + + this.$el.removeClass('table-responsive'); this.$el.html(this._renderNoContentHelper()); - return this._super(); + return this._super.apply(this, arguments); } - var $table = $('<table>').addClass('o_list_view table table-sm table-hover table-striped'); - this.$el.addClass('table-responsive') - .append($table); + var orderedBy = this.state.orderedBy; + this.hasHandle = orderedBy.length === 0 || orderedBy[0].name === this.handleField; this._computeAggregates(); + + var $table = $('<table>').addClass('o_list_view table table-sm table-hover table-striped'); $table.toggleClass('o_list_view_grouped', this.isGrouped); $table.toggleClass('o_list_view_ungrouped', !this.isGrouped); - this.hasHandle = this.state.orderedBy.length === 0 || - this.state.orderedBy[0].name === this.handleField; + var defs = []; + this.defs = defs; if (this.isGrouped) { - $table - .append(this._renderHeader(true)) - .append(this._renderGroups(this.state.data)) - .append(this._renderFooter()); + $table.append(this._renderHeader()); + $table.append(this._renderGroups(this.state.data)); + $table.append(this._renderFooter()); + } else { - $table - .append(this._renderHeader()) - .append(this._renderBody()) - .append(this._renderFooter()); - } - if (this.selection.length) { - var $checked_rows = this.$('tr').filter(function (index, el) { - return _.contains(self.selection, $(el).data('id')); - }); - $checked_rows.find('.o_list_record_selector input').prop('checked', true); + $table.append(this._renderHeader()); + $table.append(this._renderBody()); + $table.append(this._renderFooter()); } - return this._super(); + delete this.defs; + + var prom = Promise.all(defs).then(function () { + // destroy the previously instantiated pagers, if any + _.invoke(oldPagers, 'destroy'); + + self.$el.addClass('table-responsive').html($table); + + if (self.selection.length) { + var $checked_rows = self.$('tr').filter(function (index, el) { + return _.contains(self.selection, $(el).data('id')); + }); + $checked_rows.find('.o_list_record_selector input').prop('checked', true); + } + }); + return Promise.all([this._super.apply(this, arguments), prom]); }, /** * Each line can be decorated according to a few simple rules. The arch diff --git a/addons/web/static/src/js/views/pivot/pivot_model.js b/addons/web/static/src/js/views/pivot/pivot_model.js index 3f195c1ea24..a4a2bae8fb0 100644 --- a/addons/web/static/src/js/views/pivot/pivot_model.js +++ b/addons/web/static/src/js/views/pivot/pivot_model.js @@ -45,10 +45,10 @@ var PivotModel = AbstractModel.extend({ /** * Close a header. This method is actually synchronous, but returns a - * deferred. + * promise. * * @param {any} headerID - * @returns {Deferred} + * @returns {Promise} */ closeHeader: function (headerID) { var header = this.data.headers[headerID]; @@ -58,7 +58,7 @@ var PivotModel = AbstractModel.extend({ header.root.groupbys.splice(newGroupbyLength); }, /** - * @returns {Deferred} + * @returns {Promise} */ expandAll: function () { return this._loadData(); @@ -118,8 +118,8 @@ var PivotModel = AbstractModel.extend({ }); })); } - return $.when.apply(null, defs).then(function () { - var results = Array.prototype.slice.call(arguments); + return Promise.all(defs).then(function () { + var results = Array.prototype.slice.call(arguments[0]); var data = []; var comparisonData = []; _.each(results, function (result) { @@ -358,7 +358,7 @@ var PivotModel = AbstractModel.extend({ * @param {string[]} params.compare * @param {Object} params.fields * @param {string} params.default_order - * @returns {Deferred} + * @returns {Promise} */ load: function (params) { var self = this; @@ -394,7 +394,7 @@ var PivotModel = AbstractModel.extend({ * @override * @param {any} handle this parameter is ignored * @param {Object} params - * @returns {Deferred} + * @returns {Promise} */ reload: function (handle, params) { var self = this; @@ -507,14 +507,14 @@ var PivotModel = AbstractModel.extend({ * Toggle the active state for a given measure, then reload the data. * * @param {string} field - * @returns {Deferred} + * @returns {Promise} */ toggleMeasure: function (field) { if (_.contains(this.data.measures, field)) { this.data.measures = _.without(this.data.measures, field); // in this case, we already have all data in memory, no need to // actually reload a lesser amount of information - return $.when(); + return Promise.resolve(); } else { this.data.measures.push(field); } @@ -705,7 +705,7 @@ var PivotModel = AbstractModel.extend({ return result; }, /** - * @returns {Deferred} + * @returns {Promise} */ _loadData: function () { var self = this; @@ -755,8 +755,7 @@ var PivotModel = AbstractModel.extend({ })); } - return this._loadDataDropPrevious.add($.when.apply(null, defs)).then(function () { - var results = Array.prototype.slice.call(arguments); + return this._loadDataDropPrevious.add(Promise.all(defs)).then(function (results) { var data = []; var comparisonData = []; _.each(results, function (result) { diff --git a/addons/web/static/src/js/views/pivot/pivot_renderer.js b/addons/web/static/src/js/views/pivot/pivot_renderer.js index 1a3c451f064..101f3734392 100644 --- a/addons/web/static/src/js/views/pivot/pivot_renderer.js +++ b/addons/web/static/src/js/views/pivot/pivot_renderer.js @@ -72,7 +72,7 @@ var PivotRenderer = AbstractRenderer.extend({ /** * @override * @private - * @returns {Deferred} + * @returns {Promise} */ _render: function () { if (!this._hasContent()) { diff --git a/addons/web/static/src/js/views/standalone_field_manager_mixin.js b/addons/web/static/src/js/views/standalone_field_manager_mixin.js index da3cc19350d..501ecf7c012 100644 --- a/addons/web/static/src/js/views/standalone_field_manager_mixin.js +++ b/addons/web/static/src/js/views/standalone_field_manager_mixin.js @@ -38,7 +38,7 @@ var StandaloneFieldManagerMixin = _.extend({}, FieldManagerMixin, { * @param {string} id basicModel Id for the changed record * @param {string[]} fields the fields (names) that have been changed * @param {OdooEvent} event the event that triggered the change - * @returns {Deferred} + * @returns {Promise} */ _confirmChange: function (id, fields, event) { var result = FieldManagerMixin._confirmChange.apply(this, arguments); diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js index befe4247c00..75be3a3d07e 100644 --- a/addons/web/static/src/js/views/view_dialogs.js +++ b/addons/web/static/src/js/views/view_dialogs.js @@ -128,7 +128,7 @@ var FormViewDialog = ViewDialog.extend({ text: (multi_select ? _t("Save & Close") : _t("Save")), classes: "btn-primary", click: function () { - this._save().then(self.close.bind(self)); + self._save().then(self.close.bind(self)); } }); @@ -137,7 +137,7 @@ var FormViewDialog = ViewDialog.extend({ text: _t("Save & New"), classes: "btn-primary", click: function () { - this._save() + self._save() .then(self.form_view.createRecord.bind(self.form_view, self.parentID)) .then(function () { if (!self.deletable) { @@ -183,7 +183,7 @@ var FormViewDialog = ViewDialog.extend({ var FormView = view_registry.get('form'); var fields_view_def; if (this.options.fields_view) { - fields_view_def = $.when(this.options.fields_view); + fields_view_def = Promise.resolve(this.options.fields_view); } else { fields_view_def = this.loadFieldView(this.res_model, this.context, this.options.view_id, 'form'); } @@ -213,9 +213,9 @@ var FormViewDialog = ViewDialog.extend({ if (self.recordID && self.shouldSaveLocally) { self.model.save(self.recordID, {savePoint: true}); } - self.form_view.appendTo(fragment) + return self.form_view.appendTo(fragment) .then(function () { - self.opened().always(function () { + self.opened().then(function () { var $buttons = $('<div>'); self.form_view.renderButtons($buttons); if ($buttons.children().length) { @@ -226,7 +226,7 @@ var FormViewDialog = ViewDialog.extend({ in_DOM: true, }); }); - _super(); + return _super(); }); }); @@ -255,7 +255,7 @@ var FormViewDialog = ViewDialog.extend({ /** * @private - * @returns {Deferred} + * @returns {Promise} */ _save: function () { var self = this; @@ -332,7 +332,7 @@ var SelectCreateDialog = ViewDialog.extend({ } var self = this; var _super = this._super.bind(this); - this.loadViews(this.res_model, this.context, [[false, 'list'], [false, 'search']], {}) + return this.loadViews(this.res_model, this.context, [[false, 'list'], [false, 'search']], {}) .then(this.setup.bind(this)) .then(function (fragment) { self.opened().then(function () { @@ -342,9 +342,8 @@ var SelectCreateDialog = ViewDialog.extend({ }); self.set_buttons(self.__buttons); }); - _super(); + return _super(); }); - return this; }, setup: function (fieldsViews) { diff --git a/addons/web/static/src/js/views/view_utils.js b/addons/web/static/src/js/views/view_utils.js index 94485c1401a..ea2270ce466 100644 --- a/addons/web/static/src/js/views/view_utils.js +++ b/addons/web/static/src/js/views/view_utils.js @@ -63,7 +63,7 @@ var viewUtils = { */ renderButtonFromNode: function (node, options) { var btnOptions = { - attrs: _.omit(node.attrs, 'icon', 'string', 'type', 'attrs', 'modifiers', 'options'), + attrs: _.omit(node.attrs, 'icon', 'string', 'type', 'attrs', 'modifiers', 'options', 'effect'), icon: node.attrs.icon, }; if (options && options.extraClass) { diff --git a/addons/web/static/src/js/widgets/attach_document.js b/addons/web/static/src/js/widgets/attach_document.js index 13496f3aea7..3175875c151 100644 --- a/addons/web/static/src/js/widgets/attach_document.js +++ b/addons/web/static/src/js/widgets/attach_document.js @@ -108,26 +108,27 @@ var AttachDocument = Widget.extend({ * @private */ _onFileLoaded: function () { - var self = this, - def = $.Deferred(); - - if (self.node.attrs.action) { - // the first argument isn't a file but the jQuery.Event - var files = Array.prototype.slice.call(arguments, 1); - self._rpc({ - model: self.res_model, - method: self.node.attrs.action, - args: [self.res_id], - kwargs: { - 'attachment_ids': _.map(files, function (file) {return file.id;}), - } - }).then( function () { - def.resolve(); - }); - } else { - def.resolve(); - } - return $.when(def).then(function () { + var self = this; + return new Promise(function (resolve) { + if (self.node.attrs.action) { + // the first argument isn't a file but the jQuery.Event + var files = Array.prototype.slice.call(arguments, 1); + self._rpc({ + model: self.res_model, + method: self.node.attrs.action, + args: [self.res_id], + kwargs: { + attachment_ids: _.map(files, function (file) { + return file.id; + }), + } + }).then(function () { + resolve(); + }); + } else { + resolve(); + } + }).then(function () { self.trigger_up('reload'); framework.unblockUI(); }); diff --git a/addons/web/static/src/js/widgets/change_password.js b/addons/web/static/src/js/widgets/change_password.js index c05fa30408b..e75d8dfda38 100644 --- a/addons/web/static/src/js/widgets/change_password.js +++ b/addons/web/static/src/js/widgets/change_password.js @@ -20,7 +20,7 @@ var ChangePassword = AbstractAction.extend({ * @fixme: weird interaction with the parent for the $buttons handling * * @override - * @returns {Deferred} + * @returns {Promise} */ start: function () { var self = this; @@ -37,7 +37,7 @@ var ChangePassword = AbstractAction.extend({ fields: $('form[name=change_password_form]').serializeArray() } }) - .done(function (result) { + .then(function (result) { if (result.error) { self._display_error(result); } else { diff --git a/addons/web/static/src/js/widgets/data_export.js b/addons/web/static/src/js/widgets/data_export.js index e24818f7336..a913fcd0ea2 100644 --- a/addons/web/static/src/js/widgets/data_export.js +++ b/addons/web/static/src/js/widgets/data_export.js @@ -56,7 +56,7 @@ var DataExport = Dialog.extend({ */ start: function () { var self = this; - var defs = [this._super.apply(this, arguments)]; + var proms = [this._super.apply(this, arguments)]; // The default for the ".modal_content" element is "max-height: 100%;" // but we want it to always expand to "height: 100%;" for this modal. @@ -67,10 +67,10 @@ var DataExport = Dialog.extend({ this.$fieldsList = this.$('.o_fields_list'); this.$importCompatRadios = this.$('.o_import_compat input'); - defs.push(this._rpc({route: '/web/export/formats'}).then(doSetupExportFormats)); - defs.push(this._onChangeCompatibleInput()); + proms.push(this._rpc({route: '/web/export/formats'}).then(doSetupExportFormats)); + proms.push(this._onChangeCompatibleInput()); - defs.push(this.getParent().getActiveDomain().then(function (domain) { + proms.push(this.getParent().getActiveDomain().then(function (domain) { if (domain === undefined) { self.idsToExport = self.getParent().getSelectedIds(); self.domain = self.record.domain; @@ -80,7 +80,7 @@ var DataExport = Dialog.extend({ } })); - defs.push(this._showExportsList().then(function () { + proms.push(this._showExportsList().then(function () { _.each(self.records, function (record, key) { if (_.contains(self.defaultExportFields, key)) { self._addField(key, record.string); @@ -88,12 +88,12 @@ var DataExport = Dialog.extend({ }); })); - return $.when.apply($, defs); + return Promise.all(proms); - function doSetupExportFormats (formats) { + function doSetupExportFormats(formats) { var $fmts = self.$('.o_export_format'); - _.each(formats, function (format, i) { + _.each(formats, function (format) { var $radio = $('<input/>', {type: 'radio', value: format.tag, name: 'o_export_format_name'}); var $label = $('<span/>', {html: format.label}); @@ -192,16 +192,16 @@ var DataExport = Dialog.extend({ */ _onExpandAction: function (record) { var self = this; - if (!record['children']) { + if (!record.children) { return; } - var model = record['params']['model']; - var prefix = record['params']['prefix']; - var name = record['params']['name']; + var model = record.params.model; + var prefix = record.params.prefix; + var name = record.params.name; var excludeFields = []; - if (record['relation_field']) { - excludeFields.push(record['relation_field']); + if (record.relation_field) { + excludeFields.push(record.relation_field); } if (!record.loaded) { @@ -212,11 +212,11 @@ var DataExport = Dialog.extend({ prefix: prefix, parent_name: name, import_compat: !!this.$importCompatRadios.filter(':checked').val(), - parent_field_type: record['field_type'], - parent_field: record['params']['parent_field'], + parent_field_type: record.field_type, + parent_field: record.params.parent_field, exclude: excludeFields, }, - }).done(function (results) { + }).then(function (results) { record.loaded = true; self._onShowData(results, record.id); }); @@ -294,7 +294,7 @@ var DataExport = Dialog.extend({ var self = this; if (this.$('.o_exported_lists_select').is(':hidden')) { this.$('.o_exported_lists').show(); - return $.when(); + return Promise.resolve(); } return this._rpc({ @@ -355,7 +355,7 @@ var DataExport = Dialog.extend({ model: this.record.model, import_compat: this.isCompatibleMode, }, - }).done(function (records) { + }).then(function (records) { var compatibleFields = _.map(records, function (record) { return record.id; }); self.$fieldsList .find('option') diff --git a/addons/web/static/src/js/widgets/domain_selector.js b/addons/web/static/src/js/widgets/domain_selector.js index 8e20e1ce18d..ed71679699c 100644 --- a/addons/web/static/src/js/widgets/domain_selector.js +++ b/addons/web/static/src/js/widgets/domain_selector.js @@ -188,11 +188,14 @@ var DomainTree = DomainNode.extend({ }, /** * @see DomainNode.start - * @returns {Deferred} + * @returns {Promise} */ start: function () { this._postRender(); - return $.when(this._super.apply(this, arguments), this._renderChildrenTo(this.$childrenContainer)); + return Promise.all([ + this._super.apply(this, arguments), + this._renderChildrenTo(this.$childrenContainer) + ]); }, //-------------------------------------------------------------------------- @@ -369,11 +372,11 @@ var DomainTree = DomainNode.extend({ * * @private * @param {jQuery} $to - the jQuery node to which the children must be added - * @returns {Deferred} + * @returns {Promise} */ _renderChildrenTo: function ($to) { var $div = $("<div/>"); - return $.when.apply($, _.map(this.children, (function (child) { + return Promise.all(_.map(this.children, (function (child) { return child.appendTo($div); }).bind(this))).then((function () { _.each(this.children, function (child) { @@ -485,11 +488,11 @@ var DomainSelector = DomainTree.extend({ * does nothing. * * @param {string} domain - * @returns {Deferred} resolved when the rerendering is finished + * @returns {Promise} resolved when the rerendering is finished */ setDomain: function (domain) { if (domain === Domain.prototype.arrayToString(this.getDomain())) { - return $.when(); + return Promise.resolve(); } var parsedDomain = this._parseDomain(domain); if (parsedDomain) { @@ -542,7 +545,7 @@ var DomainSelector = DomainTree.extend({ * This method is ugly but achieves the right behavior without flickering. * * @param {Array|string} domain - * @returns {Deferred} + * @returns {Promise} */ _redraw: function (domain) { var oldChildren = this.children.slice(); @@ -637,7 +640,7 @@ var DomainLeaf = DomainNode.extend({ * Prepares the information the rendering of the widget will need by * pre-instantiating its internal field selector widget. * - * @returns {Deferred} + * @returns {Promise} */ willStart: function () { var defs = [this._super.apply(this, arguments)]; @@ -702,17 +705,17 @@ var DomainLeaf = DomainNode.extend({ }).bind(this))); } - return $.when.apply($, wDefs); + return Promise.all(wDefs); } }).bind(this))); - return $.when.apply($, defs); + return Promise.all(defs); }, /** * @see DomainNode.start * Appends the prepared field selector and value widget. * - * @returns {Deferred} + * @returns {Promise} */ start: function () { this.fieldSelector.$el.prependTo(this.$("> .o_domain_leaf_info, > .o_domain_leaf_edition")); // place the field selector diff --git a/addons/web/static/src/js/widgets/domain_selector_dialog.js b/addons/web/static/src/js/widgets/domain_selector_dialog.js index 46b54089c5f..f6fe7f0c479 100644 --- a/addons/web/static/src/js/widgets/domain_selector_dialog.js +++ b/addons/web/static/src/js/widgets/domain_selector_dialog.js @@ -45,10 +45,10 @@ return Dialog.extend({ // this restores default modal height (bootstrap) and allows field selector to overflow self.$el.css('overflow', 'visible').closest('.modal-dialog').css('height', 'auto'); }); - return $.when( + return Promise.all([ this._super.apply(this, arguments), this.domainSelector.appendTo(this.$el) - ); + ]); }, }); }); diff --git a/addons/web/static/src/js/widgets/iframe_widget.js b/addons/web/static/src/js/widgets/iframe_widget.js index 77d27fac5b4..8ecbd5f1c7d 100644 --- a/addons/web/static/src/js/widgets/iframe_widget.js +++ b/addons/web/static/src/js/widgets/iframe_widget.js @@ -29,7 +29,7 @@ var IFrameWidget = Widget.extend({ }, /** * @override - * @returns {Deferred} + * @returns {Promise} */ start: function () { this.$el.css({height: '100%', width: '100%', border: 0}); diff --git a/addons/web/static/src/js/widgets/model_field_selector.js b/addons/web/static/src/js/widgets/model_field_selector.js index b8c953a2d0e..08a7ae47c81 100644 --- a/addons/web/static/src/js/widgets/model_field_selector.js +++ b/addons/web/static/src/js/widgets/model_field_selector.js @@ -112,17 +112,17 @@ var ModelFieldSelector = Widget.extend({ }, /** * @see Widget.willStart() - * @returns {Deferred} + * @returns {Promise} */ willStart: function () { - return $.when( + return Promise.all([ this._super.apply(this, arguments), this._prefill() - ); + ]); }, /** * @see Widget.start - * @returns {Deferred} + * @returns {Promise} */ start: function () { this.$value = this.$(".o_field_selector_value"); @@ -162,11 +162,11 @@ var ModelFieldSelector = Widget.extend({ * Saves a new field chain (array) and re-render. * * @param {string[]} chain - the new field chain - * @returns {Deferred} resolved once the re-rendering is finished + * @returns {Promise} resolved once the re-rendering is finished */ setChain: function (chain) { if (_.isEqual(chain, this.chain)) { - return $.when(); + return Promise.resolve(); } this.chain = chain; @@ -290,14 +290,14 @@ var ModelFieldSelector = Widget.extend({ * chain. * * @private - * @returns {Deferred} resolved once the whole field chain has been + * @returns {Promise} resolved once the whole field chain has been * processed */ _prefill: function () { this.pages = []; return this._pushPageData(this.model).then((function () { this._validate(true); - return (this.chain.length ? processChain.call(this, this.chain.slice().reverse()) : $.when()); + return (this.chain.length ? processChain.call(this, this.chain.slice().reverse()) : Promise.resolve()); }).bind(this)); function processChain(chain) { @@ -305,11 +305,11 @@ var ModelFieldSelector = Widget.extend({ if (field && field.relation && chain.length > 0) { // Fetch next chain node if any and possible return this._pushPageData(field.relation).then(processChain.bind(this, chain)); } else if (field && chain.length === 0) { // Last node fetched - return $.when(); + return Promise.resolve(); } else { // Wrong node chain this._validate(false); } - return $.when(); + return Promise.resolve(); } }, /** @@ -318,12 +318,12 @@ var ModelFieldSelector = Widget.extend({ * * @private * @param {string} model - the model name whose fields have to be fetched - * @returns {Deferred} resolved once the fields have been added + * @returns {Promise} resolved once the fields have been added */ _pushPageData: function (model) { var def; if (this.model === model && this.options.fields) { - def = $.when(sortFields(this.options.fields, model, this.options.order)); + def = Promise.resolve(sortFields(this.options.fields, model, this.options.order)); } else { def = this._getModelFieldsFromCache(model, this.options.filters); } diff --git a/addons/web/static/src/js/widgets/name_and_signature.js b/addons/web/static/src/js/widgets/name_and_signature.js index c860b0a4361..7c3f05a7148 100644 --- a/addons/web/static/src/js/widgets/name_and_signature.js +++ b/addons/web/static/src/js/widgets/name_and_signature.js @@ -80,14 +80,12 @@ var NameAndSignature = Widget.extend({ */ willStart: function () { var self = this; - - return $.when( + return Promise.all([ this._super.apply(this, arguments), this._rpc({route: '/web/sign/get_fonts/' + self.defaultFont}).then(function (data) { self.fonts = data; }) - ); - + ]); }, /** * Finds the DOM elements, initializes the signature area, @@ -217,7 +215,7 @@ var NameAndSignature = Widget.extend({ resetSignature: function () { if (!this.$signatureField) { // no action if called before start - return $.Deferred().reject(); + return Promise.reject(); } // recompute size based on the current width this.$signatureField.css({width: 'unset'}); @@ -248,7 +246,7 @@ var NameAndSignature = Widget.extend({ this.focusName(); - return $.Deferred().resolve(); + return Promise.resolve(); }, /** * Changes the signature mode. Toggles the display of the relevant diff --git a/addons/web/static/src/js/widgets/pie_chart.js b/addons/web/static/src/js/widgets/pie_chart.js index 44a08a5a22a..ddeca1fdcec 100644 --- a/addons/web/static/src/js/widgets/pie_chart.js +++ b/addons/web/static/src/js/widgets/pie_chart.js @@ -67,7 +67,7 @@ var PieChart = Widget.extend({ self.controller = controller; return self.controller.appendTo(document.createDocumentFragment()); }); - return $.when(def1, def2); + return Promise.all([def1, def2]); }, /** * @override diff --git a/addons/web/static/src/js/widgets/switch_company_menu.js b/addons/web/static/src/js/widgets/switch_company_menu.js index cc0f8825643..a389ee268cd 100644 --- a/addons/web/static/src/js/widgets/switch_company_menu.js +++ b/addons/web/static/src/js/widgets/switch_company_menu.js @@ -31,12 +31,6 @@ var SwitchCompanyMenu = Widget.extend({ /** * @override */ - willStart: function () { - return session.user_companies ? this._super() : $.Deferred().reject(); - }, - /** - * @override - */ start: function () { var companiesList = ''; if (this.isMobile) { @@ -81,9 +75,9 @@ var SwitchCompanyMenu = Widget.extend({ }); }, }); - -SystrayMenu.Items.push(SwitchCompanyMenu); - +if (session.user_companies) { + SystrayMenu.Items.push(SwitchCompanyMenu); +} return SwitchCompanyMenu; }); diff --git a/addons/web/static/tests/chrome/action_manager_mobile_tests.js b/addons/web/static/tests/chrome/action_manager_mobile_tests.js index d1bc7a5e489..6be9aae8e26 100644 --- a/addons/web/static/tests/chrome/action_manager_mobile_tests.js +++ b/addons/web/static/tests/chrome/action_manager_mobile_tests.js @@ -53,23 +53,23 @@ QUnit.module('ActionManager', { }; }, }, function () { - QUnit.test('uses a mobile-friendly view by default (if possible)', function (assert) { + QUnit.test('uses a mobile-friendly view by default (if possible)', async function (assert) { assert.expect(4); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); // should default on a mobile-friendly view (kanban) for action 1 - actionManager.doAction(1); + await actionManager.doAction(1); assert.containsNone(actionManager, '.o_list_view'); assert.containsOnce(actionManager, '.o_kanban_view'); // there is no mobile-friendly view for action 2, should use the first one (list) - actionManager.doAction(2); + await actionManager.doAction(2); assert.containsOnce(actionManager, '.o_list_view'); assert.containsNone(actionManager, '.o_kanban_view'); @@ -77,10 +77,10 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('lazy load mobile-friendly view', function (assert) { + QUnit.test('lazy load mobile-friendly view', async function (assert) { assert.expect(11); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -89,7 +89,7 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.loadState({ + await actionManager.loadState({ action: 1, view_type: 'form', }); @@ -99,7 +99,7 @@ QUnit.module('ActionManager', { assert.containsOnce(actionManager, '.o_form_view'); // go back to lazy loaded view - testUtils.dom.click(actionManager.$('.o_control_panel .breadcrumb .breadcrumb-item:first')); + await testUtils.dom.click(actionManager.$('.o_control_panel .breadcrumb .breadcrumb-item:first')); assert.containsNone(actionManager, '.o_form_view'); assert.containsNone(actionManager, '.o_list_view'); assert.containsOnce(actionManager, '.o_kanban_view'); @@ -114,16 +114,16 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('view switcher button should be displayed in dropdown on mobile screens', function (assert) { + QUnit.test('view switcher button should be displayed in dropdown on mobile screens', async function (assert) { assert.expect(3); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(1); + await actionManager.doAction(1); assert.containsOnce(actionManager.$('.o_control_panel'), '.o_cp_switch_buttons button[data-toggle="dropdown"]'); assert.hasClass(actionManager.$('.o_cp_switch_buttons .o_cp_switch_kanban'), 'active'); diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js index 4b8a3a79751..bef2057a076 100644 --- a/addons/web/static/tests/chrome/action_manager_tests.js +++ b/addons/web/static/tests/chrome/action_manager_tests.js @@ -146,23 +146,23 @@ QUnit.module('ActionManager', { }, function () { QUnit.module('Misc'); - QUnit.test('breadcrumbs and actions with target inline', function (assert) { + QUnit.test('breadcrumbs and actions with target inline', async function (assert) { assert.expect(3); this.actions[3].views = [[false, 'form']]; this.actions[3].target = 'inline'; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(4); + await actionManager.doAction(4); assert.ok(!$('.o_control_panel').is(':visible'), "control panel should not be visible"); - actionManager.doAction(1, {clear_breadcrumbs: true}); + await actionManager.doAction(1, {clear_breadcrumbs: true}); assert.ok($('.o_control_panel').is(':visible'), "control panel should now be visible"); assert.strictEqual($('.o_control_panel .breadcrumb').text(), "Partners Action 1", @@ -171,7 +171,7 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('no widget memory leaks when doing some action stuff', function (assert) { + QUnit.test('no widget memory leaks when doing some action stuff', async function (assert) { assert.expect(1); var delta = 0; @@ -186,21 +186,21 @@ QUnit.module('ActionManager', { }, }); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(8); + await actionManager.doAction(8); var n = delta; - actionManager.doAction(4); + await actionManager.doAction(4); // kanban view is loaded, switch to list view - testUtils.dom.click($('.o_control_panel .o_cp_switch_list')); + await testUtils.dom.click($('.o_control_panel .o_cp_switch_list')); // open a record in form view - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); // go back to action 7 in breadcrumbs - testUtils.dom.click($('.o_control_panel .breadcrumb a:first')); + await testUtils.dom.click($('.o_control_panel .breadcrumb a:first')); assert.strictEqual(delta, n, "should have properly destroyed all other widgets"); @@ -208,7 +208,7 @@ QUnit.module('ActionManager', { testUtils.mock.unpatch(Widget); }); - QUnit.test('no widget memory leaks when executing actions in dialog', function (assert) { + QUnit.test('no widget memory leaks when executing actions in dialog', async function (assert) { assert.expect(1); var delta = 0; @@ -225,15 +225,15 @@ QUnit.module('ActionManager', { }, }); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); var n = delta; - actionManager.doAction(5); - actionManager.doAction({type: 'ir.actions.act_window_close'}); + await actionManager.doAction(5); + await actionManager.doAction({type: 'ir.actions.act_window_close'}); assert.strictEqual(delta, n, "should have properly destroyed all widgets"); @@ -242,7 +242,7 @@ QUnit.module('ActionManager', { testUtils.mock.unpatch(Widget); }); - QUnit.test('no memory leaks when executing an action while switching view', function (assert) { + QUnit.test('no memory leaks when executing an action while switching view', async function (assert) { assert.expect(1); var def; @@ -258,33 +258,34 @@ QUnit.module('ActionManager', { }, }); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, mockRPC: function (route, args) { var result = this._super.apply(this, arguments); if (args.method === 'read') { - return $.when(def).then(_.constant(result)); + return Promise.resolve(def).then(_.constant(result)); } return result; }, }); - actionManager.doAction(4); + await actionManager.doAction(4); var n = delta; - actionManager.doAction(3, {clear_breadcrumbs: true}); + await actionManager.doAction(3, {clear_breadcrumbs: true}); // switch to the form view (this request is blocked) - def = $.Deferred(); - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + def = testUtils.makeTestPromise(); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); // execute another action meanwhile (don't block this request) - actionManager.doAction(4, {clear_breadcrumbs: true}); + await actionManager.doAction(4, {clear_breadcrumbs: true}); // unblock the switch to the form view in action 3 def.resolve(); + await testUtils.nextTick(); assert.strictEqual(n, delta, "all widgets of action 3 should have been destroyed"); @@ -293,7 +294,7 @@ QUnit.module('ActionManager', { testUtils.mock.unpatch(Widget); }); - QUnit.test('no memory leaks when executing an action while loading views', function (assert) { + QUnit.test('no memory leaks when executing an action while loading views', async function (assert) { assert.expect(1); var def; @@ -309,30 +310,31 @@ QUnit.module('ActionManager', { }, }); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, mockRPC: function (route, args) { var result = this._super.apply(this, arguments); if (args.method === 'load_views') { - return $.when(def).then(_.constant(result)); + return Promise.resolve(def).then(_.constant(result)); } return result; }, }); // execute action 4 to know the number of widgets it instantiates - actionManager.doAction(4); + await actionManager.doAction(4); var n = delta; // execute a first action (its 'load_views' RPC is blocked) - def = $.Deferred(); + def = testUtils.makeTestPromise(); actionManager.doAction(3, {clear_breadcrumbs: true}); // execute another action meanwhile (and unlock the RPC) actionManager.doAction(4, {clear_breadcrumbs: true}); def.resolve(); + await testUtils.nextTick(); assert.strictEqual(n, delta, "all widgets of action 3 should have been destroyed"); @@ -341,7 +343,7 @@ QUnit.module('ActionManager', { testUtils.mock.unpatch(Widget); }); - QUnit.test('no memory leaks when executing an action while loading data of default view', function (assert) { + QUnit.test('no memory leaks when executing an action while loading data of default view', async function (assert) { assert.expect(1); var def; @@ -357,30 +359,31 @@ QUnit.module('ActionManager', { }, }); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, mockRPC: function (route) { var result = this._super.apply(this, arguments); if (route === '/web/dataset/search_read') { - return $.when(def).then(_.constant(result)); + return Promise.resolve(def).then(_.constant(result)); } return result; }, }); // execute action 4 to know the number of widgets it instantiates - actionManager.doAction(4); + await actionManager.doAction(4); var n = delta; // execute a first action (its 'search_read' RPC is blocked) - def = $.Deferred(); + def = testUtils.makeTestPromise(); actionManager.doAction(3, {clear_breadcrumbs: true}); // execute another action meanwhile (and unlock the RPC) actionManager.doAction(4, {clear_breadcrumbs: true}); def.resolve(); + await testUtils.nextTick(); assert.strictEqual(n, delta, "all widgets of action 3 should have been destroyed"); @@ -389,71 +392,72 @@ QUnit.module('ActionManager', { testUtils.mock.unpatch(Widget); }); - QUnit.test('action with "no_breadcrumbs" set to true', function (assert) { + QUnit.test('action with "no_breadcrumbs" set to true', async function (assert) { assert.expect(2); _.findWhere(this.actions, {id: 4}).context = {no_breadcrumbs: true}; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(3); + await actionManager.doAction(3); assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 1, "there should be one controller in the breadcrumbs"); // push another action flagged with 'no_breadcrumbs=true' - actionManager.doAction(4); + await actionManager.doAction(4); assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 0, "the breadcrumbs should be empty"); actionManager.destroy(); }); - QUnit.test('on_reverse_breadcrumb handler is correctly called', function (assert) { + QUnit.test('on_reverse_breadcrumb handler is correctly called', async function (assert) { assert.expect(3); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); // execute action 3 and open a record in form view - actionManager.doAction(3); + await actionManager.doAction(3); testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); // execute action 4 without 'on_reverse_breadcrumb' handler, then go back - actionManager.doAction(4); - testUtils.dom.click($('.o_control_panel .breadcrumb a:first')); + await actionManager.doAction(4); + await testUtils.dom.click($('.o_control_panel .breadcrumb a:first')); assert.verifySteps([]); // execute action 4 with an 'on_reverse_breadcrumb' handler, then go back - actionManager.doAction(4, { + await actionManager.doAction(4, { on_reverse_breadcrumb: function () { assert.step('on_reverse_breadcrumb'); } }); - testUtils.dom.click($('.o_control_panel .breadcrumb a:first')); + await testUtils.dom.click($('.o_control_panel .breadcrumb a:first')); assert.verifySteps(['on_reverse_breadcrumb']); actionManager.destroy(); }); - QUnit.test('handles "history_back" event', function (assert) { + QUnit.test('handles "history_back" event', async function (assert) { assert.expect(2); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(4); - actionManager.doAction(3); + await actionManager.doAction(4); + await actionManager.doAction(3); actionManager.trigger_up('history_back'); + await testUtils.nextTick(); assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 1, "there should be one controller in the breadcrumbs"); assert.strictEqual($('.o_control_panel .breadcrumb-item').text(), 'Partners Action 4', @@ -462,12 +466,12 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('stores and restores scroll position', function (assert) { + QUnit.test('stores and restores scroll position', async function (assert) { assert.expect(7); var left; var top; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -484,17 +488,17 @@ QUnit.module('ActionManager', { // execute a first action and simulate a scroll assert.step('execute action 3'); - actionManager.doAction(3); + await actionManager.doAction(3); left = 50; top = 100; // execute a second action (in which we don't scroll) assert.step('execute action 4'); - actionManager.doAction(4); + await actionManager.doAction(4); // go back using the breadcrumbs assert.step('go back to action 3'); - testUtils.dom.click($('.o_control_panel .breadcrumb a')); + await testUtils.dom.click($('.o_control_panel .breadcrumb a')); assert.verifySteps([ 'execute action 3', @@ -510,7 +514,7 @@ QUnit.module('ActionManager', { QUnit.module('Push State'); - QUnit.test('properly push state', function (assert) { + QUnit.test('properly push state', async function (assert) { assert.expect(3); var stateDescriptions = [ @@ -519,7 +523,7 @@ QUnit.module('ActionManager', { {action: 8, id: 4, model: "pony", title: "Twilight Sparkle", view_type: "form"}, ]; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -531,17 +535,17 @@ QUnit.module('ActionManager', { }, }, }); - actionManager.doAction(4); - actionManager.doAction(8); - testUtils.dom.click(actionManager.$('tr.o_data_row:first')); + await actionManager.doAction(4); + await actionManager.doAction(8); + await testUtils.dom.click(actionManager.$('tr.o_data_row:first')); actionManager.destroy(); }); - QUnit.test('push state after action is loaded, not before', function (assert) { + QUnit.test('push state after action is loaded, not before', async function (assert) { assert.expect(5); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -555,7 +559,7 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.doAction(4); + await actionManager.doAction(4); assert.verifySteps([ '/web/action/load', '/web/dataset/call_kw/partner', @@ -566,10 +570,10 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('do not push state for actions in target=new', function (assert) { + QUnit.test('do not push state for actions in target=new', async function (assert) { assert.expect(3); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -579,18 +583,18 @@ QUnit.module('ActionManager', { }, }, }); - actionManager.doAction(4); - assert.verifySteps(['push_state']); - actionManager.doAction(5); + await actionManager.doAction(4); assert.verifySteps(['push_state']); + await actionManager.doAction(5); + assert.verifySteps([]); actionManager.destroy(); }); - QUnit.test('do not push state when action fails', function (assert) { + QUnit.test('do not push state when action fails', async function (assert) { assert.expect(4); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -602,15 +606,15 @@ QUnit.module('ActionManager', { mockRPC: function (route, args) { if (args.method === 'read') { // this is the rpc to load form view - return $.Deferred().reject(); + return Promise.reject(); } return this._super.apply(this, arguments); }, }); - actionManager.doAction(8); - assert.verifySteps(['push_state']); - testUtils.dom.click(actionManager.$('tr.o_data_row:first')); + await actionManager.doAction(8); assert.verifySteps(['push_state']); + await testUtils.dom.click(actionManager.$('tr.o_data_row:first')); + assert.verifySteps([]); // we make sure here that the list view is still in the dom assert.containsOnce(actionManager, '.o_list_view', "there should still be a list view in dom"); @@ -620,10 +624,10 @@ QUnit.module('ActionManager', { QUnit.module('Load State'); - QUnit.test('should not crash on invalid state', function (assert) { + QUnit.test('should not crash on invalid state', async function (assert) { assert.expect(2); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -632,7 +636,7 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.loadState({ + await actionManager.loadState({ res_model: 'partner', // the valid key for the model is 'model', not 'res_model' }); @@ -642,7 +646,7 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('properly load client actions', function (assert) { + QUnit.test('properly load client actions', async function (assert) { assert.expect(2); var ClientAction = AbstractAction.extend({ @@ -653,7 +657,7 @@ QUnit.module('ActionManager', { }); core.action_registry.add('HelloWorldTest', ClientAction); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -662,7 +666,7 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.loadState({ + await actionManager.loadState({ action: 'HelloWorldTest', }); @@ -675,10 +679,10 @@ QUnit.module('ActionManager', { delete core.action_registry.map.HelloWorldTest; }); - QUnit.test('properly load act window actions', function (assert) { + QUnit.test('properly load act window actions', async function (assert) { assert.expect(6); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -687,7 +691,7 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.loadState({ + await actionManager.loadState({ action: 1, }); @@ -705,10 +709,10 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('properly load records', function (assert) { + QUnit.test('properly load records', async function (assert) { assert.expect(5); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -717,7 +721,7 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.loadState({ + await actionManager.loadState({ id: 2, model: 'partner', }); @@ -735,10 +739,10 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('properly load default record', function (assert) { + QUnit.test('properly load default record', async function (assert) { assert.expect(5); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -747,7 +751,7 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.loadState({ + await actionManager.loadState({ action: 3, id: "", // might happen with bbq and id=& in URL model: 'partner', @@ -766,10 +770,10 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('load requested view for act window actions', function (assert) { + QUnit.test('load requested view for act window actions', async function (assert) { assert.expect(6); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -778,7 +782,7 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.loadState({ + await actionManager.loadState({ action: 3, view_type: 'kanban', }); @@ -797,10 +801,10 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('lazy load multi record view if mono record one is requested', function (assert) { + QUnit.test('lazy load multi record view if mono record one is requested', async function (assert) { assert.expect(11); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -809,12 +813,11 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.loadState({ + await actionManager.loadState({ action: 3, id: 2, view_type: 'form', }); - assert.containsNone(actionManager, '.o_list_view', "should not have rendered a list view"); assert.containsOnce(actionManager, '.o_form_view', @@ -825,7 +828,7 @@ QUnit.module('ActionManager', { "breadcrumbs should contain the display_name of the opened record"); // go back to Lst - testUtils.dom.click($('.o_control_panel .breadcrumb a')); + await testUtils.dom.click($('.o_control_panel .breadcrumb a')); assert.containsOnce(actionManager, '.o_list_view', "should now display the list view"); assert.containsNone(actionManager, '.o_form_view', @@ -841,22 +844,22 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('lazy load multi record view with previous action', function (assert) { + QUnit.test('lazy load multi record view with previous action', async function (assert) { assert.expect(6); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(4); + await actionManager.doAction(4); assert.strictEqual($('.o_control_panel .breadcrumb li').length, 1, "there should be one controller in the breadcrumbs"); assert.strictEqual($('.o_control_panel .breadcrumb li').text(), 'Partners Action 4', "breadcrumbs should contain the display_name of the opened record"); - actionManager.doAction(3, { + await actionManager.doAction(3, { resID: 2, viewType: 'form', }); @@ -867,7 +870,7 @@ QUnit.module('ActionManager', { "the breadcrumb elements should be correctly ordered"); // go back to List - testUtils.dom.click($('.o_control_panel .breadcrumb a:last')); + await testUtils.dom.click($('.o_control_panel .breadcrumb a:last')); assert.strictEqual($('.o_control_panel .breadcrumb li').length, 2, "there should be two controllers in the breadcrumbs"); @@ -877,10 +880,10 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('change the viewType of the current action', function (assert) { + QUnit.test('change the viewType of the current action', async function (assert) { assert.expect(13); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -889,13 +892,13 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.doAction(3); + await actionManager.doAction(3); assert.containsOnce(actionManager, '.o_list_view', "should have rendered a list view"); // switch to kanban view - actionManager.loadState({ + await actionManager.loadState({ action: 3, view_type: 'kanban', }); @@ -906,7 +909,7 @@ QUnit.module('ActionManager', { "should have switched to the kanban view"); // switch to form view, open record 4 - actionManager.loadState({ + await actionManager.loadState({ action: 3, id: 4, view_type: 'form', @@ -935,10 +938,10 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('change the id of the current action', function (assert) { + QUnit.test('change the id of the current action', async function (assert) { assert.expect(11); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -949,8 +952,8 @@ QUnit.module('ActionManager', { }); // execute action 3 and open the first record in a form view - actionManager.doAction(3); - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + await actionManager.doAction(3); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); assert.containsOnce(actionManager, '.o_form_view', "should have rendered a form view"); @@ -958,7 +961,7 @@ QUnit.module('ActionManager', { "should have opened the first record"); // switch to record 4 - actionManager.loadState({ + await actionManager.loadState({ action: 3, id: 4, view_type: 'form', @@ -985,10 +988,10 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('should not push a loaded state', function (assert) { + QUnit.test('should not push a loaded state', async function (assert) { assert.expect(3); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -998,11 +1001,11 @@ QUnit.module('ActionManager', { }, }, }); - actionManager.loadState({action: 3}); + await actionManager.loadState({action: 3}); assert.verifySteps([], "should not push the loaded state"); - testUtils.dom.click(actionManager.$('tr.o_data_row:first')); + await testUtils.dom.click(actionManager.$('tr.o_data_row:first')); assert.verifySteps(['push_state'], "should push the state of it changes afterwards"); @@ -1010,7 +1013,7 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('should not push a loaded state of a client action', function (assert) { + QUnit.test('should not push a loaded state of a client action', async function (assert) { assert.expect(4); var ClientAction = AbstractAction.extend({ @@ -1033,7 +1036,7 @@ QUnit.module('ActionManager', { }); core.action_registry.add('ClientAction', ClientAction); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -1048,11 +1051,11 @@ QUnit.module('ActionManager', { }, }, }); - actionManager.loadState({action: 9}); + await actionManager.loadState({action: 9}); assert.verifySteps([], "should not push the loaded state"); - testUtils.dom.click(actionManager.$('button')); + await testUtils.dom.click(actionManager.$('button')); assert.verifySteps(['push_state'], "should push the state of it changes afterwards"); @@ -1060,7 +1063,7 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('change a param of an ir.actions.client in the url', function (assert) { + QUnit.test('change a param of an ir.actions.client in the url', async function (assert) { assert.expect(7); var ClientAction = AbstractAction.extend({ @@ -1083,14 +1086,14 @@ QUnit.module('ActionManager', { }); core.action_registry.add('ClientAction', ClientAction); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); // execute the client action - actionManager.doAction(9); + await actionManager.doAction(9); assert.strictEqual(actionManager.$('.o_client_action .o_content').text(), 'default value', "should have rendered the client action"); @@ -1098,7 +1101,7 @@ QUnit.module('ActionManager', { "there should be one controller in the breadcrumbs"); // update param 'a' in the url - actionManager.loadState({ + await actionManager.loadState({ action: 9, a: 'new value', }); @@ -1115,14 +1118,14 @@ QUnit.module('ActionManager', { delete core.action_registry.map.ClientAction; }); - QUnit.test('load a window action without id (in a multi-record view)', function (assert) { + QUnit.test('load a window action without id (in a multi-record view)', async function (assert) { assert.expect(14); var RamStorageService = AbstractStorageService.extend({ storage: new RamStorage(), }); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -1141,14 +1144,14 @@ QUnit.module('ActionManager', { } }, true); - actionManager.doAction(4); + await actionManager.doAction(4); assert.containsOnce(actionManager, '.o_kanban_view', "should display a kanban view"); assert.strictEqual($('.o_control_panel .breadcrumb-item').text(), 'Partners Action 4', "breadcrumbs should display the display_name of the action"); - actionManager.loadState({ + await actionManager.loadState({ model: 'partner', view_type: 'list', }); @@ -1176,11 +1179,11 @@ QUnit.module('ActionManager', { QUnit.module('Concurrency management'); - QUnit.test('drop previous actions if possible', function (assert) { + QUnit.test('drop previous actions if possible', async function (assert) { assert.expect(6); - var def = $.Deferred(); - var actionManager = createActionManager({ + var def = testUtils.makeTestPromise(); + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -1197,7 +1200,7 @@ QUnit.module('ActionManager', { actionManager.doAction(8); def.resolve(); - + await testUtils.nextTick(); // action 4 loads a kanban view first, 6 loads a list view. We want a list assert.containsOnce(actionManager, '.o_list_view', 'there should be a list view in DOM'); @@ -1212,13 +1215,13 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('handle switching view and switching back on slow network', function (assert) { + QUnit.test('handle switching view and switching back on slow network', async function (assert) { assert.expect(8); - var def = $.Deferred(); - var defs = [$.when(), def, $.when()]; + var def = testUtils.makeTestPromise(); + var defs = [Promise.resolve(), def, Promise.resolve()]; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -1232,14 +1235,13 @@ QUnit.module('ActionManager', { return result; }, }); - actionManager.doAction(4); - + await actionManager.doAction(4); // kanban view is loaded, switch to list view - testUtils.dom.click($('.o_control_panel .o_cp_switch_list')); + await testUtils.dom.click($('.o_control_panel .o_cp_switch_list')); // here, list view is not ready yet, because def is not resolved // switch back to kanban view - testUtils.dom.click($('.o_control_panel .o_cp_switch_kanban')); + await testUtils.dom.click($('.o_control_panel .o_cp_switch_kanban')); // here, we want the kanban view to reload itself, regardless of list view assert.verifySteps([ @@ -1252,7 +1254,7 @@ QUnit.module('ActionManager', { // we resolve def => list view is now ready (but we want to ignore it) def.resolve(); - + await testUtils.nextTick(); assert.containsOnce(actionManager, '.o_kanban_view', "there should be a kanban view in dom"); assert.containsNone(actionManager, '.o_list_view', @@ -1261,12 +1263,12 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('when an server action takes too much time...', function (assert) { + QUnit.test('when an server action takes too much time...', async function (assert) { assert.expect(1); - var def = $.Deferred(); + var def = testUtils.makeTestPromise(); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -1282,19 +1284,19 @@ QUnit.module('ActionManager', { actionManager.doAction(4); def.resolve(); - + await testUtils.nextTick(); assert.strictEqual($('.o_control_panel .breadcrumb-item.active').text(), 'Partners Action 4', 'action 4 should be loaded'); actionManager.destroy(); }); - QUnit.test('clicking quickly on breadcrumbs...', function (assert) { + QUnit.test('clicking quickly on breadcrumbs...', async function (assert) { assert.expect(1); - var def = $.when(); + var def = Promise.resolve(); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -1308,21 +1310,23 @@ QUnit.module('ActionManager', { }); // create a situation with 3 breadcrumbs: kanban/form/list - actionManager.doAction(4); - testUtils.dom.click(actionManager.$('.o_kanban_record:first')); + await actionManager.doAction(4); + await testUtils.dom.click(actionManager.$('.o_kanban_record:first')); actionManager.doAction(8); - // now, the next read operations will be deferred (this is the read + // now, the next read operations will be promise (this is the read // operation for the form view reload) - def = $.Deferred(); + def = testUtils.makeTestPromise(); + await testUtils.nextTick(); // click on the breadcrumbs for the form view, then on the kanban view // before the form view is fully reloaded - testUtils.dom.click($('.o_control_panel .breadcrumb-item:eq(1)')); - testUtils.dom.click($('.o_control_panel .breadcrumb-item:eq(0)')); + await testUtils.dom.click($('.o_control_panel .breadcrumb-item:eq(1)')); + await testUtils.dom.click($('.o_control_panel .breadcrumb-item:eq(0)')); // resolve the form view read def.resolve(); + await testUtils.nextTick(); assert.strictEqual($('.o_control_panel .breadcrumb-item.active').text(), 'Partners Action 4', 'action 4 should be loaded and visible'); @@ -1330,11 +1334,11 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('execute a new action while loading a lazy-loaded controller', function (assert) { + QUnit.test('execute a new action while loading a lazy-loaded controller', async function (assert) { assert.expect(15); var def; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -1342,12 +1346,12 @@ QUnit.module('ActionManager', { var result = this._super.apply(this, arguments); assert.step(args.method || route); if (route === '/web/dataset/search_read' && args.model === 'partner') { - return $.when(def).then(_.constant(result)); + return Promise.resolve(def).then(_.constant(result)); } return result; }, }); - actionManager.loadState({ + await actionManager.loadState({ action: 4, id: 2, view_type: 'form', @@ -1357,19 +1361,20 @@ QUnit.module('ActionManager', { "should display the form view of action 4"); // click to go back to Kanban (this request is blocked) - def = $.Deferred(); - testUtils.dom.click($('.o_control_panel .breadcrumb a')); + def = testUtils.makeTestPromise(); + await testUtils.nextTick(); + await testUtils.dom.click($('.o_control_panel .breadcrumb a')); assert.containsOnce(actionManager, '.o_form_view', - "should still display the form view of action 4"); + "should still display the form view of action 4"); // execute another action meanwhile (don't block this request) - actionManager.doAction(8, {clear_breadcrumbs: true}); + await actionManager.doAction(8, {clear_breadcrumbs: true}); assert.containsOnce(actionManager, '.o_list_view', - "should display action 8"); + "should display action 8"); assert.containsNone(actionManager, '.o_form_view', - "should no longer display the form view"); + "should no longer display the form view"); assert.verifySteps([ '/web/action/load', // load state action 4 @@ -1383,31 +1388,24 @@ QUnit.module('ActionManager', { // unblock the switch to Kanban in action 4 def.resolve(); + await testUtils.nextTick(); assert.containsOnce(actionManager, '.o_list_view', "should still display action 8"); assert.containsNone(actionManager, '.o_kanban_view', "should not display the kanban view of action 4"); - assert.verifySteps([ - '/web/action/load', // load state action 4 - 'load_views', // load state action 4 - 'read', // read the opened record (action 4) - '/web/dataset/search_read', // blocked search read when coming back to Kanban (action 4) - '/web/action/load', // action 8 - 'load_views', // action 8 - '/web/dataset/search_read', // search read action 8 - ]); + assert.verifySteps([]); actionManager.destroy(); }); - QUnit.test('execute a new action while handling a call_button', function (assert) { + QUnit.test('execute a new action while handling a call_button', async function (assert) { assert.expect(16); var self = this; - var def = $.Deferred(); - var actionManager = createActionManager({ + var def = testUtils.makeTestPromise(); + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -1421,20 +1419,20 @@ QUnit.module('ActionManager', { }); // execute action 3 and open a record in form view - actionManager.doAction(3); - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + await actionManager.doAction(3); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); assert.containsOnce(actionManager, '.o_form_view', "should display the form view of action 3"); // click on 'Call method' button (this request is blocked) - testUtils.dom.click(actionManager.$('.o_form_view button:contains(Call method)')); + await testUtils.dom.click(actionManager.$('.o_form_view button:contains(Call method)')); assert.containsOnce(actionManager, '.o_form_view', "should still display the form view of action 3"); // execute another action - actionManager.doAction(8, {clear_breadcrumbs: true}); + await actionManager.doAction(8, {clear_breadcrumbs: true}); assert.containsOnce(actionManager, '.o_list_view', "should display the list view of action 8"); @@ -1454,31 +1452,22 @@ QUnit.module('ActionManager', { // unblock the call_button request def.resolve(); - + await testUtils.nextTick(); assert.containsOnce(actionManager, '.o_list_view', "should still display the list view of action 8"); assert.containsNone(actionManager, '.o_kanban_view', "should not display action 1"); - assert.verifySteps([ - '/web/action/load', // action 3 - 'load_views', // action 3 - '/web/dataset/search_read', // list for action 3 - 'read', // form for action 3 - 'object', // click on 'Call method' button (this request is blocked) - '/web/action/load', // action 8 - 'load_views', // action 8 - '/web/dataset/search_read', // list for action 8 - ]); + assert.verifySteps([]); actionManager.destroy(); }); - QUnit.test('execute a new action while switching to another controller', function (assert) { + QUnit.test('execute a new action while switching to another controller', async function (assert) { assert.expect(15); var def; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -1486,26 +1475,27 @@ QUnit.module('ActionManager', { var result = this._super.apply(this, arguments); assert.step(args.method || route); if (args.method === 'read') { - return $.when(def).then(_.constant(result)); + return Promise.resolve(def).then(_.constant(result)); } return result; }, }); - actionManager.doAction(3); + await actionManager.doAction(3); assert.containsOnce(actionManager, '.o_list_view', "should display the list view of action 3"); // switch to the form view (this request is blocked) - def = $.Deferred(); + def = testUtils.makeTestPromise(); + await testUtils.nextTick(); testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); assert.containsOnce(actionManager, '.o_list_view', - "should still display the list view of action 3"); + "should still display the list view of action 3"); // execute another action meanwhile (don't block this request) - actionManager.doAction(4, {clear_breadcrumbs: true}); + await actionManager.doAction(4, {clear_breadcrumbs: true}); assert.containsOnce(actionManager, '.o_kanban_view', "should display the kanban view of action 8"); @@ -1516,38 +1506,31 @@ QUnit.module('ActionManager', { '/web/action/load', // action 3 'load_views', // action 3 '/web/dataset/search_read', // search read of list view of action 3 - 'read', // read the opened record of action 3 (this request is blocked) '/web/action/load', // action 4 'load_views', // action 4 + 'read', // read the opened record of action 3 (this request is blocked) '/web/dataset/search_read', // search read action 4 ]); // unblock the switch to the form view in action 3 def.resolve(); + await testUtils.nextTick(); assert.containsOnce(actionManager, '.o_kanban_view', "should still display the kanban view of action 8"); assert.containsNone(actionManager, '.o_form_view', "should not display the form view of action 3"); - assert.verifySteps([ - '/web/action/load', // action 3 - 'load_views', // action 3 - '/web/dataset/search_read', // search read of list view of action 3 - 'read', // read the opened record of action 3 (this request is blocked) - '/web/action/load', // action 4 - 'load_views', // action 4 - '/web/dataset/search_read', // search read action 4 - ]); + assert.verifySteps([]); actionManager.destroy(); }); - QUnit.test('execute a new action while loading views', function (assert) { + QUnit.test('execute a new action while loading views', async function (assert) { assert.expect(10); var def; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -1555,22 +1538,24 @@ QUnit.module('ActionManager', { var result = this._super.apply(this, arguments); assert.step(args.method || route); if (args.method === 'load_views') { - return $.when(def).then(_.constant(result)); + return Promise.resolve(def).then(_.constant(result)); } return result; }, }); // execute a first action (its 'load_views' RPC is blocked) - def = $.Deferred(); + def = testUtils.makeTestPromise(); actionManager.doAction(3); assert.containsNone(actionManager, '.o_list_view', "should not display the list view of action 3"); + await testUtils.nextTick(); // execute another action meanwhile (and unlock the RPC) actionManager.doAction(4); def.resolve(); + await testUtils.nextTick(); assert.containsOnce(actionManager, '.o_kanban_view', "should display the kanban view of action 4"); @@ -1590,11 +1575,11 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('execute a new action while loading data of default view', function (assert) { + QUnit.test('execute a new action while loading data of default view', async function (assert) { assert.expect(11); var def; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -1602,23 +1587,24 @@ QUnit.module('ActionManager', { var result = this._super.apply(this, arguments); assert.step(args.method || route); if (route === '/web/dataset/search_read') { - return $.when(def).then(_.constant(result)); + return Promise.resolve(def).then(_.constant(result)); } return result; }, }); // execute a first action (its 'search_read' RPC is blocked) - def = $.Deferred(); + def = testUtils.makeTestPromise(); actionManager.doAction(3); assert.containsNone(actionManager, '.o_list_view', "should not display the list view of action 3"); + await testUtils.nextTick(); // execute another action meanwhile (and unlock the RPC) actionManager.doAction(4); def.resolve(); - + await testUtils.nextTick(); assert.containsOnce(actionManager, '.o_kanban_view', "should display the kanban view of action 4"); assert.containsNone(actionManager, '.o_list_view', @@ -1638,24 +1624,24 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('open a record while reloading the list view', function (assert) { + QUnit.test('open a record while reloading the list view', async function (assert) { assert.expect(12); var def; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, mockRPC: function (route) { var result = this._super.apply(this, arguments); if (route === '/web/dataset/search_read') { - return $.when(def).then(_.constant(result)); + return Promise.resolve(def).then(_.constant(result)); } return result; }, }); - actionManager.doAction(3); + await actionManager.doAction(3); assert.containsOnce(actionManager, '.o_list_view', "should display the list view"); @@ -1665,8 +1651,9 @@ QUnit.module('ActionManager', { "list view buttons should be displayed in control panel"); // reload (the search_read RPC will be blocked) - def = $.Deferred(); - testUtils.dom.click($('.o_control_panel .o_cp_switch_list')); + def = testUtils.makeTestPromise(); + await testUtils.nextTick(); + await testUtils.dom.click($('.o_control_panel .o_cp_switch_list')); assert.containsN(actionManager, '.o_list_view .o_data_row', 5, "list view should still contain 5 records"); @@ -1674,7 +1661,7 @@ QUnit.module('ActionManager', { "list view buttons should still be displayed in control panel"); // open a record in form view - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); assert.containsOnce(actionManager, '.o_form_view', "should display the form view"); @@ -1685,6 +1672,7 @@ QUnit.module('ActionManager', { // unblock the search_read RPC def.resolve(); + await testUtils.nextTick(); assert.containsOnce(actionManager, '.o_form_view', "should display the form view"); @@ -1700,7 +1688,7 @@ QUnit.module('ActionManager', { QUnit.module('Client Actions'); - QUnit.test('can execute client actions from tag name', function (assert) { + QUnit.test('can execute client actions from tag name', async function (assert) { assert.expect(3); var ClientAction = AbstractAction.extend({ @@ -1711,13 +1699,13 @@ QUnit.module('ActionManager', { }); core.action_registry.add('HelloWorldTest', ClientAction); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ mockRPC: function (route, args) { assert.step(args.method || route); return this._super.apply(this, arguments); } }); - actionManager.doAction('HelloWorldTest'); + await actionManager.doAction('HelloWorldTest'); assert.strictEqual($('.o_control_panel:visible').length, 0, // AAB: global selector until the ControlPanel is moved from ActionManager to the Views "shouldn't have rendered a control panel"); @@ -1729,7 +1717,7 @@ QUnit.module('ActionManager', { delete core.action_registry.map.HelloWorldTest; }); - QUnit.test('client action with control panel', function (assert) { + QUnit.test('client action with control panel', async function (assert) { assert.expect(4); var ClientAction = AbstractAction.extend({ @@ -1743,8 +1731,8 @@ QUnit.module('ActionManager', { }); core.action_registry.add('HelloWorldTest', ClientAction); - var actionManager = createActionManager(); - actionManager.doAction('HelloWorldTest'); + var actionManager = await createActionManager(); + await actionManager.doAction('HelloWorldTest'); assert.strictEqual($('.o_control_panel:visible').length, 1, "should have rendered a control panel"); @@ -1759,11 +1747,11 @@ QUnit.module('ActionManager', { delete core.action_registry.map.HelloWorldTest; }); - QUnit.test('state is pushed for client actions', function (assert) { + QUnit.test('state is pushed for client actions', async function (assert) { assert.expect(2); var ClientAction = AbstractAction.extend({}); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ intercepts: { push_state: function () { assert.step('push state'); @@ -1772,7 +1760,7 @@ QUnit.module('ActionManager', { }); core.action_registry.add('HelloWorldTest', ClientAction); - actionManager.doAction('HelloWorldTest'); + await actionManager.doAction('HelloWorldTest'); assert.verifySteps(['push state']); @@ -1780,7 +1768,7 @@ QUnit.module('ActionManager', { delete core.action_registry.map.HelloWorldTest; }); - QUnit.test('breadcrumb is updated on title change', function (assert) { + QUnit.test('breadcrumb is updated on title change', async function (assert) { assert.expect(2); var ClientAction = AbstractAction.extend({ @@ -1797,9 +1785,9 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - var actionManager = createActionManager(); + var actionManager = await createActionManager(); core.action_registry.add('HelloWorldTest', ClientAction); - actionManager.doAction('HelloWorldTest'); + await actionManager.doAction('HelloWorldTest'); assert.strictEqual($('ol.breadcrumb').text(), "initial title", "should have initial title as breadcrumb content"); @@ -1814,10 +1802,10 @@ QUnit.module('ActionManager', { QUnit.module('Server actions'); - QUnit.test('can execute server actions from db ID', function (assert) { + QUnit.test('can execute server actions from db ID', async function (assert) { assert.expect(9); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -1826,12 +1814,12 @@ QUnit.module('ActionManager', { if (route === '/web/action/run') { assert.strictEqual(args.action_id, 2, "should call the correct server action"); - return $.when(1); // execute action 1 + return Promise.resolve(1); // execute action 1 } return this._super.apply(this, arguments); }, }); - actionManager.doAction(2); + await actionManager.doAction(2); assert.strictEqual($('.o_control_panel:visible').length, 1, "should have rendered a control panel"); @@ -1848,31 +1836,31 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('handle server actions returning false', function (assert) { + QUnit.test('handle server actions returning false', async function (assert) { assert.expect(9); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, mockRPC: function (route, args) { assert.step(args.method || route); if (route === '/web/action/run') { - return $.when(false); + return Promise.resolve(false); } return this._super.apply(this, arguments); }, }); // execute an action in target="new" - actionManager.doAction(5, { + await actionManager.doAction(5, { on_close: assert.step.bind(assert, 'close handler'), }); assert.strictEqual($('.o_technical_modal .o_form_view').length, 1, "should have rendered a form view in a modal"); // execute a server action that returns false - actionManager.doAction(2); + await actionManager.doAction(2); assert.strictEqual($('.o_technical_modal').length, 0, "should have closed the modal"); assert.verifySteps([ @@ -1889,10 +1877,10 @@ QUnit.module('ActionManager', { QUnit.module('Report actions'); - QUnit.test('can execute report actions from db ID', function (assert) { - assert.expect(6); + QUnit.test('can execute report actions from db ID', async function (assert) { + assert.expect(5); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -1902,12 +1890,12 @@ QUnit.module('ActionManager', { mockRPC: function (route, args) { assert.step(args.method || route); if (route === '/report/check_wkhtmltopdf') { - return $.when('ok'); + return Promise.resolve('ok'); } return this._super.apply(this, arguments); }, session: { - get_file: function (params) { + get_file: async function (params) { assert.step(params.url); params.success(); params.complete(); @@ -1915,16 +1903,15 @@ QUnit.module('ActionManager', { }, }, }); - actionManager.doAction(7, { + await actionManager.doAction(7, { on_close: function () { assert.step('on_close'); }, }); - + await testUtils.nextTick(); assert.verifySteps([ '/web/action/load', '/report/check_wkhtmltopdf', - '/web/static/src/img/spin.png', // block UI image '/report/download', 'on_close', ]); @@ -1932,10 +1919,10 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('report actions can close modals and reload views', function (assert) { + QUnit.test('report actions can close modals and reload views', async function (assert) { assert.expect(8); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -1944,12 +1931,12 @@ QUnit.module('ActionManager', { }, mockRPC: function (route, args) { if (route === '/report/check_wkhtmltopdf') { - return $.when('ok'); + return Promise.resolve('ok'); } return this._super.apply(this, arguments); }, session: { - get_file: function (params) { + get_file: async function (params) { assert.step(params.url); params.success(); params.complete(); @@ -1959,7 +1946,7 @@ QUnit.module('ActionManager', { }); // load modal - actionManager.doAction(5, { + await actionManager.doAction(5, { on_close: function () { assert.step('on_close'); }, @@ -1968,7 +1955,7 @@ QUnit.module('ActionManager', { assert.strictEqual($('.o_technical_modal .o_form_view').length, 1, "should have rendered a form view in a modal"); - actionManager.doAction(7, { + await actionManager.doAction(7, { on_close: function () { assert.step('on_printed'); }, @@ -1977,7 +1964,7 @@ QUnit.module('ActionManager', { assert.strictEqual($('.o_technical_modal .o_form_view').length, 1, "The modal should still exist"); - actionManager.doAction(11); + await actionManager.doAction(11); assert.strictEqual($('.o_technical_modal .o_form_view').length, 0, "the modal should have been closed after the action report"); @@ -1992,10 +1979,10 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('should trigger a notification if wkhtmltopdf is to upgrade', function (assert) { - assert.expect(6); + QUnit.test('should trigger a notification if wkhtmltopdf is to upgrade', async function (assert) { + assert.expect(5); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -2010,12 +1997,12 @@ QUnit.module('ActionManager', { mockRPC: function (route, args) { assert.step(args.method || route); if (route === '/report/check_wkhtmltopdf') { - return $.when('upgrade'); + return Promise.resolve('upgrade'); } return this._super.apply(this, arguments); }, session: { - get_file: function (params) { + get_file: async function (params) { assert.step(params.url); params.success(); params.complete(); @@ -2023,19 +2010,18 @@ QUnit.module('ActionManager', { }, }, }); - actionManager.doAction(7); + await actionManager.doAction(7); assert.verifySteps([ '/web/action/load', '/report/check_wkhtmltopdf', 'notification', - '/web/static/src/img/spin.png', // block UI image '/report/download', ]); actionManager.destroy(); }); - QUnit.test('should open the report client action if wkhtmltopdf is broken', function (assert) { + QUnit.test('should open the report client action if wkhtmltopdf is broken', async function (assert) { assert.expect(6); // patch the report client action to override its iframe's url so that @@ -2052,7 +2038,7 @@ QUnit.module('ActionManager', { } }); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -2067,10 +2053,10 @@ QUnit.module('ActionManager', { mockRPC: function (route, args) { assert.step(args.method || route); if (route === '/report/check_wkhtmltopdf') { - return $.when('broken'); + return Promise.resolve('broken'); } if (route === 'test /report/html/some_report') { - return $.when(); + return Promise.resolve(); } return this._super.apply(this, arguments); }, @@ -2081,7 +2067,7 @@ QUnit.module('ActionManager', { }, }, }); - actionManager.doAction(7); + await actionManager.doAction(7); assert.containsOnce(actionManager, '.o_report_iframe', "should have opened the report client action"); @@ -2099,10 +2085,10 @@ QUnit.module('ActionManager', { QUnit.module('Window Actions'); - QUnit.test('can execute act_window actions from db ID', function (assert) { + QUnit.test('can execute act_window actions from db ID', async function (assert) { assert.expect(6); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -2111,7 +2097,7 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.doAction(1); + await actionManager.doAction(1); assert.strictEqual($('.o_control_panel').length, 1, "should have rendered a control panel"); @@ -2126,10 +2112,10 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('sidebar is present in list view', function (assert) { + QUnit.test('sidebar is present in list view', async function (assert) { assert.expect(5); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -2148,21 +2134,21 @@ QUnit.module('ActionManager', { return res; }, }); - actionManager.doAction(3); + await actionManager.doAction(3); assert.isNotVisible(actionManager.$('.o_cp_sidebar button.o_dropdown_toggler_btn:contains("Print")')); assert.isNotVisible(actionManager.$('.o_cp_sidebar button.o_dropdown_toggler_btn:contains("Action")')); - testUtils.dom.clickFirst(actionManager.$('input.custom-control-input')); + await testUtils.dom.clickFirst(actionManager.$('input.custom-control-input')); assert.isVisible(actionManager.$('.o_cp_sidebar button.o_dropdown_toggler_btn:contains("Print")')); assert.isVisible(actionManager.$('.o_cp_sidebar button.o_dropdown_toggler_btn:contains("Action")')); actionManager.destroy(); }); - QUnit.test('can switch between views', function (assert) { + QUnit.test('can switch between views', async function (assert) { assert.expect(18); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -2171,27 +2157,27 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.doAction(3); + await actionManager.doAction(3); assert.containsOnce(actionManager, '.o_list_view', "should display the list view"); // switch to kanban view - testUtils.dom.click($('.o_control_panel .o_cp_switch_kanban')); + await testUtils.dom.click($('.o_control_panel .o_cp_switch_kanban')); assert.containsNone(actionManager, '.o_list_view', "should no longer display the list view"); assert.containsOnce(actionManager, '.o_kanban_view', "should display the kanban view"); // switch back to list view - testUtils.dom.click($('.o_control_panel .o_cp_switch_list')); + await testUtils.dom.click($('.o_control_panel .o_cp_switch_list')); assert.containsOnce(actionManager, '.o_list_view', "should display the list view"); assert.containsNone(actionManager, '.o_kanban_view', "should no longer display the kanban view"); // open a record in form view - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); assert.containsNone(actionManager, '.o_list_view', "should no longer display the list view"); assert.containsOnce(actionManager, '.o_form_view', @@ -2200,7 +2186,7 @@ QUnit.module('ActionManager', { "should have opened the correct record"); // go back to list view using the breadcrumbs - testUtils.dom.click($('.o_control_panel .breadcrumb a')); + await testUtils.dom.click($('.o_control_panel .breadcrumb a')); assert.containsOnce(actionManager, '.o_list_view', "should display the list view"); assert.containsNone(actionManager, '.o_form_view', @@ -2219,7 +2205,7 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('orderedBy in context is not propagated when executing another action', function (assert) { + QUnit.test('orderedBy in context is not propagated when executing another action', async function (assert) { assert.expect(6); this.data.partner.fields.foo.sortable = true, @@ -2229,7 +2215,7 @@ QUnit.module('ActionManager', { '</header>'; var searchReadCount = 1; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -2252,7 +2238,7 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.doAction(3); + await actionManager.doAction(3); // Simulate the activation of a filter var searchData = { @@ -2264,26 +2250,26 @@ QUnit.module('ActionManager', { actionManager.trigger_up('search', searchData); // Sort records - actionManager.$('.o_list_view th.o_column_sortable').click(); + await testUtils.dom.click(actionManager.$('.o_list_view th.o_column_sortable')); // get to the form view of the model, on the first record - actionManager.$('.o_data_cell:first').click(); + await testUtils.dom.click(actionManager.$('.o_data_cell:first')); // Change model by clicking on the button within the form - actionManager.$('.o_form_view button').click(); + await testUtils.dom.click(actionManager.$('.o_form_view button')); actionManager.destroy(); }); - QUnit.test('breadcrumbs are updated when switching between views', function (assert) { + QUnit.test('breadcrumbs are updated when switching between views', async function (assert) { assert.expect(10); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(3); + await actionManager.doAction(3); assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 1, "there should be one controller in the breadcrumbs"); @@ -2291,28 +2277,28 @@ QUnit.module('ActionManager', { "breadcrumbs should display the display_name of the action"); // switch to kanban view - testUtils.dom.click($('.o_control_panel .o_cp_switch_kanban')); + await testUtils.dom.click($('.o_control_panel .o_cp_switch_kanban')); assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 1, "there should still be one controller in the breadcrumbs"); assert.strictEqual($('.o_control_panel .breadcrumb-item').text(), 'Partners', "breadcrumbs should still display the display_name of the action"); // switch back to list view - testUtils.dom.click($('.o_control_panel .o_cp_switch_list')); + await testUtils.dom.click($('.o_control_panel .o_cp_switch_list')); assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 1, "there should still be one controller in the breadcrumbs"); assert.strictEqual($('.o_control_panel .breadcrumb-item').text(), 'Partners', "breadcrumbs should still display the display_name of the action"); // open a record in form view - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 2, "there should be two controllers in the breadcrumbs"); assert.strictEqual($('.o_control_panel .breadcrumb-item:last').text(), 'First record', "breadcrumbs should contain the display_name of the opened record"); // go back to list view using the breadcrumbs - testUtils.dom.click($('.o_control_panel .breadcrumb a')); + await testUtils.dom.click($('.o_control_panel .breadcrumb a')); assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 1, "there should be one controller in the breadcrumbs"); assert.strictEqual($('.o_control_panel .breadcrumb-item').text(), 'Partners', @@ -2321,15 +2307,15 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('switch buttons are updated when switching between views', function (assert) { + QUnit.test('switch buttons are updated when switching between views', async function (assert) { assert.expect(13); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(3); + await actionManager.doAction(3); assert.strictEqual($('.o_control_panel .o_cp_switch_buttons button').length, 2, "should have two switch buttons (list and kanban)"); @@ -2341,7 +2327,7 @@ QUnit.module('ActionManager', { "list should be the active view"); // switch to kanban view - testUtils.dom.click($('.o_control_panel .o_cp_switch_kanban')); + await testUtils.dom.click($('.o_control_panel .o_cp_switch_kanban')); assert.strictEqual($('.o_control_panel .o_cp_switch_buttons button').length, 2, "should still have two switch buttons (list and kanban)"); assert.strictEqual($('.o_control_panel .o_cp_switch_buttons button.active').length, 1, @@ -2352,19 +2338,19 @@ QUnit.module('ActionManager', { "kanban should now be the active view"); // switch back to list view - testUtils.dom.click($('.o_control_panel .o_cp_switch_list')); + await testUtils.dom.click($('.o_control_panel .o_cp_switch_list')); assert.strictEqual($('.o_control_panel .o_cp_switch_buttons button').length, 2, "should still have two switch buttons (list and kanban)"); assert.hasClass($('.o_control_panel .o_cp_switch_list'),'active', "list should now be the active view"); // open a record in form view - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); assert.strictEqual($('.o_control_panel .o_cp_switch_buttons button').length, 0, "should not have any switch buttons"); // go back to list view using the breadcrumbs - testUtils.dom.click($('.o_control_panel .breadcrumb a')); + await testUtils.dom.click($('.o_control_panel .breadcrumb a')); assert.strictEqual($('.o_control_panel .o_cp_switch_buttons button').length, 2, "should have two switch buttons (list and kanban)"); assert.hasClass($('.o_control_panel .o_cp_switch_list'),'active', @@ -2373,15 +2359,15 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('pager is updated when switching between views', function (assert) { + QUnit.test('pager is updated when switching between views', async function (assert) { assert.expect(10); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(4); + await actionManager.doAction(4); assert.strictEqual($('.o_control_panel .o_pager_value').text(), '1-5', "value should be correct for kanban"); @@ -2389,28 +2375,28 @@ QUnit.module('ActionManager', { "limit should be correct for kanban"); // switch to list view - testUtils.dom.click($('.o_control_panel .o_cp_switch_list')); + await testUtils.dom.click($('.o_control_panel .o_cp_switch_list')); assert.strictEqual($('.o_control_panel .o_pager_value').text(), '1-3', "value should be correct for list"); assert.strictEqual($('.o_control_panel .o_pager_limit').text(), '5', "limit should be correct for list"); // open a record in form view - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); assert.strictEqual($('.o_control_panel .o_pager_value').text(), '1', "value should be correct for form"); assert.strictEqual($('.o_control_panel .o_pager_limit').text(), '3', "limit should be correct for form"); // go back to list view using the breadcrumbs - testUtils.dom.click($('.o_control_panel .breadcrumb a')); + await testUtils.dom.click($('.o_control_panel .breadcrumb a')); assert.strictEqual($('.o_control_panel .o_pager_value').text(), '1-3', "value should be correct for list"); assert.strictEqual($('.o_control_panel .o_pager_limit').text(), '5', "limit should be correct for list"); // switch back to kanban view - testUtils.dom.click($('.o_control_panel .o_cp_switch_kanban')); + await testUtils.dom.click($('.o_control_panel .o_cp_switch_kanban')); assert.strictEqual($('.o_control_panel .o_pager_value').text(), '1-5', "value should be correct for kanban"); assert.strictEqual($('.o_control_panel .o_pager_limit').text(), '5', @@ -2419,50 +2405,52 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('there is no flickering when switching between views', function (assert) { + QUnit.test('there is no flickering when switching between views', async function (assert) { assert.expect(20); var def; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, mockRPC: function () { var result = this._super.apply(this, arguments); - return $.when(def).then(_.constant(result)); + return Promise.resolve(def).then(_.constant(result)); }, }); - actionManager.doAction(3); + await actionManager.doAction(3); // switch to kanban view - def = $.Deferred(); - testUtils.dom.click($('.o_control_panel .o_cp_switch_kanban')); + def = testUtils.makeTestPromise(); + await testUtils.dom.click($('.o_control_panel .o_cp_switch_kanban')); assert.containsOnce(actionManager, '.o_list_view', "should still display the list view"); assert.containsNone(actionManager, '.o_kanban_view', "shouldn't display the kanban view yet"); def.resolve(); + await testUtils.nextTick(); assert.containsNone(actionManager, '.o_list_view', "shouldn't display the list view anymore"); assert.containsOnce(actionManager, '.o_kanban_view', "should now display the kanban view"); // switch back to list view - def = $.Deferred(); - testUtils.dom.click($('.o_control_panel .o_cp_switch_list')); + def = testUtils.makeTestPromise(); + await testUtils.dom.click($('.o_control_panel .o_cp_switch_list')); assert.containsOnce(actionManager, '.o_kanban_view', "should still display the kanban view"); assert.containsNone(actionManager, '.o_list_view', "shouldn't display the list view yet"); def.resolve(); + await testUtils.nextTick(); assert.containsNone(actionManager, '.o_kanban_view', "shouldn't display the kanban view anymore"); assert.containsOnce(actionManager, '.o_list_view', "should now display the list view"); // open a record in form view - def = $.Deferred(); - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + def = testUtils.makeTestPromise(); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); assert.containsOnce(actionManager, '.o_list_view', "should still display the list view"); assert.containsNone(actionManager, '.o_form_view', @@ -2470,6 +2458,7 @@ QUnit.module('ActionManager', { assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 1, "there should still be one controller in the breadcrumbs"); def.resolve(); + await testUtils.nextTick(); assert.containsNone(actionManager, '.o_list_view', "should no longer display the list view"); assert.containsOnce(actionManager, '.o_form_view', @@ -2478,8 +2467,8 @@ QUnit.module('ActionManager', { "there should be two controllers in the breadcrumbs"); // go back to list view using the breadcrumbs - def = $.Deferred(); - testUtils.dom.click($('.o_control_panel .breadcrumb a')); + def = testUtils.makeTestPromise(); + await testUtils.dom.click($('.o_control_panel .breadcrumb a')); assert.containsOnce(actionManager, '.o_form_view', "should still display the form view"); assert.containsNone(actionManager, '.o_list_view', @@ -2487,6 +2476,7 @@ QUnit.module('ActionManager', { assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 2, "there should still be two controllers in the breadcrumbs"); def.resolve(); + await testUtils.nextTick(); assert.containsNone(actionManager, '.o_form_view', "should no longer display the form view"); assert.containsOnce(actionManager, '.o_list_view', @@ -2497,27 +2487,27 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('breadcrumbs are updated when display_name changes', function (assert) { + QUnit.test('breadcrumbs are updated when display_name changes', async function (assert) { assert.expect(4); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(3); + await actionManager.doAction(3); // open a record in form view - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 2, "there should be two controllers in the breadcrumbs"); assert.strictEqual($('.o_control_panel .breadcrumb-item:last').text(), 'First record', "breadcrumbs should contain the display_name of the opened record"); // switch to edit mode and change the display_name - testUtils.dom.click($('.o_control_panel .o_form_button_edit')); - testUtils.fields.editInput(actionManager.$('.o_field_widget[name=display_name]'), 'New name'); - testUtils.dom.click($('.o_control_panel .o_form_button_save')); + await testUtils.dom.click($('.o_control_panel .o_form_button_edit')); + await testUtils.fields.editInput(actionManager.$('.o_field_widget[name=display_name]'), 'New name'); + await testUtils.dom.click($('.o_control_panel .o_form_button_save')); assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 2, "there should still be two controllers in the breadcrumbs"); @@ -2527,10 +2517,10 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('reload previous controller when discarding a new record', function (assert) { + QUnit.test('reload previous controller when discarding a new record', async function (assert) { assert.expect(8); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -2539,15 +2529,15 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.doAction(3); + await actionManager.doAction(3); // create a new record - testUtils.dom.click($('.o_control_panel .o_list_button_add')); + await testUtils.dom.click($('.o_control_panel .o_list_button_add')); assert.containsOnce(actionManager, '.o_form_view.o_form_editable', "should have opened the form view in edit mode"); // discard - testUtils.dom.click($('.o_control_panel .o_form_button_cancel')); + await testUtils.dom.click($('.o_control_panel .o_form_button_cancel')); assert.containsOnce(actionManager, '.o_list_view', "should have switched back to the list view"); @@ -2562,11 +2552,11 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('requests for execute_action of type object are handled', function (assert) { + QUnit.test('requests for execute_action of type object are handled', async function (assert) { assert.expect(10); var self = this; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -2580,7 +2570,7 @@ QUnit.module('ActionManager', { }, "should call route with correct arguments"); var record = _.findWhere(self.data.partner.records, {id: args.args[0][0]}); record.foo = 'value changed'; - return $.when(false); + return Promise.resolve(false); } return this._super.apply(this, arguments); }, @@ -2588,15 +2578,15 @@ QUnit.module('ActionManager', { some_key: 2, }}, }); - actionManager.doAction(3); + await actionManager.doAction(3); // open a record in form view - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); assert.strictEqual(actionManager.$('.o_field_widget[name=foo]').text(), 'yop', "check initial value of 'yop' field"); // click on 'Call method' button (should call an Object method) - testUtils.dom.click(actionManager.$('.o_form_view button:contains(Call method)')); + await testUtils.dom.click(actionManager.$('.o_form_view button:contains(Call method)')); assert.strictEqual(actionManager.$('.o_field_widget[name=foo]').text(), 'value changed', "'yop' has been changed by the server, and should be updated in the UI"); @@ -2612,10 +2602,10 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('requests for execute_action of type action are handled', function (assert) { + QUnit.test('requests for execute_action of type action are handled', async function (assert) { assert.expect(11); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -2624,15 +2614,15 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.doAction(3); + await actionManager.doAction(3); // open a record in form view - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); // click on 'Execute action' button (should execute an action) assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 2, "there should be two parts in the breadcrumbs"); - testUtils.dom.click(actionManager.$('.o_form_view button:contains(Execute action)')); + await testUtils.dom.click(actionManager.$('.o_form_view button:contains(Execute action)')); assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 3, "the returned action should have been stacked over the previous one"); assert.containsOnce(actionManager, '.o_kanban_view', @@ -2651,42 +2641,42 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('requests for execute_action of type object: disable buttons', function (assert) { + QUnit.test('requests for execute_action of type object: disable buttons', async function (assert) { assert.expect(2); - var self = this; var def; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, mockRPC: function (route, args) { if (route === '/web/dataset/call_button') { - return $.when(false); + return Promise.resolve(false); } else if (args.method === 'read') { // Block the 'read' call var result = this._super.apply(this, arguments); - return $.when(def).then(_.constant(result)); + return Promise.resolve(def).then(_.constant(result)); } return this._super.apply(this, arguments); }, }); - actionManager.doAction(3); + await actionManager.doAction(3); // open a record in form view - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); // click on 'Call method' button (should call an Object method) - def = $.Deferred(); - testUtils.dom.click(actionManager.$('.o_form_view button:contains(Call method)')); + def = testUtils.makeTestPromise(); + await testUtils.dom.click(actionManager.$('.o_form_view button:contains(Call method)')); // Buttons should be disabled assert.strictEqual( actionManager.$('.o_form_view button:contains(Call method)').attr('disabled'), - 'disabled', 'buttons should be disabled') + 'disabled', 'buttons should be disabled'); // Release the 'read' call def.resolve(); + await testUtils.nextTick(); // Buttons should be enabled after the reload assert.strictEqual( @@ -2696,10 +2686,10 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('can open different records from a multi record view', function (assert) { + QUnit.test('can open different records from a multi record view', async function (assert) { assert.expect(11); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -2708,20 +2698,20 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.doAction(3); + await actionManager.doAction(3); // open the first record in form view - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); assert.strictEqual($('.o_control_panel .breadcrumb-item:last').text(), 'First record', "breadcrumbs should contain the display_name of the opened record"); assert.strictEqual(actionManager.$('.o_field_widget[name=foo]').text(), 'yop', "should have opened the correct record"); // go back to list view using the breadcrumbs - testUtils.dom.click($('.o_control_panel .breadcrumb a')); + await testUtils.dom.click($('.o_control_panel .breadcrumb a')); // open the second record in form view - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:nth(1)')); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:nth(1)')); assert.strictEqual($('.o_control_panel .breadcrumb-item:last').text(), 'Second record', "breadcrumbs should contain the display_name of the opened record"); assert.strictEqual(actionManager.$('.o_field_widget[name=foo]').text(), 'blip', @@ -2739,18 +2729,18 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('restore previous view state when switching back', function (assert) { + QUnit.test('restore previous view state when switching back', async function (assert) { assert.expect(5); this.actions[2].views.unshift([false, 'graph']); this.archs['partner,false,graph'] = '<graph></graph>'; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(3); + await actionManager.doAction(3); assert.hasClass($('.o_control_panel .fa-bar-chart-o'),'active', "bar chart button is active"); @@ -2758,22 +2748,22 @@ QUnit.module('ActionManager', { "line chart button is not active"); // display line chart - testUtils.dom.click($('.o_control_panel .fa-area-chart')); + await testUtils.dom.click($('.o_control_panel .fa-area-chart')); assert.hasClass($('.o_control_panel .fa-area-chart'),'active', "line chart button is now active"); // switch to kanban and back to graph view - testUtils.dom.click($('.o_control_panel .o_cp_switch_kanban')); + await testUtils.dom.click($('.o_control_panel .o_cp_switch_kanban')); assert.strictEqual($('.o_control_panel .fa-area-chart').length, 0, "graph buttons are no longer in control panel"); - testUtils.dom.click($('.o_control_panel .o_cp_switch_graph')); + await testUtils.dom.click($('.o_control_panel .o_cp_switch_graph')); assert.hasClass($('.o_control_panel .fa-area-chart'),'active', "line chart button is still active"); actionManager.destroy(); }); - QUnit.test('view switcher is properly highlighted in graph view', function (assert) { + QUnit.test('view switcher is properly highlighted in graph view', async function (assert) { assert.expect(4); // note: this test should be moved to graph tests ? @@ -2781,12 +2771,12 @@ QUnit.module('ActionManager', { this.actions[2].views.splice(1, 1, [false, 'graph']); this.archs['partner,false,graph'] = '<graph></graph>'; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(3); + await actionManager.doAction(3); assert.hasClass($('.o_control_panel .o_cp_switch_list'),'active', "list button in control panel is active"); @@ -2794,7 +2784,7 @@ QUnit.module('ActionManager', { "graph button in control panel is not active"); // switch to graph view - testUtils.dom.click($('.o_control_panel .o_cp_switch_graph')); + await testUtils.dom.click($('.o_control_panel .o_cp_switch_graph')); assert.doesNotHaveClass($('.o_control_panel .o_cp_switch_list'), 'active', "list button in control panel is not active"); assert.hasClass($('.o_control_panel .o_cp_switch_graph'),'active', @@ -2802,7 +2792,7 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('can interact with search view', function (assert) { + QUnit.test('can interact with search view', async function (assert) { assert.expect(2); this.archs['partner,false,search'] = '<search>'+ @@ -2810,21 +2800,21 @@ QUnit.module('ActionManager', { '<filter name="foo" string="foo" context="{\'group_by\': \'foo\'}"/>' + '</group>'+ '</search>'; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(3); + await actionManager.doAction(3); assert.doesNotHaveClass(actionManager.$('.o_list_view'), 'o_list_view_grouped', "list view is not grouped"); // open group by dropdown - testUtils.dom.click($('.o_control_panel .o_cp_right button:contains(Group By)')); + await testUtils.dom.click($('.o_control_panel .o_cp_right button:contains(Group By)')); // click on first link - testUtils.dom.click($('.o_control_panel .o_group_by_menu a:first')); + await testUtils.dom.click($('.o_control_panel .o_group_by_menu a:first')); assert.hasClass(actionManager.$('.o_list_view'),'o_list_view_grouped', 'list view is now grouped'); @@ -2832,7 +2822,7 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('can open a many2one external window', function (assert) { + QUnit.test('can open a many2one external window', async function (assert) { // AAB: this test could be merged with 'many2ones in form views' in relational_fields_tests.js assert.expect(8); @@ -2849,27 +2839,27 @@ QUnit.module('ActionManager', { '</group>' + '</form>'; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, mockRPC: function (route, args) { assert.step(route); if (args.method === "get_formview_id") { - return $.when(false); + return Promise.resolve(false); } return this._super.apply(this, arguments); }, }); - actionManager.doAction(3); + await actionManager.doAction(3); // open first record in form view - testUtils.dom.click(actionManager.$('.o_data_row:first')); + await testUtils.dom.click(actionManager.$('.o_data_row:first')); // click on edit - testUtils.dom.click($('.o_control_panel .o_form_button_edit')); + await testUtils.dom.click($('.o_control_panel .o_form_button_edit')); // click on external button for m2o - testUtils.dom.click(actionManager.$('.o_external_button')); + await testUtils.dom.click(actionManager.$('.o_external_button')); assert.verifySteps([ '/web/action/load', // initial load action '/web/dataset/call_kw/partner', // load views @@ -2882,41 +2872,41 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('ask for confirmation when leaving a "dirty" view', function (assert) { + QUnit.test('ask for confirmation when leaving a "dirty" view', async function (assert) { assert.expect(4); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(4); + await actionManager.doAction(4); // open record in form view - testUtils.dom.click(actionManager.$('.o_kanban_record:first')); + await testUtils.dom.click(actionManager.$('.o_kanban_record:first')); // edit record - testUtils.dom.click($('.o_control_panel button.o_form_button_edit')); - testUtils.fields.editInput(actionManager.$('input[name="foo"]'), 'pinkypie'); + await testUtils.dom.click($('.o_control_panel button.o_form_button_edit')); + await testUtils.fields.editInput(actionManager.$('input[name="foo"]'), 'pinkypie'); // go back to kanban view - testUtils.dom.click($('.o_control_panel .breadcrumb-item:first a')); + await testUtils.dom.click($('.o_control_panel .breadcrumb-item:first a')); assert.strictEqual($('.modal .modal-body').text(), "The record has been modified, your changes will be discarded. Do you want to proceed?", "should display a modal dialog to confirm discard action"); // cancel - testUtils.dom.click($('.modal .modal-footer button.btn-secondary')); + await testUtils.dom.click($('.modal .modal-footer button.btn-secondary')); assert.containsOnce(actionManager, '.o_form_view', "should still be in form view"); // go back again to kanban view - testUtils.dom.click($('.o_control_panel .breadcrumb-item:first a')); + await testUtils.dom.click($('.o_control_panel .breadcrumb-item:first a')); // confirm discard - testUtils.dom.click($('.modal .modal-footer button.btn-primary')); + await testUtils.dom.click($('.modal .modal-footer button.btn-primary')); assert.containsNone(actionManager, '.o_form_view', "should no longer be in form view"); @@ -2926,22 +2916,22 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('limit set in action is passed to each created controller', function (assert) { + QUnit.test('limit set in action is passed to each created controller', async function (assert) { assert.expect(2); _.findWhere(this.actions, {id: 3}).limit = 2; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(3); + await actionManager.doAction(3); assert.containsN(actionManager, '.o_data_row', 2, "should only display 2 record"); // switch to kanban view - testUtils.dom.click($('.o_control_panel .o_cp_switch_kanban')); + await testUtils.dom.click($('.o_control_panel .o_cp_switch_kanban')); assert.strictEqual(actionManager.$('.o_kanban_record:not(.o_kanban_ghost)').length, 2, "should only display 2 record"); @@ -2949,44 +2939,44 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('go back to a previous action using the breadcrumbs', function (assert) { + QUnit.test('go back to a previous action using the breadcrumbs', async function (assert) { assert.expect(10); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(3); + await actionManager.doAction(3); // open a record in form view - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 2, "there should be two controllers in the breadcrumbs"); assert.strictEqual($('.o_control_panel .breadcrumb-item:last').text(), 'First record', "breadcrumbs should contain the display_name of the opened record"); // push another action on top of the first one, and come back to the form view - actionManager.doAction(4); + await actionManager.doAction(4); assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 3, "there should be three controllers in the breadcrumbs"); assert.strictEqual($('.o_control_panel .breadcrumb-item:last').text(), 'Partners Action 4', "breadcrumbs should contain the name of the current action"); // go back using the breadcrumbs - testUtils.dom.click($('.o_control_panel .breadcrumb a:nth(1)')); + await testUtils.dom.click($('.o_control_panel .breadcrumb a:nth(1)')); assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 2, "there should be two controllers in the breadcrumbs"); assert.strictEqual($('.o_control_panel .breadcrumb-item:last').text(), 'First record', "breadcrumbs should contain the display_name of the opened record"); // push again the other action on top of the first one, and come back to the list view - actionManager.doAction(4); + await actionManager.doAction(4); assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 3, "there should be three controllers in the breadcrumbs"); assert.strictEqual($('.o_control_panel .breadcrumb-item:last').text(), 'Partners Action 4', "breadcrumbs should contain the name of the current action"); // go back using the breadcrumbs - testUtils.dom.click($('.o_control_panel .breadcrumb a:first')); + await testUtils.dom.click($('.o_control_panel .breadcrumb a:first')); assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 1, "there should be one controller in the breadcrumbs"); assert.strictEqual($('.o_control_panel .breadcrumb-item:last').text(), 'Partners', @@ -2995,32 +2985,33 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('form views are restored in readonly when coming back in breadcrumbs', function (assert) { + QUnit.test('form views are restored in readonly when coming back in breadcrumbs', async function (assert) { assert.expect(2); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(3); + await actionManager.doAction(3); // open a record in form view - actionManager.$('.o_list_view .o_data_row:first').click(); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); // switch to edit mode - $('.o_control_panel .o_form_button_edit').click(); + await testUtils.dom.click($('.o_control_panel .o_form_button_edit')); - assert.ok(actionManager.$('.o_form_view').hasClass('o_form_editable')); + assert.hasClass(actionManager.$('.o_form_view'), 'o_form_editable'); // do some other action - actionManager.doAction(4); + await actionManager.doAction(4); // go back to form view - $('.o_control_panel .breadcrumb a').last().click(); - assert.ok(actionManager.$('.o_form_view').hasClass('o_form_readonly')); + await testUtils.dom.clickLast($('.o_control_panel .breadcrumb a')); + await testUtils.nextTick(); + assert.hasClass(actionManager.$('.o_form_view'), 'o_form_readonly'); actionManager.destroy(); }); - QUnit.test('honor group_by specified in actions context', function (assert) { + QUnit.test('honor group_by specified in actions context', async function (assert) { assert.expect(5); _.findWhere(this.actions, {id: 3}).context = "{'group_by': 'bar'}"; @@ -3030,12 +3021,12 @@ QUnit.module('ActionManager', { '</group>'+ '</search>'; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(3); + await actionManager.doAction(3); assert.containsOnce(actionManager, '.o_list_view_grouped', "should be grouped"); @@ -3043,14 +3034,14 @@ QUnit.module('ActionManager', { "should be grouped by 'bar' (two groups) at first load"); // groupby 'bar' using the searchview - testUtils.dom.click($('.o_control_panel .o_cp_right button:contains(Group By)')); - testUtils.dom.click($('.o_control_panel .o_group_by_menu a:first')); + await testUtils.dom.click($('.o_control_panel .o_cp_right button:contains(Group By)')); + await testUtils.dom.click($('.o_control_panel .o_group_by_menu a:first')); assert.containsN(actionManager, '.o_group_header', 5, "should be grouped by 'foo' (five groups)"); // remove the groupby in the searchview - testUtils.dom.click($('.o_control_panel .o_searchview .o_facet_remove')); + await testUtils.dom.click($('.o_control_panel .o_searchview .o_facet_remove')); assert.containsOnce(actionManager, '.o_list_view_grouped', "should still be grouped"); @@ -3060,7 +3051,7 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('switch request to unknown view type', function (assert) { + QUnit.test('switch request to unknown view type', async function (assert) { assert.expect(7); this.actions.push({ @@ -3071,7 +3062,7 @@ QUnit.module('ActionManager', { views: [[false, 'list'], [1, 'kanban']], // no form view }); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -3080,7 +3071,7 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.doAction(33); + await actionManager.doAction(33); assert.containsOnce(actionManager, '.o_list_view', "should display the list view"); @@ -3101,7 +3092,7 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('save current search', function (assert) { + QUnit.test('save current search', async function (assert) { assert.expect(4); testUtils.mock.patch(ListController, { @@ -3126,7 +3117,7 @@ QUnit.module('ActionManager', { views: [[false, 'list']], }); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -3140,59 +3131,59 @@ QUnit.module('ActionManager', { }, }, }); - actionManager.doAction(33); + await actionManager.doAction(33); assert.containsN(actionManager, '.o_data_row', 5, "should contain 5 records"); // filter on bar - testUtils.dom.click($('.o_control_panel .o_search_options .o_dropdown_toggler_btn:contains(Filters)')); - testUtils.dom.click($('.o_control_panel .o_filters_menu a:contains(Bar)')); + await testUtils.dom.click($('.o_control_panel .o_search_options .o_dropdown_toggler_btn:contains(Filters)')); + await testUtils.dom.click($('.o_control_panel .o_filters_menu a:contains(Bar)')); assert.containsN(actionManager, '.o_data_row', 2); // save filter - testUtils.dom.click($('.o_control_panel .o_search_options .o_dropdown_toggler_btn:contains(Favorites)')); - testUtils.dom.click($('.o_control_panel .o_add_favorite')); + await testUtils.dom.click($('.o_control_panel .o_search_options .o_dropdown_toggler_btn:contains(Favorites)')); + await testUtils.dom.click($('.o_control_panel .o_add_favorite')); $('.o_control_panel .o_favorite_name input[type=text]').val('some name'); // name the filter - testUtils.dom.click($('.o_control_panel .o_save_favorite button')); + await testUtils.dom.click($('.o_control_panel .o_save_favorite button')); testUtils.mock.unpatch(ListController); actionManager.destroy(); }); - QUnit.test("search menus are still available when switching between actions", function (assert) { + QUnit.test("search menus are still available when switching between actions", async function (assert) { assert.expect(3); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(1); + await actionManager.doAction(1); assert.strictEqual($('.o_search_options .o_dropdown:visible .o_filters_menu').length, 1, "the search options should be available"); - actionManager.doAction(3); + await actionManager.doAction(3); assert.strictEqual($('.o_search_options .o_dropdown:visible .o_filters_menu').length, 1, "the search options should be available"); // go back using the breadcrumbs - testUtils.dom.click($('.o_control_panel .breadcrumb a:first')); + await testUtils.dom.click($('.o_control_panel .breadcrumb a:first')); assert.strictEqual($('.o_search_options .o_dropdown:visible .o_filters_menu').length, 1, "the search options should be available"); actionManager.destroy(); }); - QUnit.test("current act_window action is stored in session_storage", function (assert) { + QUnit.test("current act_window action is stored in session_storage", async function (assert) { assert.expect(1); var expectedAction = _.extend({}, _.findWhere(this.actions, {id: 3}), { context: {}, }); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -3206,12 +3197,12 @@ QUnit.module('ActionManager', { }, }); - actionManager.doAction(3); + await actionManager.doAction(3); actionManager.destroy(); }); - QUnit.test("store evaluated context of current action in session_storage", function (assert) { + QUnit.test("store evaluated context of current action in session_storage", async function (assert) { // this test ensures that we don't store stringified instances of // CompoundContext in the session_storage, as they would be meaningless // once restored @@ -3225,7 +3216,7 @@ QUnit.module('ActionManager', { }, }); var checkSessionStorage = false; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -3242,22 +3233,50 @@ QUnit.module('ActionManager', { }); // execute an action and open a record in form view - actionManager.doAction(3); - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + await actionManager.doAction(3); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); // click on 'Execute action' button (it executes an action with a CompoundContext as context) checkSessionStorage = true; - testUtils.dom.click(actionManager.$('.o_form_view button:contains(Execute action)')); + await testUtils.dom.click(actionManager.$('.o_form_view button:contains(Execute action)')); + + actionManager.destroy(); + }); + + QUnit.test("destroy action with lazy loaded controller", async function (assert) { + assert.expect(6); + + var actionManager = await createActionManager({ + actions: this.actions, + archs: this.archs, + data: this.data, + }); + await actionManager.loadState({ + action: 3, + id: 2, + view_type: 'form', + }); + assert.containsNone(actionManager, '.o_list_view'); + assert.containsOnce(actionManager, '.o_form_view'); + assert.strictEqual($('.o_control_panel .breadcrumb-item').length, 2, + "there should be two controllers in the breadcrumbs"); + assert.strictEqual($('.o_control_panel .breadcrumb-item:last').text(), 'Second record', + "breadcrumbs should contain the display_name of the opened record"); + + await actionManager.doAction(1, {clear_breadcrumbs: true}); + + assert.containsNone(actionManager, '.o_form_view'); + assert.containsOnce(actionManager, '.o_kanban_view'); actionManager.destroy(); }); QUnit.module('Actions in target="new"'); - QUnit.test('can execute act_window actions in target="new"', function (assert) { + QUnit.test('can execute act_window actions in target="new"', async function (assert) { assert.expect(7); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -3266,7 +3285,7 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.doAction(5); + await actionManager.doAction(5); assert.strictEqual($('.o_technical_modal .o_form_view').length, 1, "should have rendered a form view in a modal"); @@ -3284,32 +3303,32 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('chained action on_close', function (assert) { + QUnit.test('chained action on_close', async function (assert) { assert.expect(3); function on_close() { assert.step('Close Action'); }; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(5, {on_close: on_close}); + await actionManager.doAction(5, {on_close: on_close}); // a target=new action shouldn't activate the on_close - actionManager.doAction(5); + await actionManager.doAction(5); assert.verifySteps([]); // An act_window_close should trigger the on_close - actionManager.doAction(10); + await actionManager.doAction(10); assert.verifySteps(['Close Action']); actionManager.destroy(); }); - QUnit.test('footer buttons are moved to the dialog footer', function (assert) { + QUnit.test('footer buttons are moved to the dialog footer', async function (assert) { assert.expect(3); this.archs['partner,false,form'] = '<form>' + @@ -3319,12 +3338,12 @@ QUnit.module('ActionManager', { '</footer>' + '</form>'; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(5); + await actionManager.doAction(5); assert.strictEqual($('.o_technical_modal .modal-body button.infooter').length, 0, "the button should not be in the body"); @@ -3336,7 +3355,7 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('on_attach_callback is called for actions in target="new"', function (assert) { + QUnit.test('on_attach_callback is called for actions in target="new"', async function (assert) { assert.expect(4); var ClientAction = AbstractAction.extend({ @@ -3351,12 +3370,12 @@ QUnit.module('ActionManager', { }); core.action_registry.add('test', ClientAction); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction({ + await actionManager.doAction({ tag: 'test', target: 'new', type: 'ir.actions.client', @@ -3372,10 +3391,10 @@ QUnit.module('ActionManager', { QUnit.module('Actions in target="inline"'); - QUnit.test('form views for actions in target="inline" open in edit mode', function (assert) { + QUnit.test('form views for actions in target="inline" open in edit mode', async function (assert) { assert.expect(5); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -3384,7 +3403,7 @@ QUnit.module('ActionManager', { return this._super.apply(this, arguments); }, }); - actionManager.doAction(6); + await actionManager.doAction(6); assert.containsOnce(actionManager, '.o_form_view.o_form_editable', "should have rendered a form view in edit mode"); @@ -3400,11 +3419,11 @@ QUnit.module('ActionManager', { QUnit.module('Actions in target="fullscreen"'); - QUnit.test('correctly execute act_window actions in target="fullscreen"', function (assert) { + QUnit.test('correctly execute act_window actions in target="fullscreen"', async function (assert) { assert.expect(7); this.actions[0].target = 'fullscreen'; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -3418,7 +3437,7 @@ QUnit.module('ActionManager', { }, }, }); - actionManager.doAction(1); + await actionManager.doAction(1); assert.strictEqual($('.o_control_panel').length, 1, "should have rendered a control panel"); @@ -3434,7 +3453,7 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('fullscreen on action change: back to a "current" action', function (assert) { + QUnit.test('fullscreen on action change: back to a "current" action', async function (assert) { assert.expect(3); this.actions[0].target = 'fullscreen'; @@ -3442,7 +3461,7 @@ QUnit.module('ActionManager', { '<button name="1" type="action" class="oe_stat_button" />' + '</form>'; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -3467,18 +3486,18 @@ QUnit.module('ActionManager', { }); var toggleFullscreenCalls = 0; - actionManager.doAction(6); + await actionManager.doAction(6); toggleFullscreenCalls = 1; - actionManager.$('button[name=1]').click(); + await testUtils.dom.click(actionManager.$('button[name=1]')); toggleFullscreenCalls = 2; - $('.breadcrumb li a:first').click(); + await testUtils.dom.click(actionManager.$('.breadcrumb li a:first')); actionManager.destroy(); }); - QUnit.test('fullscreen on action change: all "fullscreen" actions', function (assert) { + QUnit.test('fullscreen on action change: all "fullscreen" actions', async function (assert) { assert.expect(3); this.actions[5].target = 'fullscreen'; @@ -3486,7 +3505,7 @@ QUnit.module('ActionManager', { '<button name="1" type="action" class="oe_stat_button" />' + '</form>'; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -3498,33 +3517,33 @@ QUnit.module('ActionManager', { }, }); - actionManager.doAction(6); + await actionManager.doAction(6); - actionManager.$('button[name=1]').click(); + await testUtils.dom.click(actionManager.$('button[name=1]')); - $('.breadcrumb li a:first').click(); + await testUtils.dom.click(actionManager.$('.breadcrumb li a:first')); actionManager.destroy(); }); QUnit.module('"ir.actions.act_window_close" actions'); - QUnit.test('close the currently opened dialog', function (assert) { + QUnit.test('close the currently opened dialog', async function (assert) { assert.expect(2); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); // execute an action in target="new" - actionManager.doAction(5); + await actionManager.doAction(5); assert.strictEqual($('.o_technical_modal .o_form_view').length, 1, "should have rendered a form view in a modal"); // execute an 'ir.actions.act_window_close' action - actionManager.doAction({ + await actionManager.doAction({ type: 'ir.actions.act_window_close', }); assert.strictEqual($('.o_technical_modal').length, 0, @@ -3533,37 +3552,37 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('execute "on_close" only if there is no dialog to close', function (assert) { + QUnit.test('execute "on_close" only if there is no dialog to close', async function (assert) { assert.expect(3); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); // execute an action in target="new" - actionManager.doAction(5); + await actionManager.doAction(5); var options = { on_close: assert.step.bind(assert, 'on_close'), }; // execute an 'ir.actions.act_window_close' action // should not call 'on_close' as there is a dialog to close - actionManager.doAction({type: 'ir.actions.act_window_close'}, options); + await actionManager.doAction({type: 'ir.actions.act_window_close'}, options); assert.verifySteps([]); // execute again an 'ir.actions.act_window_close' action // should call 'on_close' as there is no dialog to close - actionManager.doAction({type: 'ir.actions.act_window_close'}, options); + await actionManager.doAction({type: 'ir.actions.act_window_close'}, options); assert.verifySteps(['on_close']); actionManager.destroy(); }); - QUnit.test('doAction resolved with an action', function (assert) { + QUnit.test('doAction resolved with an action', async function (assert) { assert.expect(4); this.actions.push({ @@ -3572,13 +3591,13 @@ QUnit.module('ActionManager', { type: 'ir.actions.act_window_close', }); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(21).then(function (action) { + await actionManager.doAction(21).then(function (action) { assert.ok(action, "doAction should be resolved with an action"); assert.strictEqual(action.id, 21, "should be resolved with correct action id"); @@ -3590,10 +3609,10 @@ QUnit.module('ActionManager', { }); }); - QUnit.test('close action with provided infos', function (assert) { + QUnit.test('close action with provided infos', async function (assert) { assert.expect(1); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -3606,7 +3625,7 @@ QUnit.module('ActionManager', { } }; - actionManager.doAction({ + await actionManager.doAction({ type: 'ir.actions.act_window_close', infos: 'just for testing', }, options); @@ -3614,17 +3633,17 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('history back calls on_close handler of dialog action', function (assert) { + QUnit.test('history back calls on_close handler of dialog action', async function (assert) { assert.expect(2); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); // open a new dialog form - actionManager.doAction(this.actions[4], { + await actionManager.doAction(this.actions[4], { on_close: function () { assert.step('on_close'); }, @@ -3636,10 +3655,10 @@ QUnit.module('ActionManager', { actionManager.destroy(); }); - QUnit.test('properly drop client actions after new action is initiated', function (assert) { + QUnit.test('properly drop client actions after new action is initiated', async function (assert) { assert.expect(1); - var slowWillStartDef = $.Deferred(); + var slowWillStartDef = testUtils.makeTestPromise(); var ClientAction = AbstractAction.extend({ willStart: function () { @@ -3649,7 +3668,7 @@ QUnit.module('ActionManager', { core.action_registry.add('slowAction', ClientAction); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -3657,6 +3676,7 @@ QUnit.module('ActionManager', { actionManager.doAction('slowAction'); actionManager.doAction(4); slowWillStartDef.resolve(); + await testUtils.nextTick(); assert.containsOnce(actionManager, '.o_kanban_view', 'should have loaded a kanban view'); @@ -3665,17 +3685,17 @@ QUnit.module('ActionManager', { }); - QUnit.test('abstract action does not crash on navigation_moves', function (assert) { + QUnit.test('abstract action does not crash on navigation_moves', async function (assert) { assert.expect(1); var ClientAction = AbstractAction.extend({ }); core.action_registry.add('ClientAction', ClientAction); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction('ClientAction'); + await actionManager.doAction('ClientAction'); actionManager.trigger_up('navigation_move', {direction:'down'}); assert.ok(true); // no error so it's good @@ -3683,10 +3703,8 @@ QUnit.module('ActionManager', { delete core.action_registry.ClientAction; }); - QUnit.test('fields in abstract action does not crash on navigation_moves', function (assert) { + QUnit.test('fields in abstract action does not crash on navigation_moves', async function (assert) { assert.expect(1); - var self = this; - // create a client action with 2 input field var inputWidget; var secondInputWidget; @@ -3716,12 +3734,12 @@ QUnit.module('ActionManager', { } }); core.action_registry.add('ClientAction', ClientAction); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction('ClientAction'); + await actionManager.doAction('ClientAction'); inputWidget.$el[0].focus(); var event = $.Event('keydown', { which: $.ui.keyCode.TAB, @@ -3735,12 +3753,12 @@ QUnit.module('ActionManager', { delete core.action_registry.ClientAction; }); - QUnit.test('web client is not deadlocked when a view crashes', function (assert) { + QUnit.test('web client is not deadlocked when a view crashes', async function (assert) { assert.expect(3); - var readOnFirstRecordDef = $.Deferred(); + var readOnFirstRecordDef = testUtils.makeTestPromise(); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -3752,11 +3770,11 @@ QUnit.module('ActionManager', { } }); - actionManager.doAction(3); + await actionManager.doAction(3); // open first record in form view. this will crash and will not // display a form view - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:first')); readOnFirstRecordDef.reject("not working as intended"); @@ -3764,7 +3782,7 @@ QUnit.module('ActionManager', { "there should still be a list view in dom"); // open another record, the read will not crash - testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:eq(2)')); + await testUtils.dom.click(actionManager.$('.o_list_view .o_data_row:eq(2)')); assert.containsNone(actionManager, '.o_list_view', "there should not be a list view in dom"); @@ -3777,7 +3795,7 @@ QUnit.module('ActionManager', { QUnit.module('Search View Action'); - QUnit.test('search view should keep focus during do_search', function (assert) { + QUnit.test('search view should keep focus during do_search', async function (assert) { assert.expect(5); /* One should be able to type something in the search view, press on enter to @@ -3787,48 +3805,46 @@ QUnit.module('ActionManager', { * because of how native events are handled in tests */ - var searchDeferred = $.Deferred(); + var searchPromise = testUtils.makeTestPromise(); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, mockRPC: function (route, args) { if (route === '/web/dataset/search_read') { assert.step('search_read ' + args.domain); - if ( _.isEqual(args.domain, [['foo', 'ilike', 'm']])) { - return searchDeferred.then(this._super.bind(this, route, args)); + if (_.isEqual(args.domain, [['foo', 'ilike', 'm']])) { + return searchPromise.then(this._super.bind(this, route, args)); } } return this._super.apply(this, arguments); - } + }, }); - actionManager.doAction(3); + await actionManager.doAction(3); var $searchInput = $('.o_searchview input'); - $searchInput.val('m'); - $searchInput.trigger($.Event('keypress', {key: 'm', which: 109, keyCode: 109})); - $searchInput.trigger($.Event('keydown', {key: 'Enter', which: 13, keyCode: 13})); + $searchInput.val('m'); // the searchbar checks the contents of the input on keypress events + await testUtils.fields.triggerKey('press', $searchInput, 'm'); + await testUtils.fields.triggerKeydown($searchInput, 'enter'); assert.verifySteps(["search_read ", "search_read foo,ilike,m"]); // Triggering the do_search above will kill the current searchview Input $searchInput = $('.o_searchview input'); - $searchInput.val('o'); - $searchInput.trigger($.Event('keypress', {key: 'o', which: 111, keyCode: 111})); + $searchInput.val('o'); // the searchbar checks the contents of the input on keypress events + await testUtils.fields.triggerKey('press', $searchInput, 'o'); // We have something in the input of the search view. Making the search_read // return at this point will trigger the redraw of the view. // However we want to hold on to what we just typed - searchDeferred.resolve(); + searchPromise.resolve(); + await testUtils.nextTick(); + await testUtils.fields.triggerKeydown($searchInput, 'enter'); - $searchInput.trigger($.Event('keydown', {key: 'Enter', which: 13, keyCode: 13})); - - assert.verifySteps(["search_read ", - "search_read foo,ilike,m", - "search_read |,foo,ilike,m,foo,ilike,o"]); + assert.verifySteps(["search_read |,foo,ilike,m,foo,ilike,o"]); actionManager.destroy(); }); diff --git a/addons/web/static/tests/chrome/keyboard_navigation_mixin_tests.js b/addons/web/static/tests/chrome/keyboard_navigation_mixin_tests.js index e26451cd268..da417deddf5 100644 --- a/addons/web/static/tests/chrome/keyboard_navigation_mixin_tests.js +++ b/addons/web/static/tests/chrome/keyboard_navigation_mixin_tests.js @@ -6,10 +6,14 @@ var testUtils = require('web.test_utils'); var Widget = require('web.Widget'); QUnit.module('KeyboardNavigationMixin', function () { - QUnit.test('aria-keyshortcuts is added on elements with accesskey', function (assert) { + QUnit.test('aria-keyshortcuts is added on elements with accesskey', async function (assert) { assert.expect(1); var $target = $('#qunit-fixture'); var KeyboardWidget = Widget.extend(KeyboardNavigationMixin, { + init: function () { + this._super.apply(this, arguments); + KeyboardNavigationMixin.init.call(this); + }, start: function () { var $button = $('<button>').text('Click Me!').attr('accesskey', 'o'); // we need to define the accesskey because it will not be assigned on invisible buttons @@ -19,7 +23,7 @@ QUnit.module('KeyboardNavigationMixin', function () { }); var parent = testUtils.createParent({}); var w = new KeyboardWidget(parent); - w.appendTo($target); + await w.appendTo($target); // minimum set of attribute to generate a native event that works with the mixin var e = new Event("keydown"); @@ -32,7 +36,7 @@ QUnit.module('KeyboardNavigationMixin', function () { parent.destroy(); }); - QUnit.test('keep CSS position absolute for parent of overlay', function (assert) { + QUnit.test('keep CSS position absolute for parent of overlay', async function (assert) { // If we change the CSS position of an 'absolute' element to 'relative', // we may likely change its position on the document. Since the overlay // CSS position is 'absolute', it will match the size and cover the @@ -42,6 +46,10 @@ QUnit.module('KeyboardNavigationMixin', function () { var $target = $('#qunit-fixture'); var $button; var KeyboardWidget = Widget.extend(KeyboardNavigationMixin, { + init: function () { + this._super.apply(this, arguments); + KeyboardNavigationMixin.init.call(this); + }, start: function () { $button = $('<button>').text('Click Me!').attr('accesskey', 'o'); // we need to define the accesskey because it will not be assigned on invisible buttons @@ -51,7 +59,7 @@ QUnit.module('KeyboardNavigationMixin', function () { }); var parent = testUtils.createParent({}); var w = new KeyboardWidget(parent); - w.appendTo($target); + await w.appendTo($target); $button.css('position', 'absolute'); diff --git a/addons/web/static/tests/chrome/pager_tests.js b/addons/web/static/tests/chrome/pager_tests.js index e089929abbc..18a4ae7fc37 100644 --- a/addons/web/static/tests/chrome/pager_tests.js +++ b/addons/web/static/tests/chrome/pager_tests.js @@ -9,40 +9,41 @@ QUnit.module('chrome', {}, function () { QUnit.module('Pager'); - QUnit.test('basic stuff', function (assert) { + QUnit.test('basic stuff', async function (assert) { assert.expect(2); var pager = new Pager(null, 10, 1, 4); pager.appendTo($('#qunit-fixture')); + await testUtils.nextTick(); assert.strictEqual(pager.state.current_min, 1, "current_min should be set to 1"); // click on next - testUtils.dom.click(pager.$('.o_pager_next')); + await testUtils.dom.click(pager.$('.o_pager_next')); assert.strictEqual(pager.state.current_min, 5, "current_min should now be 5"); pager.destroy(); }); - QUnit.test('edit the pager', function (assert) { + QUnit.test('edit the pager', async function (assert) { assert.expect(5); var pager = new Pager(null, 10, 1, 4); pager.appendTo($('#qunit-fixture')); + await testUtils.nextTick(); // enter edition - testUtils.dom.click(pager.$('.o_pager_value')); + await testUtils.dom.click(pager.$('.o_pager_value')); assert.containsOnce(pager, 'input', "the pager should contain an input"); assert.strictEqual(pager.$('input').val(), '1-4', "the input should have correct value"); // change the limit - pager.$('input') - .val('1-6') - .trigger($.Event('keydown', {which: $.ui.keyCode.ENTER})); // trigger the change + await testUtils.fields.editInput(pager.$('input'), '1-6'); + await testUtils.fields.triggerKeydown(pager.$('input'), 'enter'); assert.strictEqual(pager.state.limit, 6, "the limit should have been updated"); assert.strictEqual(pager.state.current_min, 1, @@ -53,43 +54,40 @@ QUnit.module('chrome', {}, function () { pager.destroy(); }); - QUnit.test('disabling the pager', function (assert) { - var done = assert.async(); + QUnit.test('disabling the pager', async function (assert) { assert.expect(4); var pager = new Pager(null, 10, 1, 4); pager.appendTo($('#qunit-fixture')); - - pager.disable(); + await testUtils.nextTick(); + await pager.disable(); // try to go to the next or previous pages - testUtils.dom.click(pager.$('.o_pager_next')); + await testUtils.dom.click(pager.$('.o_pager_next')); assert.strictEqual(pager.state.current_min, 1, "current_min should still be 1"); - testUtils.dom.click(pager.$('.o_pager_previous')); + await testUtils.dom.click(pager.$('.o_pager_previous')); assert.strictEqual(pager.state.current_min, 1, "current_min should still be 1"); // try to change the limit - testUtils.dom.click(pager.$('.o_pager_value')); + await testUtils.dom.click(pager.$('.o_pager_value')); assert.containsNone(pager, 'input', "the pager should not contain an input"); // a common use is to disable the pager before reloading the data, and // re-enable it once they have been loaded - // the following imulates this situation + // the following emulates this situation pager.on('pager_changed', null, function () { pager.disable(); concurrency.delay(0).then(function () { assert.ok(pager.disabled, "pager should still be disabled"); pager.destroy(); - done(); }); }); pager.enable(); - testUtils.dom.click(pager.$('.o_pager_next')); - + await testUtils.dom.click(pager.$('.o_pager_next')); }); }); -});
\ No newline at end of file +}); diff --git a/addons/web/static/tests/chrome/user_menu_tests.js b/addons/web/static/tests/chrome/user_menu_tests.js index 0151afdf6d6..73c74b0753c 100644 --- a/addons/web/static/tests/chrome/user_menu_tests.js +++ b/addons/web/static/tests/chrome/user_menu_tests.js @@ -8,14 +8,14 @@ var Widget = require('web.Widget'); QUnit.module('chrome', {}, function () { QUnit.module('UserMenu'); - QUnit.test('basic rendering', function (assert) { + QUnit.test('basic rendering', async function (assert) { assert.expect(3); var parent = new Widget(); testUtils.mock.addMockEnvironment(parent, {}); var userMenu = new UserMenu(parent); - userMenu.appendTo($('body')); + await userMenu.appendTo($('body')); assert.strictEqual($('.o_user_menu').length, 1, "should have a user menu in the DOM"); diff --git a/addons/web/static/tests/core/concurrency_tests.js b/addons/web/static/tests/core/concurrency_tests.js index 516ce6a1b8f..1e195fde67d 100644 --- a/addons/web/static/tests/core/concurrency_tests.js +++ b/addons/web/static/tests/core/concurrency_tests.js @@ -2,166 +2,197 @@ odoo.define('web.concurrency_tests', function (require) { "use strict"; var concurrency = require('web.concurrency'); +var testUtils = require('web.test_utils'); + +var makeTestPromise = testUtils.makeTestPromise; +var makeTestPromiseWithAssert = testUtils.makeTestPromiseWithAssert; QUnit.module('core', {}, function () { QUnit.module('concurrency'); + QUnit.test('mutex: simple scheduling', async function (assert) { + assert.expect(5); + var mutex = new concurrency.Mutex(); - QUnit.test('mutex: simple scheduling', function (assert) { - assert.expect(6); + var prom1 = makeTestPromiseWithAssert(assert, 'prom1'); + var prom2 = makeTestPromiseWithAssert(assert, 'prom2'); - var m = new concurrency.Mutex(); + mutex.exec(function () { return prom1; }); + mutex.exec(function () { return prom2; }); - var def1 = $.Deferred(), - def2 = $.Deferred(); + assert.verifySteps([]); - var p1 = m.exec(function () { return def1; }); - var p2 = m.exec(function () { return def2; }); + await prom1.resolve(); - assert.strictEqual(p1.state(), "pending"); - assert.strictEqual(p2.state(), "pending"); + assert.verifySteps(['ok prom1']); - def1.resolve(); - assert.strictEqual(p1.state(), "resolved"); - assert.strictEqual(p2.state(), "pending"); + await prom2.resolve(); - def2.resolve(); - assert.strictEqual(p1.state(), "resolved"); - assert.strictEqual(p2.state(), "resolved"); + assert.verifySteps(['ok prom2']); }); - QUnit.test('mutex: simpleScheduling2', function (assert) { - assert.expect(6); + QUnit.test('mutex: simpleScheduling2', async function (assert) { + assert.expect(5); + var mutex = new concurrency.Mutex(); + + var prom1 = makeTestPromiseWithAssert(assert, 'prom1'); + var prom2 = makeTestPromiseWithAssert(assert, 'prom2'); - var m = new concurrency.Mutex(); + mutex.exec(function () { return prom1; }); + mutex.exec(function () { return prom2; }); - var def1 = $.Deferred(), - def2 = $.Deferred(); + assert.verifySteps([]); - var p1 = m.exec(function() { return def1; }); - var p2 = m.exec(function() { return def2; }); + await prom2.resolve(); - assert.strictEqual(p1.state(), "pending"); - assert.strictEqual(p2.state(), "pending"); - def2.resolve(); + assert.verifySteps(['ok prom2']); - assert.strictEqual(p1.state(), "pending"); - assert.strictEqual(p2.state(), "pending"); + await prom1.resolve(); - def1.resolve(); - assert.strictEqual(p1.state(), "resolved"); - assert.strictEqual(p2.state(), "resolved"); + assert.verifySteps(['ok prom1']); }); - QUnit.test('mutex: reject', function (assert) { - assert.expect(12); + QUnit.test('mutex: reject', async function (assert) { + assert.expect(7); + var mutex = new concurrency.Mutex(); - var m = new concurrency.Mutex(); + var prom1 = makeTestPromiseWithAssert(assert, 'prom1'); + var prom2 = makeTestPromiseWithAssert(assert, 'prom2'); + var prom3 = makeTestPromiseWithAssert(assert, 'prom3'); - var def1 = $.Deferred(), - def2 = $.Deferred(), - def3 = $.Deferred(); + mutex.exec(function () { return prom1; }).catch(function () {}); + mutex.exec(function () { return prom2; }).catch(function () {}); + mutex.exec(function () { return prom3; }).catch(function () {}); - var p1 = m.exec(function() {return def1;}); - var p2 = m.exec(function() {return def2;}); - var p3 = m.exec(function() {return def3;}); + assert.verifySteps([]); - assert.strictEqual(p1.state(), "pending"); - assert.strictEqual(p2.state(), "pending"); - assert.strictEqual(p3.state(), "pending"); + prom1.resolve(); + await testUtils.nextMicrotaskTick(); - def1.resolve(); - assert.strictEqual(p1.state(), "resolved"); - assert.strictEqual(p2.state(), "pending"); - assert.strictEqual(p3.state(), "pending"); + assert.verifySteps(['ok prom1']); - def2.reject(); - assert.strictEqual(p1.state(), "resolved"); - assert.strictEqual(p2.state(), "rejected"); - assert.strictEqual(p3.state(), "pending"); + prom2.catch(function () { + assert.verifySteps(['ko prom2']); + }); + prom2.reject({name: "sdkjfmqsjdfmsjkdfkljsdq"}); + await testUtils.nextMicrotaskTick(); + + prom3.resolve(); + await testUtils.nextMicrotaskTick(); - def3.resolve(); - assert.strictEqual(p1.state(), "resolved"); - assert.strictEqual(p2.state(), "rejected"); - assert.strictEqual(p3.state(), "resolved"); + assert.verifySteps(['ok prom3']); }); - QUnit.test('mutex: getUnlockedDef checks', function (assert) { - assert.expect(5); + QUnit.test('mutex: getUnlockedDef checks', async function (assert) { + assert.expect(9); + + var mutex = new concurrency.Mutex(); - var m = new concurrency.Mutex(); + var prom1 = makeTestPromiseWithAssert(assert, 'prom1'); + var prom2 = makeTestPromiseWithAssert(assert, 'prom2'); - var def1 = $.Deferred(); - var def2 = $.Deferred(); + mutex.getUnlockedDef().then(function () { + assert.step('mutex unlocked (1)'); + }); + + await testUtils.nextMicrotaskTick(); - assert.strictEqual(m.getUnlockedDef().state(), "resolved"); + assert.verifySteps(['mutex unlocked (1)']); - m.exec(function() { return def1; }); + mutex.exec(function () { return prom1; }); + await testUtils.nextMicrotaskTick(); - var unlockedDef = m.getUnlockedDef(); + mutex.getUnlockedDef().then(function () { + assert.step('mutex unlocked (2)'); + }); - assert.strictEqual(unlockedDef.state(), "pending"); + assert.verifySteps([]); - m.exec(function() { return def2; }); + mutex.exec(function () { return prom2; }); + await testUtils.nextMicrotaskTick(); - assert.strictEqual(unlockedDef.state(), "pending"); + assert.verifySteps([]); - def1.resolve(); + await prom1.resolve(); - assert.strictEqual(unlockedDef.state(), "pending"); + assert.verifySteps(['ok prom1']); - def2.resolve(); + prom2.resolve(); + await testUtils.nextTick(); - assert.strictEqual(unlockedDef.state(), "resolved"); + assert.verifySteps(['ok prom2', 'mutex unlocked (2)']); }); - QUnit.test('DropPrevious: basic usecase', function (assert) { - assert.expect(5); + QUnit.test('DropPrevious: basic usecase', async function (assert) { + assert.expect(4); + + var dp = new concurrency.DropPrevious(); + + var prom1 = makeTestPromise(assert, 'prom1'); + var prom2 = makeTestPromise(assert, 'prom2'); + + dp.add(prom1).then(() => assert.step('should not go here')) + .catch(()=> assert.step("rejected dp1")); + dp.add(prom2).then(() => assert.step("ok dp2")); + + await testUtils.nextMicrotaskTick(); + assert.verifySteps(['rejected dp1']); + + prom2.resolve(); + await testUtils.nextMicrotaskTick(); + + assert.verifySteps(['ok dp2']); + }); + + QUnit.test('DropPrevious: resolve first before last', async function (assert) { + assert.expect(4); var dp = new concurrency.DropPrevious(); - var def1 = $.Deferred(); - var def2 = $.Deferred(); + var prom1 = makeTestPromise(assert, 'prom1'); + var prom2 = makeTestPromise(assert, 'prom2'); - var r1 = dp.add(def1); + dp.add(prom1).then(() => assert.step('should not go here')) + .catch(()=> assert.step("rejected dp1")); + dp.add(prom2).then(() => assert.step("ok dp2")); - assert.strictEqual(r1.state(), "pending"); - var r2 = dp.add(def2); + await testUtils.nextMicrotaskTick(); - assert.strictEqual(r1.state(), "rejected"); - assert.strictEqual(r2.state(), "pending"); + assert.verifySteps(['rejected dp1']); - def2.resolve(); + prom1.resolve(); + prom2.resolve(); + await testUtils.nextMicrotaskTick(); - assert.strictEqual(r1.state(), "rejected"); - assert.strictEqual(r2.state(), "resolved"); + assert.verifySteps(['ok dp2']); }); - QUnit.test('DropMisordered: resolve all correctly ordered, sync', function (assert) { + QUnit.test('DropMisordered: resolve all correctly ordered, sync', async function (assert) { assert.expect(1); var dm = new concurrency.DropMisordered(), flag = false; - var d1 = $.Deferred(), - d2 = $.Deferred(); + var d1 = makeTestPromise(); + var d2 = makeTestPromise(); var r1 = dm.add(d1), r2 = dm.add(d2); - $.when(r1, r2).done(function () { + Promise.all([r1, r2]).then(function () { flag = true; }); d1.resolve(); d2.resolve(); + await testUtils.nextTick(); assert.ok(flag); }); - QUnit.test("DropMisordered: don't resolve mis-ordered, sync", function (assert) { + QUnit.test("DropMisordered: don't resolve mis-ordered, sync", async function (assert) { assert.expect(4); var dm = new concurrency.DropMisordered(), @@ -170,45 +201,47 @@ QUnit.module('core', {}, function () { fail1 = false, fail2 = false; - var d1 = $.Deferred(), - d2 = $.Deferred(); + var d1 = makeTestPromise(); + var d2 = makeTestPromise(); - dm.add(d1).done(function () { done1 = true; }) - .fail(function () { fail1 = true; }); - dm.add(d2).done(function () { done2 = true; }) - .fail(function () { fail2 = true; }); + dm.add(d1).then(function () { done1 = true; }) + .catch(function () { fail1 = true; }); + dm.add(d2).then(function () { done2 = true; }) + .catch(function () { fail2 = true; }); d2.resolve(); d1.resolve(); + await testUtils.nextMicrotaskTick(); // d1 is in limbo assert.ok(!done1); assert.ok(!fail1); - // d2 is resolved + // d2 is fulfilled assert.ok(done2); assert.ok(!fail2); }); - QUnit.test('DropMisordered: fail mis-ordered flag, sync', function (assert) { + QUnit.test('DropMisordered: fail mis-ordered flag, sync', async function (assert) { assert.expect(4); - var dm = new concurrency.DropMisordered(true), + var dm = new concurrency.DropMisordered(true/* failMisordered */), done1 = false, done2 = false, fail1 = false, fail2 = false; - var d1 = $.Deferred(), - d2 = $.Deferred(); + var d1 = makeTestPromise(); + var d2 = makeTestPromise(); - dm.add(d1).done(function () { done1 = true; }) - .fail(function () { fail1 = true; }); - dm.add(d2).done(function () { done2 = true; }) - .fail(function () { fail2 = true; }); + dm.add(d1).then(function () { done1 = true; }) + .catch(function () { fail1 = true; }); + dm.add(d2).then(function () { done2 = true; }) + .catch(function () { fail2 = true; }); d2.resolve(); d1.resolve(); + await testUtils.nextMicrotaskTick(); // d1 is in limbo assert.ok(!done1); @@ -225,8 +258,8 @@ QUnit.module('core', {}, function () { var dm = new concurrency.DropMisordered(); - var d1 = $.Deferred(), - d2 = $.Deferred(); + var d1 = makeTestPromise(); + var d2 = makeTestPromise(); var r1 = dm.add(d1), r2 = dm.add(d2); @@ -234,7 +267,7 @@ QUnit.module('core', {}, function () { setTimeout(function () { d1.resolve(); }, 10); setTimeout(function () { d2.resolve(); }, 20); - $.when(r1, r2).done(function () { + Promise.all([r1, r2]).then(function () { assert.ok(true); done(); }); @@ -248,13 +281,13 @@ QUnit.module('core', {}, function () { done1 = false, done2 = false, fail1 = false, fail2 = false; - var d1 = $.Deferred(), - d2 = $.Deferred(); + var d1 = makeTestPromise(); + var d2 = makeTestPromise(); - dm.add(d1).done(function () { done1 = true; }) - .fail(function () { fail1 = true; }); - dm.add(d2).done(function () { done2 = true; }) - .fail(function () { fail2 = true; }); + dm.add(d1).then(function () { done1 = true; }) + .catch(function () { fail1 = true; }); + dm.add(d2).then(function () { done2 = true; }) + .catch(function () { fail2 = true; }); setTimeout(function () { d1.resolve(); }, 20); setTimeout(function () { d2.resolve(); }, 10); @@ -279,13 +312,13 @@ QUnit.module('core', {}, function () { done1 = false, done2 = false, fail1 = false, fail2 = false; - var d1 = $.Deferred(), - d2 = $.Deferred(); + var d1 = makeTestPromise(); + var d2 = makeTestPromise(); - dm.add(d1).done(function () { done1 = true; }) - .fail(function () { fail1 = true; }); - dm.add(d2).done(function () { done2 = true; }) - .fail(function () { fail2 = true; }); + dm.add(d1).then(function () { done1 = true; }) + .catch(function () { fail1 = true; }); + dm.add(d2).then(function () { done2 = true; }) + .catch(function () { fail2 = true; }); setTimeout(function () { d1.resolve(); }, 20); setTimeout(function () { d2.resolve(); }, 10); @@ -302,230 +335,242 @@ QUnit.module('core', {}, function () { }, 30); }); - QUnit.test('MutexedDropPrevious: simple', function (assert) { - assert.expect(3); + QUnit.test('MutexedDropPrevious: simple', async function (assert) { + assert.expect(5); var m = new concurrency.MutexedDropPrevious(); + var d1 = makeTestPromise(); - var d1 = $.Deferred(); - var p1 = m.exec(function () { return d1; }).then(function (result) { + d1.then(function () { + assert.step("d1 resolved"); + }); + m.exec(function () { return d1; }).then(function (result) { + assert.step("p1 done"); assert.strictEqual(result, 'd1'); }); - assert.strictEqual(p1.state(), "pending"); - + assert.verifySteps([]); d1.resolve('d1'); - assert.strictEqual(p1.state(), "resolved"); + await testUtils.nextMicrotaskTick(); + + assert.verifySteps(["d1 resolved","p1 done"]); }); - QUnit.test('MutexedDropPrevious: 2 arrives after 1 resolution', function (assert) { - assert.expect(6); + QUnit.test('MutexedDropPrevious: d2 arrives after d1 resolution', async function (assert) { + assert.expect(8); var m = new concurrency.MutexedDropPrevious(); + var d1 = makeTestPromiseWithAssert(assert, 'd1'); - var d1 = $.Deferred(); - var p1 = m.exec(function () { return d1; }).then(function (result) { - assert.strictEqual(result, 'd1'); + m.exec(function () { return d1; }).then(function () { + assert.step("p1 resolved"); }); - assert.strictEqual(p1.state(), "pending"); - + assert.verifySteps([]); d1.resolve('d1'); - assert.strictEqual(p1.state(), "resolved"); + await testUtils.nextMicrotaskTick(); - var d2 = $.Deferred(); - var p2 = m.exec(function () { return d2; }).then(function (result) { - assert.strictEqual(result, 'd2'); - }); + assert.verifySteps(['ok d1','p1 resolved']); - assert.strictEqual(p2.state(), "pending"); + var d2 = makeTestPromiseWithAssert(assert, 'd2'); + m.exec(function () { return d2; }).then(function () { + assert.step("p2 resolved"); + }); + assert.verifySteps([]); d2.resolve('d2'); - assert.strictEqual(p2.state(), "resolved"); + await testUtils.nextMicrotaskTick(); + + assert.verifySteps(['ok d2','p2 resolved']); }); - QUnit.test('MutexedDropPrevious: 1 does not return a deferred', function (assert) { - assert.expect(5); + QUnit.test('MutexedDropPrevious: p1 does not return a deferred', async function (assert) { + assert.expect(7); var m = new concurrency.MutexedDropPrevious(); - var p1 = m.exec(function () { return 42; }).then(function (result) { - assert.strictEqual(result, 42); + m.exec(function () { return 42; }).then(function () { + assert.step("p1 resolved"); }); - assert.strictEqual(p1.state(), "resolved"); + assert.verifySteps([]); + await testUtils.nextMicrotaskTick(); - var d2 = $.Deferred(); - var p2 = m.exec(function () { return d2; }).then(function (result) { - assert.strictEqual(result, 'd2'); - }); + assert.verifySteps(['p1 resolved']); - assert.strictEqual(p2.state(), "pending"); + var d2 = makeTestPromiseWithAssert(assert, 'd2'); + m.exec(function () { return d2; }).then(function () { + assert.step("p2 resolved"); + }); + assert.verifySteps([]); d2.resolve('d2'); - assert.strictEqual(p2.state(), "resolved"); + await testUtils.nextMicrotaskTick(); + assert.verifySteps(['ok d2','p2 resolved']); }); - QUnit.test('MutexedDropPrevious: 2 arrives before 1 resolution', function (assert) { - assert.expect(13); + QUnit.test('MutexedDropPrevious: p2 arrives before p1 resolution', async function (assert) { + assert.expect(8); var m = new concurrency.MutexedDropPrevious(); + var d1 = makeTestPromiseWithAssert(assert, 'd1'); - var d1 = $.Deferred(); - var d2 = $.Deferred(); - - var p1 = m.exec(function () { - assert.step('p1'); - return d1; + m.exec(function () { return d1; }).catch(function () { + assert.step("p1 rejected"); }); - assert.strictEqual(p1.state(), "pending"); + assert.verifySteps([]); - var p2 = m.exec(function () { - assert.step('p2'); - return d2; - }).then(function (result) { - assert.strictEqual(result, 'd2'); + var d2 = makeTestPromiseWithAssert(assert, 'd2'); + m.exec(function () { return d2; }).then(function () { + assert.step("p2 resolved"); }); - assert.strictEqual(p1.state(), "rejected"); - assert.strictEqual(p2.state(), "pending"); - - assert.step('d1 resolved'); + assert.verifySteps([]); d1.resolve('d1'); - assert.strictEqual(p1.state(), "rejected"); - assert.strictEqual(p2.state(), "pending"); + await testUtils.nextMicrotaskTick(); + assert.verifySteps(['p1 rejected', 'ok d1']); - assert.step('d2 resolved'); d2.resolve('d2'); - assert.strictEqual(p1.state(), "rejected"); - assert.strictEqual(p2.state(), "resolved"); - - assert.verifySteps(['p1', 'd1 resolved', 'p2', 'd2 resolved']); + await testUtils.nextMicrotaskTick(); + assert.verifySteps(['ok d2', 'p2 resolved']); }); - QUnit.test('MutexedDropPrevious: 3 arrives before 2 initialization', function (assert) { - assert.expect(13); - + QUnit.test('MutexedDropPrevious: 3 arrives before 2 initialization', async function (assert) { + assert.expect(10); var m = new concurrency.MutexedDropPrevious(); - var d1 = $.Deferred(); - var d2 = $.Deferred(); - var d3 = $.Deferred(); + var d1 = makeTestPromiseWithAssert(assert, 'd1'); + var d3 = makeTestPromiseWithAssert(assert, 'd3'); - var p1 = m.exec(function () { return d1; }); - assert.strictEqual(p1.state(), "pending"); + m.exec(function () { return d1; }).catch(function () { + assert.step('p1 rejected'); + }); - var p2 = m.exec(function () { + m.exec(function () { assert.ok(false, "should not execute this function"); - return d2; + }).catch(function () { + assert.step('p2 rejected'); }); - assert.strictEqual(p1.state(), "rejected"); - assert.strictEqual(p2.state(), "pending"); - var p3 = m.exec(function () { return d3; }).then(function (result) { + m.exec(function () { return d3; }).then(function (result) { assert.strictEqual(result, 'd3'); + assert.step('p3 resolved'); }); - assert.strictEqual(p1.state(), "rejected"); - assert.strictEqual(p2.state(), "rejected"); - assert.strictEqual(p3.state(), "pending"); + + assert.verifySteps([]); + + await testUtils.nextMicrotaskTick(); + + assert.verifySteps(['p1 rejected', 'p2 rejected']); d1.resolve('d1'); - assert.strictEqual(p1.state(), "rejected"); - assert.strictEqual(p2.state(), "rejected"); - assert.strictEqual(p3.state(), "pending"); + await testUtils.nextMicrotaskTick(); + + assert.verifySteps(['ok d1']); d3.resolve('d3'); - assert.strictEqual(p1.state(), "rejected"); - assert.strictEqual(p2.state(), "rejected"); - assert.strictEqual(p3.state(), "resolved"); - }); + await testUtils.nextTick(); - QUnit.test('MutexedDropPrevious: 3 arrives after 2 initialization', function (assert) { - assert.expect(19); + assert.verifySteps(['ok d3','p3 resolved']); + }); + + QUnit.test('MutexedDropPrevious: 3 arrives after 2 initialization', async function (assert) { + assert.expect(15); var m = new concurrency.MutexedDropPrevious(); - var d1 = $.Deferred(); - var d2 = $.Deferred(); - var d3 = $.Deferred(); + var d1 = makeTestPromiseWithAssert(assert, 'd1'); + var d2 = makeTestPromiseWithAssert(assert, 'd2'); + var d3 = makeTestPromiseWithAssert(assert, 'd3'); - var p1 = m.exec(function () { - assert.step('p1'); + m.exec(function () { + assert.step('execute d1'); return d1; + }).catch(function () { + assert.step('p1 rejected'); }); - assert.strictEqual(p1.state(), "pending"); - var p2 = m.exec(function () { - assert.step('p2'); + m.exec(function () { + assert.step('execute d2'); return d2; + }).catch(function () { + assert.step('p2 rejected'); }); - assert.strictEqual(p1.state(), "rejected"); - assert.strictEqual(p2.state(), "pending"); + assert.verifySteps(['execute d1']); + + await testUtils.nextMicrotaskTick(); + assert.verifySteps(['p1 rejected']); - assert.step('d1 resolved'); d1.resolve('d1'); - assert.strictEqual(p1.state(), "rejected"); - assert.strictEqual(p2.state(), "pending"); + await testUtils.nextMicrotaskTick(); + + assert.verifySteps(['ok d1', 'execute d2']); - var p3 = m.exec(function () { - assert.step('p3'); + m.exec(function () { + assert.step('execute d3'); return d3; - }).then(function (result) { - assert.strictEqual(result, 'd3'); + }).then(function () { + assert.step('p3 resolved'); }); + await testUtils.nextMicrotaskTick(); + assert.verifySteps(['p2 rejected']); - assert.step('d2 resolved'); - d2.resolve('d2'); - assert.strictEqual(p1.state(), "rejected"); - assert.strictEqual(p2.state(), "rejected"); - assert.strictEqual(p3.state(), "pending"); + d2.resolve(); + await testUtils.nextMicrotaskTick(); + assert.verifySteps(['ok d2', 'execute d3']); - assert.step('d3 resolved'); - d3.resolve('d3'); - assert.strictEqual(p1.state(), "rejected"); - assert.strictEqual(p2.state(), "rejected"); - assert.strictEqual(p3.state(), "resolved"); + d3.resolve(); + await testUtils.nextTick(); + assert.verifySteps(['ok d3', 'p3 resolved']); - assert.verifySteps(['p1', 'd1 resolved', 'p2', 'd2 resolved', 'p3', 'd3 resolved']); - }); + }); - QUnit.test('MutexedDropPrevious: 2 in then of 1 with 3', function (assert) { - assert.expect(6); + QUnit.test('MutexedDropPrevious: 2 in then of 1 with 3', async function (assert) { + assert.expect(9); var m = new concurrency.MutexedDropPrevious(); - var d1 = $.Deferred(); - var d2 = $.Deferred(); - var d3 = $.Deferred(); + var d1 = makeTestPromiseWithAssert(assert, 'd1'); + var d2 = makeTestPromiseWithAssert(assert, 'd2'); + var d3 = makeTestPromiseWithAssert(assert, 'd3'); var p3; - var p1 = m.exec(function () { return d1; }) - .always(function () { + m.exec(function () { return d1; }) + .catch(function () { + assert.step('p1 rejected'); p3 = m.exec(function () { return d3; - }).then(function (result) { - assert.strictEqual(result, 'd3'); + }).then(function () { + assert.step('p3 resolved'); }); return p3; }); - assert.strictEqual(p1.state(), "pending"); + await testUtils.nextTick(); + assert.verifySteps([]); - var p2 = m.exec(function () { - assert.ok(false, "should not execute this function"); + m.exec(function () { + assert.ok(false, 'should not execute this function'); return d2; + }).catch(function () { + assert.step('p2 rejected'); }); - assert.strictEqual(p1.state(), "rejected"); - assert.strictEqual(p2.state(), "rejected"); + + await testUtils.nextTick(); + assert.verifySteps(['p1 rejected', 'p2 rejected']); d1.resolve('d1'); - assert.strictEqual(p3.state(), "pending"); + await testUtils.nextTick(); + + assert.verifySteps(['ok d1']); d3.resolve('d3'); - assert.strictEqual(p3.state(), "resolved"); + await testUtils.nextTick(); + + assert.verifySteps(['ok d3', 'p3 resolved']); }); }); -});
\ No newline at end of file +}); diff --git a/addons/web/static/tests/core/dom_tests.js b/addons/web/static/tests/core/dom_tests.js index ab10bbb78f8..c57ced52099 100644 --- a/addons/web/static/tests/core/dom_tests.js +++ b/addons/web/static/tests/core/dom_tests.js @@ -30,7 +30,11 @@ function prepareAutoresizeTextArea(options) { QUnit.module('core', {}, function () { QUnit.module('dom', {}, function () { - QUnit.module('autoresize'); + QUnit.module('autoresize', { + afterEach: function () { + $('#qunit-fixture').find('textarea').remove(); + }, + }); QUnit.test('autoresize (border-box): no padding + no border', function (assert) { assert.expect(3); diff --git a/addons/web/static/tests/core/mixins_tests.js b/addons/web/static/tests/core/mixins_tests.js index 27c87870876..8c71a2592ce 100644 --- a/addons/web/static/tests/core/mixins_tests.js +++ b/addons/web/static/tests/core/mixins_tests.js @@ -10,6 +10,7 @@ QUnit.module('core', {}, function () { QUnit.test('perform a do_action properly', function (assert) { assert.expect(3); + var done = assert.async(); var widget = new Widget(); @@ -22,9 +23,10 @@ QUnit.module('core', {}, function () { }); widget.do_action('test.some_action_id', {clear_breadcrumbs: true}).then(function () { - assert.step('deferred should have been resolved'); + assert.ok(true, 'deferred should have been resolved'); + widget.destroy(); + done(); }); - widget.destroy(); }); diff --git a/addons/web/static/tests/core/widget_tests.js b/addons/web/static/tests/core/widget_tests.js index 0eb4192c4a1..440cc97b524 100644 --- a/addons/web/static/tests/core/widget_tests.js +++ b/addons/web/static/tests/core/widget_tests.js @@ -2,7 +2,6 @@ odoo.define('web.widget_tests', function (require) { "use strict"; var AjaxService = require('web.AjaxService'); -var concurrency = require('web.concurrency'); var core = require('web.core'); var QWeb = require('web.QWeb'); var Widget = require('web.Widget'); @@ -86,8 +85,6 @@ QUnit.module('core', {}, function () { w.destroy(); }); - - QUnit.test('renderElement, no template, default', function (assert) { assert.expect(7); @@ -206,7 +203,7 @@ QUnit.module('core', {}, function () { widget.destroy(); }); - QUnit.test('repeated', function (assert) { + QUnit.test('repeated', async function (assert) { assert.expect(4); var $fix = $( "#qunit-fixture"); @@ -222,17 +219,16 @@ QUnit.module('core', {}, function () { }))(); widget.value = 42; - return widget.appendTo($fix) - .done(function () { + await widget.appendTo($fix) + .then(function () { assert.strictEqual($fix.find('p').text(), '42', "DOM fixture should contain initial value"); assert.strictEqual(widget.$el.text(), '42', "should set initial value"); widget.value = 36; widget.renderElement(); assert.strictEqual($fix.find('p').text(), '36', "DOM fixture should use new value"); assert.strictEqual(widget.$el.text(), '36', "should set new value"); - }).always(function () { - widget.destroy(); }); + widget.destroy(); }); @@ -274,7 +270,7 @@ QUnit.module('core', {}, function () { }); - QUnit.test('delegate', function (assert) { + QUnit.test('delegate', async function (assert) { assert.expect(5); var a = []; @@ -292,9 +288,9 @@ QUnit.module('core', {}, function () { }))(); widget.renderElement(); - widget.$el.click(); - widget.$('li:eq(3)').click(); - widget.$('input:last').val('foo').change(); + await testUtils.dom.click(widget.$el, {allowInvisible: true}); + await testUtils.dom.click(widget.$('li:eq(3)'), {allowInvisible: true}); + await testUtils.fields.editAndTrigger(widget.$('input:last'), 'foo', 'change'); for(var i=0; i<3; ++i) { assert.ok(a[i], "should pass test " + i); @@ -302,7 +298,7 @@ QUnit.module('core', {}, function () { widget.destroy(); }); - QUnit.test('undelegate', function (assert) { + QUnit.test('undelegate', async function (assert) { assert.expect(4); var clicked = false; @@ -316,13 +312,13 @@ QUnit.module('core', {}, function () { widget.renderElement(); widget.$el.on('click', 'li', function () { newclicked = true; }); - widget.$('li').click(); + await testUtils.dom.clickFirst(widget.$('li'), {allowInvisible: true}); assert.ok(clicked, "should trigger bound events"); assert.ok(newclicked, "should trigger bound events"); clicked = newclicked = false; widget._undelegateEvents(); - widget.$('li').click(); + await testUtils.dom.clickFirst(widget.$('li'), {allowInvisible: true}); assert.ok(!clicked, "undelegate should unbind events delegated"); assert.ok(newclicked, "undelegate should only unbind events it created"); widget.destroy(); @@ -330,50 +326,54 @@ QUnit.module('core', {}, function () { QUnit.module('Widget, and async stuff'); - QUnit.test("alive(alive)", function (assert) { + QUnit.test("alive(alive)", async function (assert) { assert.expect(1); var widget = new (Widget.extend({})); - return concurrency.asyncWhen(widget.start()) - .then(function () { return widget.alive(concurrency.asyncWhen()); }) - .then(function () { assert.ok(true); }) - .always(function () { widget.destroy(); }); + await widget.start() + .then(function () {return widget.alive(Promise.resolve()) ;}) + .then(function () { assert.ok(true); }); + + widget.destroy(); }); QUnit.test("alive(dead)", function (assert) { assert.expect(1); var widget = new (Widget.extend({})); - return $.Deferred(function (d) { - concurrency.asyncWhen(widget.start()) + return new Promise(function (resolve, reject) { + widget.start() .then(function () { // destroy widget widget.destroy(); - var promise = concurrency.asyncWhen(); + var promise = Promise.resolve(); // leave time for alive() to do its stuff promise.then(function () { - return concurrency.asyncWhen(); + return Promise.resolve(); }).then(function () { assert.ok(true); - d.resolve(); + resolve(); }); // ensure that widget.alive() refuses to resolve or reject return widget.alive(promise); - }).always(function () { - d.reject(); + }).then(function () { + reject(); + assert.ok(false, "alive() should not terminate by default"); + }).catch(function() { + reject(); assert.ok(false, "alive() should not terminate by default"); }); }); }); - QUnit.test("alive(alive, true)", function (assert) { + QUnit.test("alive(alive, true)", async function (assert) { assert.expect(1); var widget = new (Widget.extend({})); - return concurrency.asyncWhen(widget.start()) - .then(function () { return widget.alive(concurrency.asyncWhen(), true) }) - .then(function () { assert.ok(true); }) - .always(function () { widget.destroy(); }); + await widget.start() + .then(function () { return widget.alive(Promise.resolve(), true); }) + .then(function () { assert.ok(true); }); + widget.destroy(); }); QUnit.test("alive(dead, true)", function (assert) { @@ -382,11 +382,11 @@ QUnit.module('core', {}, function () { var widget = new (Widget.extend({})); - concurrency.asyncWhen(widget.start()) + widget.start() .then(function () { // destroy widget widget.destroy(); - return widget.alive(concurrency.asyncWhen(), true); + return widget.alive(Promise.resolve(), true); }).then(function () { assert.ok(false, "alive(p, true) should fail its promise"); done(); @@ -396,7 +396,7 @@ QUnit.module('core', {}, function () { }); }); - QUnit.test("calling _rpc on destroyed widgets", function (assert) { + QUnit.test("calling _rpc on destroyed widgets", async function (assert) { assert.expect(3); var def; @@ -404,7 +404,7 @@ QUnit.module('core', {}, function () { testUtils.mock.addMockEnvironment(parent, { session: { rpc: function () { - def = $.Deferred(); + def = testUtils.makeTestPromise(); def.abort = def.reject; return def; }, @@ -419,23 +419,30 @@ QUnit.module('core', {}, function () { assert.ok(true, "The ajax call should be resolve"); }); def.resolve(); + await testUtils.nextMicrotaskTick(); def = null; - widget._rpc({route: '/a/route'}).always(function () { + widget._rpc({route: '/a/route'}).then(function () { throw Error("Calling _rpc on a destroyed widget should return a " + - "deferred that is never resolved nor rejected"); + "promise that remains pending forever"); + }).catch(function () { + throw Error("Calling _rpc on a destroyed widget should return a " + + "promise that remains pending forever"); }); widget.destroy(); def.resolve(); + await testUtils.nextMicrotaskTick(); def = null; - widget._rpc({route: '/a/route'}).always(function () { + widget._rpc({route: '/a/route'}).then(function () { + throw Error("Calling _rpc on a destroyed widget should return a " + + "promise that remains pending forever"); + }).catch(function () { throw Error("Calling _rpc on a destroyed widget should return a " + - "deferred that is never resolved nor rejected"); + "promise that remains pending forever"); }); - assert.ok(!def, - "The trigger_up is not performed and the call returns a deferred "+ - "never resolved nor rejected"); + assert.ok(!def, "trigger_up is not performed and the call returns a " + + "promise that remains pending forever"); assert.ok(true, "there should be no crash when calling _rpc on a destroyed widget"); @@ -444,12 +451,12 @@ QUnit.module('core', {}, function () { QUnit.test('start is not called when widget is destroyed', function (assert) { assert.expect(0); - var slowWillStartDef = $.Deferred(); + var slowWillStartPromise = testUtils.makeTestPromise(); var $fix = $( "#qunit-fixture"); var widget = new (Widget.extend({ willStart: function () { - return slowWillStartDef; + return slowWillStartPromise; }, start: function () { throw new Error('Should not call start method'); @@ -458,7 +465,7 @@ QUnit.module('core', {}, function () { widget.appendTo($fix); widget.destroy(); - slowWillStartDef.resolve(); + slowWillStartPromise.resolve(); }); }); diff --git a/addons/web/static/tests/fields/basic_fields_mobile_tests.js b/addons/web/static/tests/fields/basic_fields_mobile_tests.js index fd302ebeb36..a58957ebfca 100644 --- a/addons/web/static/tests/fields/basic_fields_mobile_tests.js +++ b/addons/web/static/tests/fields/basic_fields_mobile_tests.js @@ -53,10 +53,10 @@ QUnit.module('basic_fields', { QUnit.module('PhoneWidget'); - QUnit.test('phone field in form view on extra small screens', function (assert) { + QUnit.test('phone field in form view on extra small screens', async function (assert) { assert.expect(7); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -79,17 +79,17 @@ QUnit.module('basic_fields', { "should have proper tel prefix"); // switch to edit mode and check the result - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, 'input[type="text"].o_field_widget', "should have an int for the phone field"); assert.strictEqual(form.$('input[type="text"].o_field_widget').val(), 'yop', "input should contain field value in edit mode"); // change value in edit mode - testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), 'new'); + await testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), 'new'); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); $phoneLink = form.$('a.o_form_uri.o_field_widget'); assert.strictEqual($phoneLink.text(), 'new', "new value should be displayed properly"); @@ -99,10 +99,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('phone field in editable list view on extra small screens', function (assert) { + QUnit.test('phone field in editable list view on extra small screens', async function (assert) { assert.expect(10); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -122,14 +122,14 @@ QUnit.module('basic_fields', { // Edit a line and check the result var $cell = list.$('tbody td:not(.o_list_record_selector)').first(); - testUtils.dom.click($cell); + await testUtils.dom.click($cell); assert.hasClass($cell.parent(),'o_selected_row', 'should be set as edit mode'); assert.strictEqual($cell.find('input').val(), 'yop', 'should have the corect value in internal input'); - testUtils.fields.editInput($cell.find('input'), 'new'); + await testUtils.fields.editInput($cell.find('input'), 'new'); // save - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); $cell = list.$('tbody td:not(.o_list_record_selector)').first(); assert.doesNotHaveClass($cell.parent(), 'o_selected_row', 'should not be in edit mode anymore'); assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'new', @@ -143,10 +143,10 @@ QUnit.module('basic_fields', { list.destroy(); }); - QUnit.test('phone field does not allow html injections', function (assert) { + QUnit.test('phone field does not allow html injections', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -164,10 +164,10 @@ QUnit.module('basic_fields', { }); var val = '<script>throw Error();</script><script>throw Error();</script>'; - testUtils.fields.editInput(form.$('input.o_field_widget[name="foo"]'), val); + await testUtils.fields.editInput(form.$('input.o_field_widget[name="foo"]'), val); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_widget').text(), val, "value should have been correctly escaped"); diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js index 33f18558917..223e1e5764c 100644 --- a/addons/web/static/tests/fields/basic_fields_tests.js +++ b/addons/web/static/tests/fields/basic_fields_tests.js @@ -13,7 +13,7 @@ var testUtils = require('web.test_utils'); var field_registry = require('web.field_registry'); var createView = testUtils.createView; -var createAsyncView = testUtils.createAsyncView; +var createView = testUtils.createView; var DebouncedField = basicFields.DebouncedField; var JournalDashboardGraph = basicFields.JournalDashboardGraph; var _t = core._t; @@ -128,11 +128,10 @@ QUnit.module('basic_fields', { QUnit.module('DebouncedField'); - QUnit.test('debounced fields do not trigger call _setValue once destroyed', function (assert) { - var done = assert.async(); + QUnit.test('debounced fields do not trigger call _setValue once destroyed', async function (assert) { assert.expect(4); - var def = $.Deferred(); + var def = testUtils.makeTestPromise(); var _doAction = DebouncedField.prototype._doAction; DebouncedField.prototype._doAction = function () { _doAction.apply(this, arguments); @@ -144,7 +143,7 @@ QUnit.module('basic_fields', { _setValue.apply(this, arguments); }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -167,31 +166,28 @@ QUnit.module('basic_fields', { assert.verifySteps([], "_setValue shouldn't have been called yet"); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.verifySteps(['_setValue'], "_setValue should have been called once"); // destroy the form view - def = $.Deferred(); + def = testUtils.makeTestPromise(); form.destroy(); + await testUtils.nextMicrotaskTick(); // wait for the debounced callback to be called - def.then(function () { - assert.verifySteps(['_setValue'], - "_setValue should not have been called after widget destruction"); - - DebouncedField.prototype._doAction = _doAction; - DebouncedField.prototype._setValue = _setValue; - done(); - }); + assert.verifySteps([], + "_setValue should not have been called after widget destruction"); + DebouncedField.prototype._doAction = _doAction; + DebouncedField.prototype._setValue = _setValue; }); QUnit.module('FieldBoolean'); - QUnit.test('boolean field in form view', function (assert) { - assert.expect(12); + QUnit.test('boolean field in form view', async function (assert) { + assert.expect(13); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -203,70 +199,73 @@ QUnit.module('basic_fields', { "checkbox should be checked"); // switch to edit mode and check the result - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, '.o_field_boolean input:checked', "checkbox should still be checked"); // uncheck the checkbox - testUtils.dom.click(form.$('.o_field_boolean input:checked')); + await testUtils.dom.click(form.$('.o_field_boolean input:checked')); assert.containsNone(form, '.o_field_boolean input:checked', "checkbox should no longer be checked"); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.containsNone(form, '.o_field_boolean input:checked', "checkbox should still no longer be checked"); // switch to edit mode and test the opposite change - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsNone(form, '.o_field_boolean input:checked', "checkbox should still be unchecked"); // check the checkbox - testUtils.dom.click(form.$('.o_field_boolean input')); + await testUtils.dom.click(form.$('.o_field_boolean input')); assert.containsOnce(form, '.o_field_boolean input:checked', "checkbox should now be checked"); // uncheck it back - testUtils.dom.click(form.$('.o_field_boolean input')); + await testUtils.dom.click(form.$('.o_field_boolean input')); assert.containsNone(form, '.o_field_boolean input:checked', "checkbox should now be unchecked"); // check the checkbox by clicking on label - testUtils.dom.click(form.$('.o_form_view label:first')); + await testUtils.dom.click(form.$('.o_form_view label:first')); assert.containsOnce(form, '.o_field_boolean input:checked', "checkbox should now be checked"); // uncheck it back - testUtils.dom.click(form.$('.o_form_view label:first')); + await testUtils.dom.click(form.$('.o_form_view label:first')); assert.containsNone(form, '.o_field_boolean input:checked', "checkbox should now be unchecked"); // check the checkbox by hitting the "enter" key after focusing it - form.$('.o_field_boolean input') - .trigger("focusin") - .trigger({type: "keydown", which: $.ui.keyCode.ENTER}) - .trigger({type: "keyup", which: $.ui.keyCode.ENTER}); + await testUtils.dom.triggerEvents(form.$('.o_field_boolean input'), [ + "focusin", + {type: "keydown", which: $.ui.keyCode.ENTER}, + {type: "keyup", which: $.ui.keyCode.ENTER}]); assert.containsOnce(form, '.o_field_boolean input:checked', - "checkbox should now be checked"); + "checkbox should now be checked"); // blindly press enter again, it should uncheck the checkbox $(document.activeElement).trigger({type: "keydown", which: $.ui.keyCode.ENTER}); assert.containsNone(form, '.o_field_boolean input:checked', - "checkbox should not be checked"); + "checkbox should not be checked"); + await testUtils.nextTick(); // blindly press enter again, it should check the checkbox back $(document.activeElement).trigger({type: "keydown", which: $.ui.keyCode.ENTER}); + assert.containsOnce(form, '.o_field_boolean input:checked', + "checkbox should still be checked"); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.containsOnce(form, '.o_field_boolean input:checked', "checkbox should still be checked"); form.destroy(); }); - QUnit.test('boolean field in editable list view', function (assert) { + QUnit.test('boolean field in editable list view', async function (assert) { assert.expect(11); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -282,13 +281,13 @@ QUnit.module('basic_fields', { var $cell = list.$('tr.o_data_row:has(.custom-checkbox input:checked) td:not(.o_list_record_selector)').first(); assert.ok($cell.find('.custom-checkbox input:checked').prop('disabled'), "input should be disabled in readonly mode"); - testUtils.dom.click($cell); + await testUtils.dom.click($cell); assert.ok(!$cell.find('.custom-checkbox input:checked').prop('disabled'), "input should not have the disabled property in edit mode"); - testUtils.dom.click($cell.find('.custom-checkbox input:checked')); + await testUtils.dom.click($cell.find('.custom-checkbox input:checked')); // save - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); $cell = list.$('tr.o_data_row:has(.custom-checkbox input:not(:checked)) td:not(.o_list_record_selector)').first(); assert.ok($cell.find('.custom-checkbox input:not(:checked)').prop('disabled'), "input should be disabled again"); @@ -298,12 +297,12 @@ QUnit.module('basic_fields', { "should now have only 3 checked input"); // Re-Edit the line and fake-check the checkbox - testUtils.dom.click($cell); - testUtils.dom.click($cell.find('.custom-checkbox input')); - testUtils.dom.click($cell.find('.custom-checkbox input')); + await testUtils.dom.click($cell); + await testUtils.dom.click($cell.find('.custom-checkbox input')); + await testUtils.dom.click($cell.find('.custom-checkbox input')); // Save - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .custom-checkbox input').length, 5, "should still have 5 checkboxes"); assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .custom-checkbox input:checked').length, 3, @@ -312,7 +311,7 @@ QUnit.module('basic_fields', { // Re-Edit the line to check the checkbox back but this time click on // the checkbox directly in readonly mode ! $cell = list.$('tr.o_data_row:has(.custom-checkbox input:not(:checked)) td:not(.o_list_record_selector)').first(); - testUtils.dom.click($cell.find('.custom-checkbox .custom-control-label')); + await testUtils.dom.click($cell.find('.custom-checkbox .custom-control-label')); assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .custom-checkbox input').length, 5, "should still have 5 checkboxes"); @@ -324,7 +323,7 @@ QUnit.module('basic_fields', { QUnit.module('FieldBooleanButton'); - QUnit.test('use custom terminology in form view', function (assert) { + QUnit.test('use custom terminology in form view', async function (assert) { assert.expect(2); var terminology = { string_true: "Production Environment", @@ -332,7 +331,7 @@ QUnit.module('basic_fields', { string_false: "Test Environment", hover_false: "Switch to production environment" }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -356,10 +355,10 @@ QUnit.module('basic_fields', { QUnit.module('FieldBooleanToggle'); - QUnit.test('use boolean toggle widget in form view', function (assert) { + QUnit.test('use boolean toggle widget in form view', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -373,10 +372,10 @@ QUnit.module('basic_fields', { QUnit.module('FieldToggleButton'); - QUnit.test('use toggle_button in list view', function (assert) { + QUnit.test('use toggle_button in list view', async function (assert) { assert.expect(6); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -397,20 +396,20 @@ QUnit.module('basic_fields', { "To Report in Payslip", "inactive buttons should have proper tooltip"); // clicking on first button to check the state is properly changed - testUtils.dom.click(list.$('.o_list_view button').first()); + await testUtils.dom.click(list.$('.o_list_view button').first()); assert.containsN(list, 'button i.fa.fa-circle.o_toggle_button_success', 3, "should have 3 green buttons"); - testUtils.dom.click(list.$('.o_list_view button').first()); + await testUtils.dom.click(list.$('.o_list_view button').first()); assert.containsN(list, 'button i.fa.fa-circle.o_toggle_button_success', 4, "should have 4 green buttons"); list.destroy(); }); - QUnit.test('toggle_button in form view (edit mode)', function (assert) { + QUnit.test('toggle_button in form view (edit mode)', async function (assert) { assert.expect(6); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -434,14 +433,14 @@ QUnit.module('basic_fields', { 1, "should be green"); // click on the button to toggle the value - testUtils.dom.click(form.$('.o_field_widget[name=bar]')); + await testUtils.dom.click(form.$('.o_field_widget[name=bar]')); assert.strictEqual(form.$('.o_field_widget[name=bar] i.text-muted:not(.o_toggle_button_success)').length, 1, "should be gray"); assert.verifySteps([]); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_widget[name=bar] i.text-muted:not(.o_toggle_button_success)').length, 1, "should still be gray"); @@ -450,10 +449,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('toggle_button in form view (readonly mode)', function (assert) { + QUnit.test('toggle_button in form view (readonly mode)', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -474,7 +473,7 @@ QUnit.module('basic_fields', { 1, "should be green"); // click on the button to toggle the value - testUtils.dom.click(form.$('.o_field_widget[name=bar]')); + await testUtils.dom.click(form.$('.o_field_widget[name=bar]')); assert.strictEqual(form.$('.o_field_widget[name=bar] i.text-muted:not(.o_toggle_button_success)').length, 1, "should be gray"); @@ -485,10 +484,10 @@ QUnit.module('basic_fields', { QUnit.module('FieldFloat'); - QUnit.test('float field when unset', function (assert) { + QUnit.test('float field when unset', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -508,10 +507,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('float fields use correct digit precision', function (assert) { + QUnit.test('float fields use correct digit precision', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -529,10 +528,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('float field in form view', function (assert) { + QUnit.test('float field in form view', async function (assert) { assert.expect(5); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -549,26 +548,26 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('.o_field_widget').first().text(), '0.000', 'The value should be displayed properly.'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('input[name=qux]').val(), '0.000', 'The value should be rendered with correct precision.'); - testUtils.fields.editInput(form.$('input[name=qux]'), '108.2458938598598'); + await testUtils.fields.editInput(form.$('input[name=qux]'), '108.2458938598598'); assert.strictEqual(form.$('input[name=qux]').val(), '108.2458938598598', 'The value should not be formated yet.'); - testUtils.fields.editInput(form.$('input[name=qux]'), '18.8958938598598'); - testUtils.form.clickSave(form); + await testUtils.fields.editInput(form.$('input[name=qux]'), '18.8958938598598'); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_widget').first().text(), '18.896', 'The new value should be rounded properly.'); form.destroy(); }); - QUnit.test('float field in editable list view', function (assert) { + QUnit.test('float field in editable list view', async function (assert) { assert.expect(4); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -583,29 +582,29 @@ QUnit.module('basic_fields', { // switch to edit mode var $cell = list.$('tr.o_data_row td:not(.o_list_record_selector)').first(); - testUtils.dom.click($cell); + await testUtils.dom.click($cell); assert.containsOnce(list, 'input[name="qux"]', 'The view should have 1 input for editable float.'); - testUtils.fields.editInput(list.$('input[name="qux"]'), '108.2458938598598'); + await testUtils.fields.editInput(list.$('input[name="qux"]'), '108.2458938598598'); assert.strictEqual(list.$('input[name="qux"]').val(), '108.2458938598598', 'The value should not be formated yet.'); - testUtils.fields.editInput(list.$('input[name="qux"]'), '18.8958938598598'); - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.fields.editInput(list.$('input[name="qux"]'), '18.8958938598598'); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); assert.strictEqual(list.$('.o_field_widget').first().text(), '18.896', 'The new value should be rounded properly.'); list.destroy(); }); - QUnit.test('do not trigger a field_changed if they have not changed', function (assert) { + QUnit.test('do not trigger a field_changed if they have not changed', async function (assert) { assert.expect(2); this.data.partner.records[1].qux = false; this.data.partner.records[1].int_field = false; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -622,22 +621,22 @@ QUnit.module('basic_fields', { } }); - testUtils.form.clickEdit(form); - testUtils.form.clickSave(form); + await testUtils.form.clickEdit(form); + await testUtils.form.clickSave(form); assert.verifySteps(['read']); // should not have save as nothing changed form.destroy(); }); - QUnit.test('float widget on monetary field', function (assert) { + QUnit.test('float widget on monetary field', async function (assert) { assert.expect(1); this.data.partner.fields.monetary = {string: "Monetary", type: 'monetary'}; this.data.partner.records[0].monetary = 9.99; this.data.partner.records[0].currency_id = 1; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -659,7 +658,7 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('float field with monetary widget and decimal precision', function (assert) { + QUnit.test('float field with monetary widget and decimal precision', async function (assert) { assert.expect(5); this.data.partner.records = [{ @@ -667,7 +666,7 @@ QUnit.module('basic_fields', { qux: -8.89859, currency_id: 1, }]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -687,17 +686,17 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('.o_field_widget').first().text(), '$\u00a0-8.9', 'The value should be displayed properly.'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), '-8.9', 'The input should be rendered without the currency symbol.'); assert.strictEqual(form.$('.o_field_widget[name=qux] input').parent().children().first().text(), '$', 'The input should be preceded by a span containing the currency symbol.'); - testUtils.fields.editInput(form.$('.o_field_monetary input'), '109.2458938598598'); + await testUtils.fields.editInput(form.$('.o_field_monetary input'), '109.2458938598598'); assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), '109.2458938598598', 'The value should not be formated yet.'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); // Non-breaking space between the currency and the amount assert.strictEqual(form.$('.o_field_widget').first().text(), '$\u00a0109.2', 'The new value should be rounded properly.'); @@ -705,10 +704,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('float field with type number option', function (assert) { + QUnit.test('float field with type number option', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -722,27 +721,27 @@ QUnit.module('basic_fields', { }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.ok(form.$('.o_field_widget')[0].hasAttribute('type'), 'Float field with option type must have a type attribute.'); assert.hasAttrValue(form.$('.o_field_widget'), 'type', 'number', 'Float field with option type must have a type attribute equals to "number".'); - testUtils.fields.editInput(form.$('input[name=qux]'), '123456.7890'); - testUtils.form.clickSave(form); - testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name=qux]'), '123456.7890'); + await testUtils.form.clickSave(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_widget').val(), '123456.789', 'Float value must be not formatted if input type is number.'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_widget').text(), '123,456.8', 'Float value must be formatted in readonly view even if the input type is number.'); form.destroy(); }); - QUnit.test('float field without type number option', function (assert) { + QUnit.test('float field without type number option', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -756,13 +755,13 @@ QUnit.module('basic_fields', { }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.hasAttrValue(form.$('.o_field_widget'), 'type', 'text', 'Float field with option type must have a text type (default type).'); - testUtils.fields.editInput(form.$('input[name=qux]'), '123456.7890'); - testUtils.form.clickSave(form); - testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name=qux]'), '123456.7890'); + await testUtils.form.clickSave(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_widget').val(), '123,456.8', 'Float value must be formatted if input type isn\'t number.'); @@ -772,10 +771,10 @@ QUnit.module('basic_fields', { QUnit.module('FieldEmail'); - QUnit.test('email field in form view', function (assert) { + QUnit.test('email field in form view', async function (assert) { assert.expect(7); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -798,17 +797,17 @@ QUnit.module('basic_fields', { "should have proper mailto prefix"); // switch to edit mode and check the result - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, 'input[type="text"].o_field_widget', "should have an input for the email field"); assert.strictEqual(form.$('input[type="text"].o_field_widget').val(), 'yop', "input should contain field value in edit mode"); // change value in edit mode - testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), 'new'); + await testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), 'new'); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); $mailtoLink = form.$('a.o_form_uri.o_field_widget.o_text_overflow'); assert.strictEqual($mailtoLink.text(), 'new', "new value should be displayed properly"); @@ -818,10 +817,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('email field in editable list view', function (assert) { + QUnit.test('email field in editable list view', async function (assert) { assert.expect(10); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -841,14 +840,14 @@ QUnit.module('basic_fields', { // Edit a line and check the result var $cell = list.$('tbody td:not(.o_list_record_selector)').first(); - testUtils.dom.click($cell); + await testUtils.dom.click($cell); assert.hasClass($cell.parent(),'o_selected_row', 'should be set as edit mode'); assert.strictEqual($cell.find('input').val(), 'yop', 'should have the corect value in internal input'); - testUtils.fields.editInput($cell.find('input'), 'new'); + await testUtils.fields.editInput($cell.find('input'), 'new'); // save - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); $cell = list.$('tbody td:not(.o_list_record_selector)').first(); assert.doesNotHaveClass($cell.parent(), 'o_selected_row', 'should not be in edit mode anymore'); assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'new', @@ -865,11 +864,11 @@ QUnit.module('basic_fields', { QUnit.module('FieldChar'); - QUnit.test('char widget isValid method works', function (assert) { + QUnit.test('char widget isValid method works', async function (assert) { assert.expect(1); this.data.partner.fields.foo.required = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -884,10 +883,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('char field in form view', function (assert) { + QUnit.test('char field in form view', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -905,26 +904,26 @@ QUnit.module('basic_fields', { "the value should be displayed properly"); // switch to edit mode and check the result - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, 'input[type="text"].o_field_widget', "should have an input for the char field"); assert.strictEqual(form.$('input[type="text"].o_field_widget').val(), 'yop', "input should contain field value in edit mode"); // change value in edit mode - testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), 'limbo'); + await testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), 'limbo'); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_widget').text(), 'limbo', 'the new value should be displayed'); form.destroy(); }); - QUnit.test('setting a char field to empty string is saved as a false value', function (assert) { + QUnit.test('setting a char field to empty string is saved as a false value', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -946,18 +945,18 @@ QUnit.module('basic_fields', { } }); - testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), ''); + await testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), ''); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('char field with size attribute', function (assert) { + QUnit.test('char field with size attribute', async function (assert) { assert.expect(1); this.data.partner.fields.foo.size = 5; // max length - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -978,10 +977,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('char field in editable list view', function (assert) { + QUnit.test('char field in editable list view', async function (assert) { assert.expect(6); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -995,14 +994,14 @@ QUnit.module('basic_fields', { // Edit a line and check the result var $cell = list.$('tbody td:not(.o_list_record_selector)').first(); - testUtils.dom.click($cell); + await testUtils.dom.click($cell); assert.hasClass($cell.parent(),'o_selected_row', 'should be set as edit mode'); assert.strictEqual($cell.find('input').val(), 'yop', 'should have the corect value in internal input'); - testUtils.fields.editInput($cell.find('input'), 'brolo'); + await testUtils.fields.editInput($cell.find('input'), 'brolo'); // save - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); $cell = list.$('tbody td:not(.o_list_record_selector)').first(); assert.doesNotHaveClass($cell.parent(), 'o_selected_row', 'should not be in edit mode anymore'); assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'brolo', @@ -1010,7 +1009,7 @@ QUnit.module('basic_fields', { list.destroy(); }); - QUnit.test('char field translatable', function (assert) { + QUnit.test('char field translatable', async function (assert) { assert.expect(3); this.data.partner.fields.foo.translate = true; @@ -1018,7 +1017,7 @@ QUnit.module('basic_fields', { var multiLang = _t.database.multi_lang; _t.database.multi_lang = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1033,18 +1032,18 @@ QUnit.module('basic_fields', { mockRPC: function (route, args) { if (route === "/web/dataset/call_button" && args.method === 'translate_fields') { assert.deepEqual(args.args, ["partner",1,"foo",{}], 'should call "call_button" route'); - return $.when(); + return Promise.resolve(); } return this._super.apply(this, arguments); }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); var $button = form.$('input[type="text"].o_field_char + .o_field_translate'); assert.strictEqual($button.length, 1, "should have a translate button"); - testUtils.dom.click($button); + await testUtils.dom.click($button); form.destroy(); - form = createView({ + form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1063,10 +1062,10 @@ QUnit.module('basic_fields', { _t.database.multi_lang = multiLang; }); - QUnit.test('char field does not allow html injections', function (assert) { + QUnit.test('char field does not allow html injections', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1083,20 +1082,20 @@ QUnit.module('basic_fields', { }, }); - testUtils.fields.editInput(form.$('input[name=foo]'), '<script>throw Error();</script>'); - testUtils.form.clickSave(form); + await testUtils.fields.editInput(form.$('input[name=foo]'), '<script>throw Error();</script>'); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_widget').text(), '<script>throw Error();</script>', 'the value should have been properly escaped'); form.destroy(); }); - QUnit.test('char field trim (or not) characters', function (assert) { + QUnit.test('char field trim (or not) characters', async function (assert) { assert.expect(2); this.data.partner.fields.foo2 = {string: "Foo2", type: "char", trim: false}; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1114,13 +1113,13 @@ QUnit.module('basic_fields', { }, }); - testUtils.fields.editInput(form.$('input[name="foo"]'), ' abc '); - testUtils.fields.editInput(form.$('input[name="foo2"]'), ' def '); + await testUtils.fields.editInput(form.$('input[name="foo"]'), ' abc '); + await testUtils.fields.editInput(form.$('input[name="foo2"]'), ' def '); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); // edit mode - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('input[name="foo"]').val(), 'abc', 'Foo value should have been trimmed'); assert.strictEqual(form.$('input[name="foo2"]').val(), ' def ', 'Foo2 value should not have been trimmed'); @@ -1128,7 +1127,7 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('input field: change value before pending onchange returns', function (assert) { + QUnit.test('input field: change value before pending onchange returns', async function (assert) { assert.expect(3); this.data.partner.onchanges = { @@ -1136,7 +1135,7 @@ QUnit.module('basic_fields', { }; var def; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1154,7 +1153,7 @@ QUnit.module('basic_fields', { mockRPC: function (route, args) { var result = this._super.apply(this, arguments); if (args.method === "onchange") { - return $.when(def).then(function () { + return Promise.resolve(def).then(function () { return result; }); } else { @@ -1166,29 +1165,31 @@ QUnit.module('basic_fields', { }, }); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual(form.$('input[name="foo"]').val(), 'My little Foo Value', 'should contain the default value'); - def = $.Deferred(); + def = testUtils.makeTestPromise(); - testUtils.fields.many2one.clickOpenDropdown('product_id'); - testUtils.fields.many2one.clickHighlightedItem('product_id'); + await testUtils.fields.many2one.clickOpenDropdown('product_id'); + await testUtils.fields.many2one.clickHighlightedItem('product_id'); // set foo before onchange - testUtils.fields.editInput(form.$('input[name="foo"]'), "tralala"); + await testUtils.fields.editInput(form.$('input[name="foo"]'), "tralala"); assert.strictEqual(form.$('input[name="foo"]').val(), 'tralala', 'input should contain tralala'); // complete the onchange def.resolve(); + await testUtils.nextTick(); + assert.strictEqual(form.$('input[name="foo"]').val(), 'tralala', 'input should contain the same value as before onchange'); form.destroy(); }); - QUnit.test('input field: change value before pending onchange renaming', function (assert) { + QUnit.test('input field: change value before pending onchange renaming', async function (assert) { assert.expect(3); this.data.partner.onchanges = { @@ -1197,8 +1198,8 @@ QUnit.module('basic_fields', { }, }; - var def = $.Deferred(); - var form = createView({ + var def = testUtils.makeTestPromise(); + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1212,7 +1213,7 @@ QUnit.module('basic_fields', { mockRPC: function (route, args) { var result = this._super.apply(this, arguments); if (args.method === "onchange") { - return $.when(def).then(function () { + return def.then(function () { return result; }); } else { @@ -1227,8 +1228,8 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('input[name="foo"]').val(), 'yop', 'should contain the correct value'); - testUtils.fields.many2one.clickOpenDropdown('product_id'); - testUtils.fields.many2one.clickHighlightedItem('product_id'); + await testUtils.fields.many2one.clickOpenDropdown('product_id'); + await testUtils.fields.many2one.clickHighlightedItem('product_id'); // set foo before onchange testUtils.fields.editInput(form.$('input[name="foo"]'), "tralala"); @@ -1243,10 +1244,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('input field: change password value', function (assert) { + QUnit.test('input field: change password value', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1261,7 +1262,7 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('.o_field_char').text(), "***", "password field value should be hidden with '*' in read mode"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.hasAttrValue(form.$('input.o_field_char'), 'type', 'password', "password field input should be with type 'password' in edit mode"); @@ -1271,12 +1272,12 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('input field: empty password', function (assert) { + QUnit.test('input field: empty password', async function (assert) { assert.expect(3); this.data.partner.records[0].foo = false; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1289,7 +1290,7 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('.o_field_char').text(), "", "password field value should be empty in read mode"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.hasAttrValue(form.$('input.o_field_char'), 'type', 'password', "password field input should be with type 'password' in edit mode"); @@ -1301,10 +1302,10 @@ QUnit.module('basic_fields', { QUnit.module('UrlWidget'); - QUnit.test('url widget in form view', function (assert) { + QUnit.test('url widget in form view', async function (assert) { assert.expect(9); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1328,7 +1329,7 @@ QUnit.module('basic_fields', { "the value should be displayed properly"); // switch to edit mode and check the result - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, 'input[type="text"].o_field_widget', "should have an input for the char field"); assert.strictEqual(form.$('input[type="text"].o_field_widget').val(), 'yop', @@ -1338,7 +1339,7 @@ QUnit.module('basic_fields', { testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), 'limbo'); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.containsOnce(form, 'a.o_form_uri.o_field_widget.o_text_overflow', "should still have a anchor with correct classes"); assert.hasAttrValue(form.$('a.o_form_uri.o_field_widget.o_text_overflow'), 'href', 'limbo', @@ -1349,10 +1350,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('url widget takes text from proper attribute', function (assert) { + QUnit.test('url widget takes text from proper attribute', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1367,10 +1368,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('char field in editable list view', function (assert) { + QUnit.test('char field in editable list view', async function (assert) { assert.expect(10); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -1388,14 +1389,14 @@ QUnit.module('basic_fields', { // Edit a line and check the result var $cell = list.$('tbody td:not(.o_list_record_selector)').first(); - testUtils.dom.click($cell); + await testUtils.dom.click($cell); assert.hasClass($cell.parent(),'o_selected_row', 'should be set as edit mode'); assert.strictEqual($cell.find('input').val(), 'yop', 'should have the corect value in internal input'); - testUtils.fields.editInput($cell.find('input'), 'brolo'); + await testUtils.fields.editInput($cell.find('input'), 'brolo'); // save - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); $cell = list.$('tbody td:not(.o_list_record_selector)').first(); assert.doesNotHaveClass($cell.parent(), 'o_selected_row', 'should not be in edit mode anymore'); assert.containsN(list, 'a.o_form_uri.o_field_widget.o_text_overflow', 5, @@ -1426,10 +1427,10 @@ QUnit.module('basic_fields', { } }); - QUnit.test('Char & Text Fields: Copy to clipboard button', function (assert) { + QUnit.test('Char & Text Fields: Copy to clipboard button', async function (assert) { assert.expect(2); var done = assert.async(); - testUtils.createAsyncView({ + testUtils.createView({ View: FormView, model: 'partner', data: this.data, @@ -1451,11 +1452,11 @@ QUnit.module('basic_fields', { QUnit.module('FieldText'); - QUnit.test('text fields are correctly rendered', function (assert) { + QUnit.test('text fields are correctly rendered', async function (assert) { assert.expect(7); this.data.partner.fields.foo.type = 'text'; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1469,7 +1470,7 @@ QUnit.module('basic_fields', { assert.ok(form.$('.o_field_text').length, "should have a text area"); assert.strictEqual(form.$('.o_field_text').text(), 'yop', 'should be "yop" in readonly'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); var $textarea = form.$('textarea.o_field_text'); assert.ok($textarea.length, "should have a text area"); @@ -1481,19 +1482,19 @@ QUnit.module('basic_fields', { testUtils.fields.editInput($textarea, 'hello world'); assert.strictEqual($textarea.val(), 'hello world', 'should be "hello world" after second edition'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_text').text(), 'hello world', 'should be "hello world" after save'); form.destroy(); }); - QUnit.test('text fields in edit mode have correct height', function (assert) { + QUnit.test('text fields in edit mode have correct height', async function (assert) { assert.expect(2); this.data.partner.fields.foo.type = 'text'; this.data.partner.records[0].foo = "f\nu\nc\nk\nm\ni\nl\ng\nr\no\nm"; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1505,23 +1506,23 @@ QUnit.module('basic_fields', { var $field = form.$('.o_field_text'); - assert.strictEqual($field.outerHeight(), $field[0].scrollHeight, + assert.strictEqual($field[0].offsetHeight, $field[0].scrollHeight, "text field should not have a scroll bar"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); var $textarea = form.$('textarea:first'); // the difference is to take small calculation errors into account - assert.strictEqual($textarea.innerHeight(), $textarea[0].scrollHeight, + assert.strictEqual($textarea[0].clientHeight, $textarea[0].scrollHeight, "textarea should not have a scroll bar"); form.destroy(); }); - QUnit.test('text fields in edit mode, no vertical resize', function (assert) { + QUnit.test('text fields in edit mode, no vertical resize', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1531,7 +1532,7 @@ QUnit.module('basic_fields', { res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); var $textarea = form.$('textarea:first'); @@ -1541,12 +1542,12 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('text fields in editable list have correct height', function (assert) { + QUnit.test('text fields in editable list have correct height', async function (assert) { assert.expect(2); this.data.partner.records[0].txt = "a\nb\nc\nd\ne\nf"; - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -1560,20 +1561,20 @@ QUnit.module('basic_fields', { // the focus on the textarea by clicking on another column. // The main goal is to test the resize is actually triggered in this // particular case. - testUtils.dom.click(list.$('.o_data_cell:first')); + await testUtils.dom.click(list.$('.o_data_cell:first')); var $textarea = list.$('textarea:first'); // make sure the correct data is there assert.strictEqual($textarea.val(), this.data.partner.records[0].txt); // make sure there is no scroll bar - assert.strictEqual($textarea.innerHeight(), $textarea[0].scrollHeight, + assert.strictEqual($textarea[0].clientHeight, $textarea[0].scrollHeight, "textarea should not have a scroll bar"); list.destroy(); }); - QUnit.test('text fields in edit mode should resize on reset', function (assert) { + QUnit.test('text fields in edit mode should resize on reset', async function (assert) { assert.expect(1); this.data.partner.fields.foo.type = 'text'; @@ -1584,7 +1585,7 @@ QUnit.module('basic_fields', { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1598,9 +1599,9 @@ QUnit.module('basic_fields', { // edit the form // trigger a textarea reset (through onchange) by clicking the box // then check there is no scroll bar - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('div[name="bar"] input')); + await testUtils.dom.click(form.$('div[name="bar"] input')); var $textarea = form.$('textarea:first'); assert.strictEqual($textarea.innerHeight(), $textarea[0].scrollHeight, @@ -1609,7 +1610,7 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('text field translatable', function (assert) { + QUnit.test('text field translatable', async function (assert) { assert.expect(3); this.data.partner.fields.txt.translate = true; @@ -1617,7 +1618,7 @@ QUnit.module('basic_fields', { var multiLang = _t.database.multi_lang; _t.database.multi_lang = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1632,18 +1633,18 @@ QUnit.module('basic_fields', { mockRPC: function (route, args) { if (route === "/web/dataset/call_button" && args.method === 'translate_fields') { assert.deepEqual(args.args, ["partner",1,"txt",{}], 'should call "call_button" route'); - return $.when(); + return Promise.resolve(); } return this._super.apply(this, arguments); }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); var $button = form.$('textarea + .o_field_translate'); assert.strictEqual($button.length, 1, "should have a translate button"); - testUtils.dom.click($button); + await testUtils.dom.click($button); form.destroy(); - form = createView({ + form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1662,11 +1663,11 @@ QUnit.module('basic_fields', { _t.database.multi_lang = multiLang; }); - QUnit.test('go to next line (and not the next row) when pressing enter', function (assert) { + QUnit.test('go to next line (and not the next row) when pressing enter', async function (assert) { assert.expect(4); this.data.partner.fields.foo.type = 'text'; - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -1677,7 +1678,7 @@ QUnit.module('basic_fields', { '</list>', }); - testUtils.dom.click(list.$('tbody tr:first .o_list_text')); + await testUtils.dom.click(list.$('tbody tr:first .o_list_text')); var $textarea = list.$('textarea.o_field_text'); assert.strictEqual($textarea.length, 1, "should have a text area"); assert.strictEqual($textarea.val(), 'yop', 'should still be "yop" in edit'); @@ -1699,10 +1700,10 @@ QUnit.module('basic_fields', { // Firefox-specific // Copying from <div style="white-space:pre-wrap"> does not keep line breaks // See https://bugzilla.mozilla.org/show_bug.cgi?id=1390115 - QUnit.test('copying text fields in RO mode should preserve line breaks', function (assert) { + QUnit.test('copying text fields in RO mode should preserve line breaks', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1725,7 +1726,7 @@ QUnit.module('basic_fields', { QUnit.module('FieldBinary'); - QUnit.test('binary fields are correctly rendered', function (assert) { + QUnit.test('binary fields are correctly rendered', async function (assert) { assert.expect(16); // save the session function @@ -1736,11 +1737,11 @@ QUnit.module('basic_fields', { assert.strictEqual(option.data.data, 'coucou==\n', "we should download the correct data"); option.complete(); - return $.when(); + return Promise.resolve(); }; this.data.partner.records[0].foo = 'coucou.txt'; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1758,9 +1759,9 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('.o_field_char').text(), 'coucou.txt', "the filename field should have the file name as value"); - testUtils.dom.click(form.$('a.o_field_widget[name="document"]')); + await testUtils.dom.click(form.$('a.o_field_widget[name="document"]')); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsNone(form, 'a.o_field_widget[name="document"] > .fa-download', "the binary field should not be rendered as a downloadable link in edit"); @@ -1774,7 +1775,7 @@ QUnit.module('basic_fields', { "the filename field should have the file name as value"); - testUtils.dom.click(form.$('.o_field_binary_file > .o_clear_file_button')); + await testUtils.dom.click(form.$('.o_field_binary_file > .o_clear_file_button')); assert.isNotVisible(form.$('.o_field_binary_file > input'), "the input should be hidden"); @@ -1783,7 +1784,7 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('input.o_field_char').val(), '', "the filename field should be empty since we removed the file"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.containsNone(form, 'a.o_field_widget[name="document"] > .fa-download', "the binary field should not render as a downloadable link since we removed the file"); assert.strictEqual(form.$('a.o_field_widget[name="document"]').text().trim(), '', @@ -1797,7 +1798,7 @@ QUnit.module('basic_fields', { session.get_file = oldGetFile; }); - QUnit.test('binary fields that are readonly in create mode do not download', function (assert) { + QUnit.test('binary fields that are readonly in create mode do not download', async function (assert) { assert.expect(2); // save the session function @@ -1815,7 +1816,7 @@ QUnit.module('basic_fields', { this.data.partner.fields.document.readonly = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1826,9 +1827,9 @@ QUnit.module('basic_fields', { res_id: 1, }); - testUtils.form.clickCreate(form); - testUtils.fields.many2one.clickOpenDropdown('product_id'); - testUtils.fields.many2one.clickHighlightedItem('product_id'); + await testUtils.form.clickCreate(form); + await testUtils.fields.many2one.clickOpenDropdown('product_id'); + await testUtils.fields.many2one.clickHighlightedItem('product_id'); assert.containsOnce(form, 'a.o_field_widget[name="document"] > .fa-download', 'The link to download the binary should be present'); @@ -1843,10 +1844,10 @@ QUnit.module('basic_fields', { QUnit.module('FieldPdfViewer'); - QUnit.test("pdf_viewer without data", function (assert) { + QUnit.test("pdf_viewer without data", async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1867,10 +1868,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test("pdf_viewer: basic rendering", function (assert) { + QUnit.test("pdf_viewer: basic rendering", async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1881,7 +1882,7 @@ QUnit.module('basic_fields', { '</form>', mockRPC: function (route) { if (route.indexOf('/web/static/lib/pdfjs/web/viewer.html') !== -1) { - return $.when(); + return Promise.resolve(); } return this._super.apply(this, arguments); } @@ -1899,7 +1900,7 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test("pdf_viewer: upload rendering", function (assert) { + QUnit.test("pdf_viewer: upload rendering", async function (assert) { assert.expect(6); testUtils.mock.patch(field_registry.map.pdf_viewer, { @@ -1908,7 +1909,7 @@ QUnit.module('basic_fields', { this._super.apply(this, arguments); }, _getURI: function (fileURI) { - var res = this._super.apply(this, arguments); + this._super.apply(this, arguments); assert.step('_getURI'); assert.ok(_.str.startsWith(fileURI, 'blob:')); this.PDFViewerApplication = { @@ -1921,7 +1922,7 @@ QUnit.module('basic_fields', { }, }); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1936,13 +1937,13 @@ QUnit.module('basic_fields', { assert.verifySteps(['_getURI']); // second upload call pdfjs method inside iframe form.$('input[type="file"]').trigger('change'); - assert.verifySteps(['_getURI', 'open']); + assert.verifySteps(['open']); testUtils.mock.unpatch(field_registry.map.pdf_viewer); form.destroy(); }); - QUnit.test('text field rendering in list view', function (assert) { + QUnit.test('text field rendering in list view', async function (assert) { assert.expect(1); var data = { @@ -1951,7 +1952,7 @@ QUnit.module('basic_fields', { records: [{id: 1, foo: "some text"}] }, }; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: data, @@ -1963,12 +1964,12 @@ QUnit.module('basic_fields', { list.destroy(); }); - QUnit.test('field text in editable list view', function (assert) { + QUnit.test('field text in editable list view', async function (assert) { assert.expect(1); this.data.partner.fields.foo.type = 'text'; - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -1977,22 +1978,22 @@ QUnit.module('basic_fields', { '</tree>', }); - testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); assert.strictEqual(list.$('textarea').first().get(0), document.activeElement, "text area should have the focus"); list.destroy(); }); - QUnit.test('field changes are correctly debounced in text fields', function (assert) { + QUnit.test('field changes are correctly debounced in text fields', async function (assert) { var done = assert.async(); assert.expect(5); this.data.partner.fields.foo.type = 'text'; - var def = $.Deferred(); + var def = testUtils.makeTestPromise(); var nbNotifyChanges = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2010,7 +2011,7 @@ QUnit.module('basic_fields', { def.resolve(); }; - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); testUtils.fields.editInput(form.$('textarea').first(), "1"); assert.strictEqual(nbNotifyChanges, 0, @@ -2019,13 +2020,12 @@ QUnit.module('basic_fields', { assert.strictEqual(nbNotifyChanges, 0, "no event should have been triggered"); - return waitForChangeTriggered().then(function () { + return waitForChangeTriggered().then(async function () { assert.strictEqual(nbNotifyChanges, 1, "one event should have been triggered"); // add something in the textarea, then focus another input - testUtils.fields.editInput(form.$('textarea').first(), "123"); - form.$('textarea').first().change(); + await testUtils.fields.editInput(form.$('textarea').first(), "123"); assert.strictEqual(nbNotifyChanges, 2, "one event should have been triggered immediately"); @@ -2040,7 +2040,7 @@ QUnit.module('basic_fields', { function waitForChangeTriggered() { return def.then(function () { - def = $.Deferred(); + def = testUtils.makeTestPromise(); return concurrency.delay(0); }); } @@ -2048,13 +2048,13 @@ QUnit.module('basic_fields', { QUnit.module('FieldImage'); - QUnit.test('image fields are correctly rendered', function (assert) { + QUnit.test('image fields are correctly rendered', async function (assert) { assert.expect(7); this.data.partner.records[0].__last_update = '2017-02-08 10:00:00'; this.data.partner.records[0].document = 'myimage'; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2068,7 +2068,7 @@ QUnit.module('basic_fields', { } if (route === 'data:image/png;base64,myimage') { assert.ok(true, "should called the correct route"); - return $.when('wow'); + return Promise.resolve('wow'); } return this._super.apply(this, arguments); }, @@ -2087,7 +2087,7 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('image fields in subviews are loaded correctly', function (assert) { + QUnit.test('image fields in subviews are loaded correctly', async function (assert) { assert.expect(6); this.data.partner.records[0].__last_update = '2017-02-08 10:00:00'; @@ -2096,7 +2096,7 @@ QUnit.module('basic_fields', { this.data.partner_type.records[0].image = 'product_image'; this.data.partner.records[0].timmy = [12]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2112,14 +2112,14 @@ QUnit.module('basic_fields', { '</field>' + '</form>', res_id: 1, - mockRPC: function (route, args) { + mockRPC: function (route) { if (route === 'data:image/png;base64,myimage') { assert.step("The view's image should have been fetched"); - return $.when('wow'); + return Promise.resolve('wow'); } if (route === 'data:image/png;base64,product_image') { assert.step("The dialog's image should have been fetched"); - return $.when(); + return Promise.resolve(); } return this._super.apply(this, arguments); }, @@ -2130,25 +2130,22 @@ QUnit.module('basic_fields', { 'There should be one record in the many2many'); // Actual flow: click on an element of the m2m to get its form view - testUtils.dom.click(form.$('tbody td:contains(gold)')); + await testUtils.dom.click(form.$('tbody td:contains(gold)')); assert.strictEqual($('.modal').length, 1, 'The modal should have opened'); - assert.verifySteps([ - "The view's image should have been fetched", - "The dialog's image should have been fetched", - ]); + assert.verifySteps(["The dialog's image should have been fetched"]); form.destroy(); }); - QUnit.test('image fields in x2many list are loaded correctly', function (assert) { + QUnit.test('image fields in x2many list are loaded correctly', async function (assert) { assert.expect(2); this.data.partner_type.fields.image = {name: 'image', type: 'binary'}; this.data.partner_type.records[0].image = 'product_image'; this.data.partner.records[0].timmy = [12]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2160,10 +2157,10 @@ QUnit.module('basic_fields', { '</field>' + '</form>', res_id: 1, - mockRPC: function (route, args) { + mockRPC: function (route) { if (route === 'data:image/png;base64,product_image') { assert.ok(true, "The list's image should have been fetched"); - return $.when(); + return Promise.resolve(); } return this._super.apply(this, arguments); }, @@ -2175,10 +2172,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('image fields with required attribute', function (assert) { + QUnit.test('image fields with required attribute', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2193,7 +2190,7 @@ QUnit.module('basic_fields', { }, }); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.hasClass(form.$('.o_form_view'),'o_form_editable', "form view should still be editable"); @@ -2237,7 +2234,7 @@ QUnit.module('basic_fields', { }, }); - QUnit.test('graph dashboard widget attach/detach callbacks', function (assert) { + QUnit.test('graph dashboard widget attach/detach callbacks', async function (assert) { // This widget is rendered with nvd3, and nvd3 renders the graphs when // the svg is in the DOM. The intent of this test is to determine when // the field widgets are in the DOM, so that we can tell nvd3 to render @@ -2254,7 +2251,7 @@ QUnit.module('basic_fields', { }, }); - createAsyncView({ + createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2276,8 +2273,6 @@ QUnit.module('basic_fields', { kanban.on_detach_callback(); assert.verifySteps([ - 'on_attach_callback', - 'on_attach_callback', 'on_detach_callback', 'on_detach_callback' ]); @@ -2288,12 +2283,12 @@ QUnit.module('basic_fields', { }); }); - QUnit.test('graph dashboard widget is rendered correctly', function (assert) { + QUnit.test('graph dashboard widget is rendered correctly', async function (assert) { var done = assert.async(); assert.expect(4); var graph_key = JSON.parse(this.data.partner.records[0].graph_data)[0].key; - createAsyncView({ + createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2343,7 +2338,7 @@ QUnit.module('basic_fields', { }); }); - QUnit.test('graph dashboard widget does not need nv to be destroyed', function (assert) { + QUnit.test('graph dashboard widget does not need nv to be destroyed', async function (assert) { // this test ensures that the JournalDashboardGraph widget doesn't crash // when being destroyed before nv has been loaded assert.expect(2); @@ -2358,7 +2353,7 @@ QUnit.module('basic_fields', { } }); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2382,7 +2377,7 @@ QUnit.module('basic_fields', { return concurrency.delay(0); }); - QUnit.test('graph dashboard widget can be destroyed when nv is partially loaded', function (assert) { + QUnit.test('graph dashboard widget can be destroyed when nv is partially loaded', async function (assert) { // this test ensures that the JournalDashboardGraph widget doesn't crash // when being destroyed before nv has been completely loaded assert.expect(2); @@ -2400,7 +2395,7 @@ QUnit.module('basic_fields', { }, }); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2424,12 +2419,12 @@ QUnit.module('basic_fields', { return concurrency.delay(0); }); - QUnit.test('rendering of a field with dashboard_graph widget in an updated kanban view (ungrouped)', function (assert) { + QUnit.test('rendering of a field with dashboard_graph widget in an updated kanban view (ungrouped)', async function (assert) { var done = assert.async(); assert.expect(2); - createAsyncView({ + createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2460,12 +2455,12 @@ QUnit.module('basic_fields', { }); }); - QUnit.test('rendering of a field with dashboard_graph widget in an updated kanban view (grouped)', function (assert) { + QUnit.test('rendering of a field with dashboard_graph widget in an updated kanban view (grouped)', async function (assert) { var done = assert.async(); assert.expect(2); - createAsyncView({ + createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2496,12 +2491,12 @@ QUnit.module('basic_fields', { QUnit.module('AceEditor'); - QUnit.test('ace widget on text fields works', function (assert) { + QUnit.test('ace widget on text fields works', async function (assert) { assert.expect(2); var done = assert.async(); this.data.partner.fields.foo.type = 'text'; - testUtils.createAsyncView({ + testUtils.createView({ View: FormView, model: 'partner', data: this.data, @@ -2519,11 +2514,11 @@ QUnit.module('basic_fields', { QUnit.module('HandleWidget'); - QUnit.test('handle widget in x2m', function (assert) { + QUnit.test('handle widget in x2m', async function (assert) { assert.expect(6); this.data.partner.records[0].p = [2, 4]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2546,7 +2541,7 @@ QUnit.module('basic_fields', { assert.containsN(form, 'span.o_row_handle', 2, "should have 2 handles"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.hasClass(form.$('td:first'),'o_handle_cell', "column widget should be displayed in css class"); @@ -2560,10 +2555,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('handle widget with falsy values', function (assert) { + QUnit.test('handle widget with falsy values', async function (assert) { assert.expect(1); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -2581,10 +2576,10 @@ QUnit.module('basic_fields', { QUnit.module('FieldDate'); - QUnit.test('date field: toggle datepicker', function (assert) { + QUnit.test('date field: toggle datepicker [REQUIRE FOCUS]', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2597,13 +2592,13 @@ QUnit.module('basic_fields', { assert.strictEqual($('.bootstrap-datetimepicker-widget:visible').length, 0, "datepicker should be closed initially"); - testUtils.dom.openDatepicker(form.$('.o_datepicker')); + await testUtils.dom.openDatepicker(form.$('.o_datepicker')); assert.strictEqual($('.bootstrap-datetimepicker-widget:visible').length, 1, "datepicker should be opened"); // focus another field - testUtils.dom.click(form.$('.o_field_widget[name=foo]').focus().mouseenter()); + await testUtils.dom.click(form.$('.o_field_widget[name=foo]').focus().mouseenter()); assert.strictEqual($('.bootstrap-datetimepicker-widget:visible').length, 0, "datepicker should close itself when the user clicks outside"); @@ -2611,10 +2606,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('date field is empty if no date is set', function (assert) { + QUnit.test('date field is empty if no date is set', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2627,10 +2622,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('date field value should not set on first click', function (assert) { + QUnit.test('date field value should not set on first click', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2638,7 +2633,7 @@ QUnit.module('basic_fields', { res_id: 4, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // open datepicker and select a date testUtils.dom.openDatepicker(form.$('.o_datepicker')); @@ -2653,10 +2648,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('date field in form view (with positive time zone offset)', function (assert) { + QUnit.test('date field in form view (with positive time zone offset)', async function (assert) { assert.expect(8); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2682,7 +2677,7 @@ QUnit.module('basic_fields', { 'the date should be correctly displayed in readonly'); // switch to edit mode - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_datepicker_input').val(), '02/03/2017', 'the date should be correct in edit mode'); @@ -2700,16 +2695,16 @@ QUnit.module('basic_fields', { 'the selected date should be displayed in the input'); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_date').text(), '02/22/2017', 'the selected date should be displayed after saving'); form.destroy(); }); - QUnit.test('date field in form view (with negative time zone offset)', function (assert) { + QUnit.test('date field in form view (with negative time zone offset)', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2729,17 +2724,17 @@ QUnit.module('basic_fields', { 'the date should be correctly displayed in readonly'); // switch to edit mode - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_datepicker_input').val(), '02/03/2017', 'the date should be correct in edit mode'); form.destroy(); }); - QUnit.test('date field with warn_future option', function (assert) { + QUnit.test('date field with warn_future option', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2750,7 +2745,7 @@ QUnit.module('basic_fields', { }); // switch to edit mode - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // open datepicker and select another value testUtils.dom.openDatepicker(form.$('.o_datepicker')); testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first()); @@ -2770,7 +2765,7 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('date field with warn_future option: do not overwrite datepicker option', function (assert) { + QUnit.test('date field with warn_future option: do not overwrite datepicker option', async function (assert) { assert.expect(2); // Making sure we don't have a legit default value @@ -2778,7 +2773,7 @@ QUnit.module('basic_fields', { this.data.partner.fields.date.default = undefined; this.data.partner.onchanges = {}; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2790,15 +2785,15 @@ QUnit.module('basic_fields', { }); // switch to edit mode - form.$buttons.find('.o_form_button_edit').click(); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('input[name="date"]').val(), '02/03/2017', - 'The existing record should have a value for the date field'); + 'The existing record should have a value for the date field'); // save with no changes - form.$buttons.find('.o_form_button_save').click(); + await testUtils.form.clickSave(form); //Create a new record - form.$buttons.find('.o_form_button_create').click(); + await testUtils.form.clickCreate(form); assert.notOk(form.$('input[name="date"]').val(), 'The new record should not have a value that the framework would have set'); @@ -2806,10 +2801,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('date field in editable list view', function (assert) { + QUnit.test('date field in editable list view', async function (assert) { assert.expect(8); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -2829,7 +2824,7 @@ QUnit.module('basic_fields', { var $cell = list.$('tr.o_data_row td:not(.o_list_record_selector)').first(); assert.strictEqual($cell.text(), '02/03/2017', 'the date should be displayed correctly in readonly'); - testUtils.dom.click($cell); + await testUtils.dom.click($cell); assert.containsOnce(list, 'input.o_datepicker_input', "the view should have a date input for editable mode"); @@ -2841,29 +2836,29 @@ QUnit.module('basic_fields', { 'the date should be correct in edit mode'); // open datepicker and select another value - testUtils.dom.openDatepicker(list.$('.o_datepicker')); + await testUtils.dom.openDatepicker(list.$('.o_datepicker')); assert.ok($('.bootstrap-datetimepicker-widget').length, 'datepicker should be open'); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first()); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .year:contains(2017)')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(1)); - testUtils.dom.click($('.day:contains(22)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first()); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .year:contains(2017)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(1)); + await testUtils.dom.click($('.day:contains(22)')); assert.ok(!$('.bootstrap-datetimepicker-widget').length, 'datepicker should be closed'); assert.strictEqual(list.$('.o_datepicker_input').val(), '02/22/2017', 'the selected date should be displayed in the input'); // save - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); assert.strictEqual(list.$('tr.o_data_row td:not(.o_list_record_selector)').text(), '02/22/2017', 'the selected date should be displayed after saving'); list.destroy(); }); - QUnit.test('date field remove value', function (assert) { + QUnit.test('date field remove value', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2881,7 +2876,7 @@ QUnit.module('basic_fields', { }); // switch to edit mode - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_datepicker_input').val(), '02/03/2017', 'the date should be correct in edit mode'); @@ -2890,17 +2885,17 @@ QUnit.module('basic_fields', { 'should have correctly removed the value'); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_date').text(), '', 'the selected date should be displayed after saving'); form.destroy(); }); - QUnit.test('do not trigger a field_changed for datetime field with date widget', function (assert) { + QUnit.test('do not trigger a field_changed for datetime field with date widget', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2923,18 +2918,18 @@ QUnit.module('basic_fields', { 'the date should be correct'); testUtils.fields.editAndTrigger(form.$('input[name="datetime"]'),'02/08/2017', ['input', 'change', 'focusout']); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.verifySteps(['read']); // should not have save as nothing changed form.destroy(); }); - QUnit.test('field date should select its content onclick when there is one', function (assert) { + QUnit.test('field date should select its content onclick when there is one', async function (assert) { assert.expect(2); var done = assert.async(); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2962,7 +2957,7 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('date field support internalization', function (assert) { + QUnit.test('date field support internalization', async function (assert) { assert.expect(2); var originalLocale = moment.locale(); @@ -2976,7 +2971,7 @@ QUnit.module('basic_fields', { ordinal: '%d.', }); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2985,14 +2980,14 @@ QUnit.module('basic_fields', { }); var dateViewForm = form.$('.o_field_date').text(); - testUtils.dom.click(form.$buttons.find('.o_form_button_edit')); - testUtils.openDatepicker(form.$('.o_datepicker')); + await testUtils.dom.click(form.$buttons.find('.o_form_button_edit')); + await testUtils.openDatepicker(form.$('.o_datepicker')); assert.strictEqual(form.$('.o_datepicker_input').val(), dateViewForm, "input date field should be the same as it was in the view form"); - testUtils.dom.click($('.day:contains(30)')); - var dateEditForm = form.$('.o_datepicker_input').val() - testUtils.dom.click(form.$buttons.find('.o_form_button_save')); + await testUtils.dom.click($('.day:contains(30)')); + var dateEditForm = form.$('.o_datepicker_input').val(); + await testUtils.dom.click(form.$buttons.find('.o_form_button_save')); assert.strictEqual(form.$('.o_field_date').text(), dateEditForm, "date field should be the same as the one selected in the view form"); @@ -3005,10 +3000,10 @@ QUnit.module('basic_fields', { QUnit.module('FieldDatetime'); - QUnit.test('datetime field in form view', function (assert) { + QUnit.test('datetime field in form view', async function (assert) { assert.expect(6); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3030,23 +3025,23 @@ QUnit.module('basic_fields', { 'the datetime should be correctly displayed in readonly'); // switch to edit mode - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_datepicker_input').val(), expectedDateString, 'the datetime should be correct in edit mode'); // select 22 February at 8:23:33 assert.ok($('.bootstrap-datetimepicker-widget').length, 'datepicker should be open'); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first()); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .year:contains(2017)')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(3)); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .day:contains(22)')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .fa-clock-o')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hour')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .hour:contains(08)')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-minute')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .minute:contains(25)')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-second')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .second:contains(35)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first()); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .year:contains(2017)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(3)); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .day:contains(22)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .fa-clock-o')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hour')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .hour:contains(08)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-minute')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .minute:contains(25)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-second')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .second:contains(35)')); assert.ok(!$('.bootstrap-datetimepicker-widget').length, 'datepicker should be closed'); var newExpectedDateString = "04/22/2017 08:25:35"; @@ -3054,7 +3049,7 @@ QUnit.module('basic_fields', { 'the selected date should be displayed in the input'); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_date').text(), newExpectedDateString, 'the selected date should be displayed after saving'); @@ -3062,10 +3057,10 @@ QUnit.module('basic_fields', { }); - QUnit.test('datetime field not visible in form view should not capture the focus on keyboard navigation', function (assert) { + QUnit.test('datetime field not visible in form view should not capture the focus on keyboard navigation', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3086,13 +3081,13 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('datetime field with datetime formatted without second', function (assert) { + QUnit.test('datetime field with datetime formatted without second', async function (assert) { assert.expect(2); this.data.partner.fields.datetime.default = "2017-08-02 12:00:05"; this.data.partner.fields.datetime.required = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3107,7 +3102,7 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('.o_field_date input').val(), expectedDateString, 'the datetime should be correctly displayed in readonly'); - testUtils.form.clickDiscard(form); + await testUtils.form.clickDiscard(form); assert.strictEqual($('.modal').length, 0, "there should not be a Warning dialog"); @@ -3115,10 +3110,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('datetime field in editable list view', function (assert) { + QUnit.test('datetime field in editable list view', async function (assert) { assert.expect(8); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -3142,7 +3137,7 @@ QUnit.module('basic_fields', { 'the datetime should be correctly displayed in readonly'); // switch to edit mode - testUtils.dom.click($cell); + await testUtils.dom.click($cell); assert.containsOnce(list, 'input.o_datepicker_input', "the view should have a date input for editable mode"); @@ -3154,18 +3149,18 @@ QUnit.module('basic_fields', { // select 22 February at 8:23:33 assert.ok($('.bootstrap-datetimepicker-widget').length, 'datepicker should be open'); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first()); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .year:contains(2017)')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(3)); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .day:contains(22)')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .fa-clock-o')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hour')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .hour:contains(08)')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-minute')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .minute:contains(25)')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-second')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .second:contains(35)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first()); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .year:contains(2017)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(3)); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .day:contains(22)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .fa-clock-o')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hour')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .hour:contains(08)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-minute')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .minute:contains(25)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-second')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .second:contains(35)')); assert.ok(!$('.bootstrap-datetimepicker-widget').length, 'datepicker should be closed'); var newExpectedDateString = "04/22/2017 08:25:35"; @@ -3173,17 +3168,17 @@ QUnit.module('basic_fields', { 'the selected datetime should be displayed in the input'); // save - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); assert.strictEqual(list.$('tr.o_data_row td:not(.o_list_record_selector)').text(), newExpectedDateString, 'the selected datetime should be displayed after saving'); list.destroy(); }); - QUnit.test('datetime field remove value', function (assert) { + QUnit.test('datetime field remove value', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3207,29 +3202,29 @@ QUnit.module('basic_fields', { }); // switch to edit mode - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_datepicker_input').val(), '02/08/2017 12:00:00', 'the date time should be correct in edit mode'); - testUtils.fields.editAndTrigger($('.o_datepicker_input'), '', ['input', 'change', 'focusout']); + await testUtils.fields.editAndTrigger($('.o_datepicker_input'), '', ['input', 'change', 'focusout']); assert.strictEqual(form.$('.o_datepicker_input').val(), '', "should have an empty input"); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_date').text(), '', 'the selected date should be displayed after saving'); form.destroy(); }); - QUnit.test('datetime field with date/datetime widget (with day change)', function (assert) { + QUnit.test('datetime field with date/datetime widget (with day change)', async function (assert) { assert.expect(2); this.data.partner.records[0].p = [2]; this.data.partner.records[1].datetime = "2017-02-08 02:00:00"; // UTC - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3260,20 +3255,20 @@ QUnit.module('basic_fields', { 'the datetime (datetime widget) should be correctly displayed in tree view'); // switch to form view - testUtils.dom.click(form.$('.o_field_widget[name=p] .o_data_row')); + await testUtils.dom.click(form.$('.o_field_widget[name=p] .o_data_row')); assert.strictEqual($('.modal .o_field_date[name=datetime]').text(), '02/07/2017', 'the datetime (date widget) should be correctly displayed in form view'); form.destroy(); }); - QUnit.test('datetime field with date/datetime widget (without day change)', function (assert) { + QUnit.test('datetime field with date/datetime widget (without day change)', async function (assert) { assert.expect(2); this.data.partner.records[0].p = [2]; this.data.partner.records[1].datetime = "2017-02-08 10:00:00"; // without timezone - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3304,20 +3299,20 @@ QUnit.module('basic_fields', { 'the datetime (datetime widget) should be correctly displayed in tree view'); // switch to form view - testUtils.dom.click(form.$('.o_field_widget[name=p] .o_data_row')); + await testUtils.dom.click(form.$('.o_field_widget[name=p] .o_data_row')); assert.strictEqual($('.modal .o_field_date[name=datetime]').text(), '02/08/2017', 'the datetime (date widget) should be correctly displayed in form view'); form.destroy(); }); - QUnit.test('datepicker option: daysOfWeekDisabled', function (assert) { + QUnit.test('datepicker option: daysOfWeekDisabled', async function (assert) { assert.expect(42); this.data.partner.fields.datetime.default = "2017-08-02 12:00:05"; this.data.partner.fields.datetime.required = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3328,7 +3323,7 @@ QUnit.module('basic_fields', { res_id: 1, }); - testUtils.form.clickCreate(form); + await testUtils.form.clickCreate(form); $.each($('.day:last-child(),.day:nth-child(2)'), function (index, value) { assert.hasClass(value, 'disabled', 'first and last days must be disabled'); }); @@ -3343,10 +3338,10 @@ QUnit.module('basic_fields', { QUnit.module('FieldMonetary'); - QUnit.test('monetary field in form view', function (assert) { + QUnit.test('monetary field in form view', async function (assert) { assert.expect(5); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3366,17 +3361,17 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('.o_field_widget').first().text(), '$\u00a09.10', 'The value should be displayed properly.'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), '9.10', 'The input should be rendered without the currency symbol.'); assert.strictEqual(form.$('.o_field_widget[name=qux] input').parent().children().first().text(), '$', 'The input should be preceded by a span containing the currency symbol.'); - testUtils.fields.editInput(form.$('.o_field_monetary input'), '108.2458938598598'); + await testUtils.fields.editInput(form.$('.o_field_monetary input'), '108.2458938598598'); assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), '108.2458938598598', 'The value should not be formated yet.'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); // Non-breaking space between the currency and the amount assert.strictEqual(form.$('.o_field_widget').first().text(), '$\u00a0108.25', 'The new value should be rounded properly.'); @@ -3384,10 +3379,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('monetary field with currency symbol after', function (assert) { + QUnit.test('monetary field with currency symbol after', async function (assert) { assert.expect(5); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3407,17 +3402,17 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('.o_field_widget').first().text(), '0.00\u00a0€', 'The value should be displayed properly.'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), '0.00', 'The input should be rendered without the currency symbol.'); assert.strictEqual(form.$('.o_field_widget[name=qux] input').parent().children().eq(1).text(), '€', 'The input should be followed by a span containing the currency symbol.'); - testUtils.fields.editInput(form.$('.o_field_widget[name=qux] input'), '108.2458938598598'); + await testUtils.fields.editInput(form.$('.o_field_widget[name=qux] input'), '108.2458938598598'); assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), '108.2458938598598', 'The value should not be formated yet.'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); // Non-breaking space between the currency and the amount assert.strictEqual(form.$('.o_field_widget').first().text(), '108.25\u00a0€', 'The new value should be rounded properly.'); @@ -3425,7 +3420,7 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('monetary field with currency digits != 2', function (assert) { + QUnit.test('monetary field with currency digits != 2', async function (assert) { assert.expect(5); this.data.partner.records = [{ @@ -3444,7 +3439,7 @@ QUnit.module('basic_fields', { digits: [16, 4], }]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3464,17 +3459,17 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('.o_field_widget').first().text(), '99.1234\u00a0Bs.F', 'The value should be displayed properly.'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), '99.1234', 'The input should be rendered without the currency symbol.'); assert.strictEqual(form.$('.o_field_widget[name=qux] input').parent().children().eq(1).text(), 'Bs.F', 'The input should be followed by a span containing the currency symbol.'); - testUtils.fields.editInput(form.$('.o_field_widget[name=qux] input'), '99.111111111'); + await testUtils.fields.editInput(form.$('.o_field_widget[name=qux] input'), '99.111111111'); assert.strictEqual(form.$('.o_field_widget[name=qux] input').val(), '99.111111111', 'The value should not be formated yet.'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); // Non-breaking space between the currency and the amount assert.strictEqual(form.$('.o_field_widget').first().text(), '99.1111\u00a0Bs.F', 'The new value should be rounded properly.'); @@ -3482,10 +3477,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('monetary field in editable list view', function (assert) { + QUnit.test('monetary field in editable list view', async function (assert) { assert.expect(9); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -3512,7 +3507,7 @@ QUnit.module('basic_fields', { // switch to edit mode var $cell = list.$('tr.o_data_row td:not(.o_list_record_selector):contains($)'); - testUtils.dom.click($cell); + await testUtils.dom.click($cell); assert.strictEqual($cell.children().length, 1, 'The cell td should only contain the special div of monetary widget.'); @@ -3523,18 +3518,18 @@ QUnit.module('basic_fields', { assert.strictEqual(list.$('[name="qux"] input').parent().children().first().text(), '$', 'The input should be preceded by a span containing the currency symbol.'); - testUtils.fields.editInput(list.$('[name="qux"] input'), '108.2458938598598'); + await testUtils.fields.editInput(list.$('[name="qux"] input'), '108.2458938598598'); assert.strictEqual(list.$('[name="qux"] input').val(), '108.2458938598598', 'The typed value should be correctly displayed.'); - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); assert.strictEqual(list.$('tr.o_data_row td:not(.o_list_record_selector):contains($)').text(), '$\u00a0108.25', 'The new value should be rounded properly.'); list.destroy(); }); - QUnit.test('monetary field with real monetary field in model', function (assert) { + QUnit.test('monetary field with real monetary field in model', async function (assert) { assert.expect(7); this.data.partner.fields.qux.type = "monetary"; @@ -3542,7 +3537,7 @@ QUnit.module('basic_fields', { string: "Quux", type: "monetary", digits: [16,1], searchable: true, readonly: true, }; - (_.find(this.data.partner.records, function (record) { return record.id === 5 })).quux = 4.2; + (_.find(this.data.partner.records, function (record) { return record.id === 5; })).quux = 4.2; this.data.partner.onchanges = { bar: function (obj) { @@ -3550,7 +3545,7 @@ QUnit.module('basic_fields', { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3573,19 +3568,19 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('.o_field_monetary').first().next().html(), "$ 4.20", "readonly value should contain the currency"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_monetary > input').val(), "9.10", "input value in edition should only contain the value, without the currency"); - testUtils.dom.click(form.$('input[type="checkbox"]')); + await testUtils.dom.click(form.$('input[type="checkbox"]')); assert.containsOnce(form, '.o_field_monetary > input', "After the onchange, the monetary <input/> should not have been duplicated"); assert.containsOnce(form, '.o_field_monetary[name=quux]', "After the onchange, the monetary readonly field should not have been duplicated"); - testUtils.fields.many2one.clickOpenDropdown('currency_id'); - testUtils.fields.many2one.clickItem('currency_id','€'); + await testUtils.fields.many2one.clickOpenDropdown('currency_id'); + await testUtils.fields.many2one.clickItem('currency_id','€'); assert.strictEqual(form.$('.o_field_monetary > span').html(), "€", "After currency change, the monetary field currency should have been updated"); assert.strictEqual(form.$('.o_field_monetary').first().next().html(), "4.20 €", @@ -3594,7 +3589,7 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('monetary field with monetary field given in options', function (assert) { + QUnit.test('monetary field with monetary field given in options', async function (assert) { assert.expect(1); this.data.partner.fields.qux.type = "monetary"; @@ -3603,7 +3598,7 @@ QUnit.module('basic_fields', { }; this.data.partner.records[4].company_currency_id = 2; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3625,14 +3620,14 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('should keep the focus when being edited in x2many lists', function (assert) { + QUnit.test('should keep the focus when being edited in x2many lists', async function (assert) { assert.expect(6); this.data.partner.fields.currency_id.default = 1; this.data.partner.fields.m2m = { string: "m2m", type: "many2many", relation: 'partner', default: [[6, false, [2]]], }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3655,30 +3650,30 @@ QUnit.module('basic_fields', { // test the monetary field inside the one2many var $o2m = form.$('.o_field_widget[name=p]'); - testUtils.dom.click($o2m.find('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput($o2m.find('.o_field_widget input'), "22"); + await testUtils.dom.click($o2m.find('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput($o2m.find('.o_field_widget input'), "22"); assert.strictEqual($o2m.find('.o_field_widget input').get(0), document.activeElement, "the focus should still be on the input"); assert.strictEqual($o2m.find('.o_field_widget input').val(), "22", "the value should not have been formatted yet"); - testUtils.dom.click(form.$el); + await testUtils.dom.click(form.$el); assert.strictEqual($o2m.find('.o_field_widget[name=qux]').html(), "$ 22.00", "the value should have been formatted after losing the focus"); // test the monetary field inside the many2many var $m2m = form.$('.o_field_widget[name=m2m]'); - testUtils.dom.click($m2m.find('.o_data_row td:first')); - testUtils.fields.editInput($m2m.find('.o_field_widget input'), "22"); + await testUtils.dom.click($m2m.find('.o_data_row td:first')); + await testUtils.fields.editInput($m2m.find('.o_field_widget input'), "22"); assert.strictEqual($m2m.find('.o_field_widget input').get(0), document.activeElement, "the focus should still be on the input"); assert.strictEqual($m2m.find('.o_field_widget input').val(), "22", "the value should not have been formatted yet"); - testUtils.dom.click(form.$el); + await testUtils.dom.click(form.$el); assert.strictEqual($m2m.find('.o_field_widget[name=qux]').html(), "22.00 €", "the value should have been formatted after losing the focus"); @@ -3698,7 +3693,7 @@ QUnit.module('basic_fields', { }, }; - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -3712,33 +3707,32 @@ QUnit.module('basic_fields', { }, }); - testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); assert.containsOnce(list, 'div.o_field_widget[name=qux] input', "monetary field should have been rendered correctly (without currency)"); assert.containsNone(list, '.o_field_widget[name=qux] span', "monetary field should have been rendered correctly (without currency)"); // set a value for int_field -> should set the currency and re-render qux - testUtils.fields.editInput(list.$('.o_field_widget[name=int_field]'),'7'); + await testUtils.fields.editInput(list.$('.o_field_widget[name=int_field]'),'7'); assert.containsOnce(list, 'div.o_field_widget[name=qux] input', "monetary field should have been re-rendered correctly (with currency)"); assert.strictEqual(list.$('.o_field_widget[name=qux] span:contains(€)').length, 1, "monetary field should have been re-rendered correctly (with currency)"); var $quxInput = list.$('.o_field_widget[name=qux] input'); - testUtils.dom.click($quxInput); + await testUtils.dom.click($quxInput); assert.strictEqual(document.activeElement, $quxInput[0], "focus should be on the qux field's input"); // unset the value of int_field -> should unset the currency and re-render qux - testUtils.dom.click(list.$('.o_field_widget[name=int_field]')); - testUtils.fields.editInput(list.$('.o_field_widget[name=int_field]'),'0'); + await testUtils.dom.click(list.$('.o_field_widget[name=int_field]')); + await testUtils.fields.editInput(list.$('.o_field_widget[name=int_field]'),'0'); $quxInput = list.$('div.o_field_widget[name=qux] input'); assert.strictEqual($quxInput.length, 1, "monetary field should have been re-rendered correctly (without currency)"); assert.containsNone(list, '.o_field_widget[name=qux] span', "monetary field should have been re-rendered correctly (without currency)"); - await concurrency.delay(0); - testUtils.dom.click($quxInput); + await testUtils.dom.click($quxInput); assert.strictEqual(document.activeElement, $quxInput[0], "focus should be on the qux field's input"); @@ -3747,10 +3741,10 @@ QUnit.module('basic_fields', { QUnit.module('FieldInteger'); - QUnit.test('integer field when unset', function (assert) { + QUnit.test('integer field when unset', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3766,10 +3760,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('integer field in form view', function (assert) { + QUnit.test('integer field in form view', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3780,22 +3774,22 @@ QUnit.module('basic_fields', { assert.doesNotHaveClass(form.$('.o_field_widget'), 'o_field_empty', 'Integer field should be considered set for value 0.'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('input[name=int_field]').val(), '0', 'The value should be rendered correctly in edit mode.'); - testUtils.fields.editInput(form.$('input[name=int_field]'), '-18'); + await testUtils.fields.editInput(form.$('input[name=int_field]'), '-18'); assert.strictEqual(form.$('input[name=int_field]').val(), '-18', 'The value should be correctly displayed in the input.'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_widget').text(), '-18', 'The new value should be saved and displayed properly.'); form.destroy(); }); - QUnit.test('integer field in form view with virtual id', function (assert) { + QUnit.test('integer field in form view with virtual id', async function (assert) { assert.expect(1); var params = { View: FormView, @@ -3805,17 +3799,17 @@ QUnit.module('basic_fields', { }; params.res_id = this.data.partner.records[1].id = "2-20170808020000"; - var form = createView(params); + var form = await createView(params); assert.strictEqual(form.$('.o_field_widget').text(), "2-20170808020000", "Should display virtual id"); form.destroy(); }); - QUnit.test('integer field in editable list view', function (assert) { + QUnit.test('integer field in editable list view', async function (assert) { assert.expect(4); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -3830,26 +3824,26 @@ QUnit.module('basic_fields', { // switch to edit mode var $cell = list.$('tr.o_data_row td:not(.o_list_record_selector)').first(); - testUtils.dom.click($cell); + await testUtils.dom.click($cell); assert.containsOnce(list, 'input[name="int_field"]', 'The view should have 1 input for editable integer.'); - testUtils.fields.editInput(list.$('input[name="int_field"]'), '-28'); + await testUtils.fields.editInput(list.$('input[name="int_field"]'), '-28'); assert.strictEqual(list.$('input[name="int_field"]').val(), '-28', 'The value should be displayed properly in the input.'); - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); assert.strictEqual(list.$('td:not(.o_list_record_selector)').first().text(), '-28', 'The new value should be saved and displayed properly.'); list.destroy(); }); - QUnit.test('integer field with type number option', function (assert) { + QUnit.test('integer field with type number option', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3863,28 +3857,28 @@ QUnit.module('basic_fields', { }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.ok(form.$('.o_field_widget')[0].hasAttribute('type'), 'Integer field with option type must have a type attribute.'); assert.hasAttrValue(form.$('.o_field_widget'), 'type', 'number', 'Integer field with option type must have a type attribute equals to "number".'); - testUtils.fields.editInput(form.$('input[name=int_field]'), '1234567890'); - testUtils.form.clickSave(form); - testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name=int_field]'), '1234567890'); + await testUtils.form.clickSave(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_widget').val(), '1234567890', 'Integer value must be not formatted if input type is number.'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_widget').text(), '1,234,567,890', 'Integer value must be formatted in readonly view even if the input type is number.'); form.destroy(); }); - QUnit.test('integer field without type number option', function (assert) { + QUnit.test('integer field without type number option', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3898,13 +3892,13 @@ QUnit.module('basic_fields', { }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.hasAttrValue(form.$('.o_field_widget'), 'type', 'text', 'Integer field without option type must have a text type (default type).'); - testUtils.fields.editInput(form.$('input[name=int_field]'), '1234567890'); - testUtils.form.clickSave(form); - testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name=int_field]'), '1234567890'); + await testUtils.form.clickSave(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_widget').val(), '1,234,567,890', 'Integer value must be formatted if input type isn\'t number.'); @@ -3914,10 +3908,10 @@ QUnit.module('basic_fields', { QUnit.module('FieldFloatTime'); - QUnit.test('float_time field in form view', function (assert) { + QUnit.test('float_time field in form view', async function (assert) { assert.expect(5); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3940,15 +3934,15 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('.o_field_widget').first().text(), '09:06', 'The formatted time value should be displayed properly.'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('input[name=qux]').val(), '09:06', 'The value should be rendered correctly in the input.'); - testUtils.fields.editInput(form.$('input[name=qux]'), '-11:48'); + await testUtils.fields.editInput(form.$('input[name=qux]'), '-11:48'); assert.strictEqual(form.$('input[name=qux]').val(), '-11:48', 'The new value should be displayed properly in the input.'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_widget').first().text(), '-11:48', 'The new value should be saved and displayed properly.'); @@ -3958,10 +3952,10 @@ QUnit.module('basic_fields', { QUnit.module('FieldFloatFactor'); - QUnit.test('float_factor field in form view', function (assert) { + QUnit.test('float_factor field in form view', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3982,13 +3976,13 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('.o_field_widget').first().text(), '4.55', // 9.1 / 0.5 'The formatted value should be displayed properly.'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('input[name=qux]').val(), '4.55', 'The value should be rendered correctly in the input.'); - testUtils.fields.editInput(form.$('input[name=qux]'), '2.3'); + await testUtils.fields.editInput(form.$('input[name=qux]'), '2.3'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_widget').first().text(), '2.30', 'The new value should be saved and displayed properly.'); @@ -3997,10 +3991,10 @@ QUnit.module('basic_fields', { QUnit.module('FieldFloatToggle'); - QUnit.test('float_toggle field in form view', function (assert) { + QUnit.test('float_toggle field in form view', async function (assert) { assert.expect(5); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4021,17 +4015,17 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('.o_field_widget').first().text(), '0.056', 'The formatted time value should be displayed properly.'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('button.o_field_float_toggle').text(), '0.056', 'The value should be rendered correctly on the button.'); - testUtils.dom.click(form.$('button.o_field_float_toggle')); + await testUtils.dom.click(form.$('button.o_field_float_toggle')); assert.strictEqual(form.$('button.o_field_float_toggle').text(), '1.000', 'The value should be rendered correctly on the button.'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_widget').first().text(), '1.000', 'The new value should be saved and displayed properly.'); @@ -4042,10 +4036,10 @@ QUnit.module('basic_fields', { QUnit.module('PhoneWidget'); - QUnit.test('phone field in form view on normal screens', function (assert) { + QUnit.test('phone field in form view on normal screens', async function (assert) { assert.expect(5); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4071,27 +4065,27 @@ QUnit.module('basic_fields', { "value should be displayed properly"); // switch to edit mode and check the result - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, 'input[type="text"].o_field_widget', "should have an input for the phone field"); assert.strictEqual(form.$('input[type="text"].o_field_widget').val(), 'yop', "input should contain field value in edit mode"); // change value in edit mode - testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), 'new'); + await testUtils.fields.editInput(form.$('input[type="text"].o_field_widget'), 'new'); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('a.o_field_widget.o_form_uri').text(), 'new', "new value should be displayed properly"); form.destroy(); }); - QUnit.test('phone field in editable list view on normal screens', function (assert) { + QUnit.test('phone field in editable list view on normal screens', async function (assert) { assert.expect(8); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -4103,8 +4097,7 @@ QUnit.module('basic_fields', { }, }); - assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').length, 5, - "should have 5 cells"); + assert.containsN(list, 'tbody td:not(.o_list_record_selector)', 5); assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'yop', "value should be displayed properly"); @@ -4113,14 +4106,14 @@ QUnit.module('basic_fields', { // Edit a line and check the result var $cell = list.$('tbody td:not(.o_list_record_selector)').first(); - testUtils.dom.click($cell); + await testUtils.dom.click($cell); assert.hasClass($cell.parent(),'o_selected_row', 'should be set as edit mode'); assert.strictEqual($cell.find('input').val(), 'yop', 'should have the corect value in internal input'); - testUtils.fields.editInput($cell.find('input'), 'new'); + await testUtils.fields.editInput($cell.find('input'), 'new'); // save - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); $cell = list.$('tbody td:not(.o_list_record_selector)').first(); assert.doesNotHaveClass($cell.parent(), 'o_selected_row', 'should not be in edit mode anymore'); assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'new', @@ -4131,10 +4124,10 @@ QUnit.module('basic_fields', { list.destroy(); }); - QUnit.test('use TAB to navigate to a phone field', function (assert) { + QUnit.test('use TAB to navigate to a phone field', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4160,10 +4153,10 @@ QUnit.module('basic_fields', { QUnit.module('PriorityWidget'); - QUnit.test('priority widget when not set', function (assert) { + QUnit.test('priority widget when not set', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4189,10 +4182,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('priority widget in form view', function (assert) { + QUnit.test('priority widget in form view', async function (assert) { assert.expect(22); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4228,7 +4221,7 @@ QUnit.module('basic_fields', { // occurs in a setTimeout after 200ms so it's not trivial to test it here. // switch to edit mode and check the result - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_widget.o_priority').find('a.o_priority_star').length, 2, "should still have two stars"); assert.strictEqual(form.$('.o_field_widget.o_priority').find('a.o_priority_star.fa-star').length, 1, @@ -4237,7 +4230,7 @@ QUnit.module('basic_fields', { "should still have one empty star since the value is the second value"); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_widget.o_priority').find('a.o_priority_star').length, 2, "should still have two stars"); assert.strictEqual(form.$('.o_field_widget.o_priority').find('a.o_priority_star.fa-star').length, 1, @@ -4246,7 +4239,7 @@ QUnit.module('basic_fields', { "should still have one empty star since the value is the second value"); // switch to edit mode to check that the new value was properly written - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_widget.o_priority').find('a.o_priority_star').length, 2, "should still have two stars"); assert.strictEqual(form.$('.o_field_widget.o_priority').find('a.o_priority_star.fa-star').length, 1, @@ -4255,7 +4248,7 @@ QUnit.module('basic_fields', { "should still have one empty star since the value is the second value"); // click on the second star in edit mode - testUtils.dom.click(form.$('.o_field_widget.o_priority a.o_priority_star.fa-star-o').last()); + await testUtils.dom.click(form.$('.o_field_widget.o_priority a.o_priority_star.fa-star-o').last()); assert.strictEqual(form.$('.o_field_widget.o_priority').find('a.o_priority_star').length, 2, "should still have two stars"); @@ -4265,7 +4258,7 @@ QUnit.module('basic_fields', { "should now have no empty star since the value is the third value"); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_widget.o_priority').find('a.o_priority_star').length, 2, "should still have two stars"); assert.strictEqual(form.$('.o_field_widget.o_priority').find('a.o_priority_star.fa-star').length, 2, @@ -4276,10 +4269,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('priority widget in editable list view', function (assert) { + QUnit.test('priority widget in editable list view', async function (assert) { assert.expect(25); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -4300,7 +4293,7 @@ QUnit.module('basic_fields', { // switch to edit mode and check the result var $cell = list.$('tbody td:not(.o_list_record_selector)').first(); - testUtils.dom.click($cell); + await testUtils.dom.click($cell); assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star').length, 2, "should have two stars for representing each possible value: no star, one star and two stars"); assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star.fa-star').length, 1, @@ -4309,7 +4302,7 @@ QUnit.module('basic_fields', { "should have one empty star since the value is the second value"); // save - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star').length, 2, "should have two stars for representing each possible value: no star, one star and two stars"); assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star.fa-star').length, 1, @@ -4327,7 +4320,7 @@ QUnit.module('basic_fields', { "should temporary have no empty star since we are hovering the third value"); // click on the first star in readonly mode - testUtils.dom.click(list.$('.o_priority a.o_priority_star.fa-star').first()); + await testUtils.dom.click(list.$('.o_priority a.o_priority_star.fa-star').first()); assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star').length, 2, "should still have two stars"); @@ -4338,7 +4331,7 @@ QUnit.module('basic_fields', { // re-enter edit mode to force re-rendering the widget to check if the value was correctly saved $cell = list.$('tbody td:not(.o_list_record_selector)').first(); - testUtils.dom.click($cell); + await testUtils.dom.click($cell); assert.strictEqual(list.$('.o_data_row').first().find('.o_priority a.o_priority_star').length, 2, "should still have two stars"); @@ -4348,7 +4341,7 @@ QUnit.module('basic_fields', { "should now have two empty stars since the value is the first value"); // Click on second star in edit mode - testUtils.dom.click(list.$('.o_priority a.o_priority_star.fa-star-o').last()); + await testUtils.dom.click(list.$('.o_priority a.o_priority_star.fa-star-o').last()); assert.strictEqual(list.$('.o_data_row').last().find('.o_priority a.o_priority_star').length, 2, "should still have two stars"); @@ -4358,7 +4351,7 @@ QUnit.module('basic_fields', { "should now have no empty star since the value is the third value"); // save - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); assert.strictEqual(list.$('.o_data_row').last().find('.o_priority a.o_priority_star').length, 2, "should still have two stars"); assert.strictEqual(list.$('.o_data_row').last().find('.o_priority a.o_priority_star.fa-star').length, 2, @@ -4372,10 +4365,10 @@ QUnit.module('basic_fields', { QUnit.module('StateSelection Widget'); - QUnit.test('state_selection widget in form view', function (assert) { + QUnit.test('state_selection widget in form view', async function (assert) { assert.expect(21); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4400,14 +4393,14 @@ QUnit.module('basic_fields', { "there should not be a dropdown"); // Click on the status button to make the dropdown appear - testUtils.dom.click(form.$('.o_field_widget.o_selection .o_status').first()); + await testUtils.dom.click(form.$('.o_field_widget.o_selection .o_status').first()); assert.containsOnce(form, '.dropdown-menu.state:visible', "there should be a dropdown"); assert.containsN(form, '.dropdown-menu.state:visible .dropdown-item', 2, "there should be two options in the dropdown"); // Click on the first option, "Normal" - testUtils.dom.click(form.$('.dropdown-menu.state:visible .dropdown-item').first()); + await testUtils.dom.click(form.$('.dropdown-menu.state:visible .dropdown-item').first()); assert.containsNone(form, '.dropdown-menu.state:visible', "there should not be a dropdown anymore"); assert.containsNone(form, '.o_field_widget.o_selection > a span.o_status.o_status_red', @@ -4418,7 +4411,7 @@ QUnit.module('basic_fields', { "should have one grey status since selection is the first, normal state"); // switch to edit mode and check the result - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsNone(form, '.dropdown-menu.state:visible', "there should still not be a dropdown"); assert.containsNone(form, '.o_field_widget.o_selection > a span.o_status.o_status_red', @@ -4429,14 +4422,14 @@ QUnit.module('basic_fields', { "should still have one grey status since selection is the first, normal state"); // Click on the status button to make the dropdown appear - testUtils.dom.click(form.$('.o_field_widget.o_selection .o_status').first()); + await testUtils.dom.click(form.$('.o_field_widget.o_selection .o_status').first()); assert.containsOnce(form, '.dropdown-menu.state:visible', "there should be a dropdown"); assert.containsN(form, '.dropdown-menu.state:visible .dropdown-item', 2, "there should be two options in the dropdown"); // Click on the last option, "Done" - testUtils.dom.click(form.$('.dropdown-menu.state:visible .dropdown-item').last()); + await testUtils.dom.click(form.$('.dropdown-menu.state:visible .dropdown-item').last()); assert.containsNone(form, '.dropdown-menu.state:visible', "there should not be a dropdown anymore"); assert.containsNone(form, '.o_field_widget.o_selection > a span.o_status.o_status_red', @@ -4445,7 +4438,7 @@ QUnit.module('basic_fields', { "should have one green status since selection is the third, done state"); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.containsNone(form, '.dropdown-menu.state:visible', "there should still not be a dropdown anymore"); assert.containsNone(form, '.o_field_widget.o_selection > a span.o_status.o_status_red', @@ -4456,10 +4449,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('state_selection widget with readonly modifier', function (assert) { + QUnit.test('state_selection widget with readonly modifier', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4471,16 +4464,16 @@ QUnit.module('basic_fields', { assert.hasClass(form.$('.o_selection > a'), 'disabled'); assert.isNotVisible(form.$('.dropdown-menu.state')); - testUtils.dom.click(form.$('.o_selection > a')); + await testUtils.dom.click(form.$('.o_selection > a')); assert.isNotVisible(form.$('.dropdown-menu.state')); form.destroy(); }); - QUnit.test('state_selection widget in editable list view', function (assert) { + QUnit.test('state_selection widget in editable list view', async function (assert) { assert.expect(32); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -4501,7 +4494,7 @@ QUnit.module('basic_fields', { // Click on the status button to make the dropdown appear var $cell = list.$('tbody td.o_state_selection_cell').first(); - testUtils.dom.click(list.$('.o_state_selection_cell .o_selection > a span.o_status').first()); + await testUtils.dom.click(list.$('.o_state_selection_cell .o_selection > a span.o_status').first()); assert.doesNotHaveClass($cell.parent(), 'o_selected_row', 'should not be in edit mode since we clicked on the state selection widget'); assert.containsOnce(list, '.dropdown-menu.state:visible', @@ -4510,7 +4503,7 @@ QUnit.module('basic_fields', { "there should be two options in the dropdown"); // Click on the first option, "Normal" - testUtils.dom.click(list.$('.dropdown-menu.state:visible .dropdown-item').first()); + await testUtils.dom.click(list.$('.dropdown-menu.state:visible .dropdown-item').first()); assert.containsN(list, '.o_state_selection_cell .o_selection > a span.o_status', 5, "should still have five status selection widgets"); assert.containsNone(list, '.o_state_selection_cell .o_selection > a span.o_status.o_status_red', @@ -4522,7 +4515,7 @@ QUnit.module('basic_fields', { // switch to edit mode and check the result $cell = list.$('tbody td.o_state_selection_cell').first(); - testUtils.dom.click($cell); + await testUtils.dom.click($cell); assert.hasClass($cell.parent(),'o_selected_row', 'should now be in edit mode'); assert.containsN(list, '.o_state_selection_cell .o_selection > a span.o_status', 5, @@ -4535,7 +4528,7 @@ QUnit.module('basic_fields', { "there should not be a dropdown"); // Click on the status button to make the dropdown appear - testUtils.dom.click(list.$('.o_state_selection_cell .o_selection > a span.o_status').first()); + await testUtils.dom.click(list.$('.o_state_selection_cell .o_selection > a span.o_status').first()); assert.containsOnce(list, '.dropdown-menu.state:visible', "there should be a dropdown"); assert.containsN(list, '.dropdown-menu.state:visible .dropdown-item', 2, @@ -4543,7 +4536,7 @@ QUnit.module('basic_fields', { // Click on another row var $lastCell = list.$('tbody td.o_state_selection_cell').last(); - testUtils.dom.click($lastCell); + await testUtils.dom.click($lastCell); assert.containsNone(list, '.dropdown-menu.state:visible', "there should not be a dropdown anymore"); var $firstCell = list.$('tbody td.o_state_selection_cell').first(); @@ -4553,14 +4546,14 @@ QUnit.module('basic_fields', { 'last row should be in edit mode'); // Click on the last status button to make the dropdown appear - testUtils.dom.click(list.$('.o_state_selection_cell .o_selection > a span.o_status').last()); + await testUtils.dom.click(list.$('.o_state_selection_cell .o_selection > a span.o_status').last()); assert.containsOnce(list, '.dropdown-menu.state:visible', "there should be a dropdown"); assert.containsN(list, '.dropdown-menu.state:visible .dropdown-item', 2, "there should be two options in the dropdown"); // Click on the last option, "Done" - testUtils.dom.click(list.$('.dropdown-menu.state:visible .dropdown-item').last()); + await testUtils.dom.click(list.$('.dropdown-menu.state:visible .dropdown-item').last()); assert.containsNone(list, '.dropdown-menu.state:visible', "there should not be a dropdown anymore"); assert.containsN(list, '.o_state_selection_cell .o_selection > a span.o_status', 5, @@ -4573,7 +4566,7 @@ QUnit.module('basic_fields', { "there should not be a dropdown"); // save - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); assert.containsN(list, '.o_state_selection_cell .o_selection > a span.o_status', 5, "should have five status selection widgets"); assert.containsNone(list, '.o_state_selection_cell .o_selection > a span.o_status.o_status_red', @@ -4589,10 +4582,10 @@ QUnit.module('basic_fields', { QUnit.module('FavoriteWidget'); - QUnit.test('favorite widget in kanban view', function (assert) { + QUnit.test('favorite widget in kanban view', async function (assert) { assert.expect(4); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4614,7 +4607,7 @@ QUnit.module('basic_fields', { 'the label should say "Remove from Favorites"'); // click on favorite - testUtils.dom.click(kanban.$('.o_field_widget.o_favorite')); + await testUtils.dom.click(kanban.$('.o_field_widget.o_favorite')); assert.containsNone(kanban, '.o_kanban_record .o_field_widget.o_favorite > a i.fa.fa-star', 'should not be favorite'); assert.strictEqual(kanban.$('.o_kanban_record .o_field_widget.o_favorite > a').text(), ' Add to Favorites', @@ -4623,10 +4616,10 @@ QUnit.module('basic_fields', { kanban.destroy(); }); - QUnit.test('favorite widget in form view', function (assert) { + QUnit.test('favorite widget in form view', async function (assert) { assert.expect(10); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4646,28 +4639,28 @@ QUnit.module('basic_fields', { 'the label should say "Remove from Favorites"'); // click on favorite - testUtils.dom.click(form.$('.o_field_widget.o_favorite')); + await testUtils.dom.click(form.$('.o_field_widget.o_favorite')); assert.containsNone(form, '.o_field_widget.o_favorite > a i.fa.fa-star', 'should not be favorite'); assert.strictEqual(form.$('.o_field_widget.o_favorite > a').text(), ' Add to Favorites', 'the label should say "Add to Favorites"'); // switch to edit mode - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, '.o_field_widget.o_favorite > a i.fa.fa-star-o', 'should not be favorite'); assert.strictEqual(form.$('.o_field_widget.o_favorite > a').text(), ' Add to Favorites', 'the label should say "Add to Favorites"'); // click on favorite - testUtils.dom.click(form.$('.o_field_widget.o_favorite')); + await testUtils.dom.click(form.$('.o_field_widget.o_favorite')); assert.containsOnce(form, '.o_field_widget.o_favorite > a i.fa.fa-star', 'should be favorite'); assert.strictEqual(form.$('.o_field_widget.o_favorite > a').text(), ' Remove from Favorites', 'the label should say "Remove from Favorites"'); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.containsOnce(form, '.o_field_widget.o_favorite > a i.fa.fa-star', 'should be favorite'); assert.strictEqual(form.$('.o_field_widget.o_favorite > a').text(), ' Remove from Favorites', @@ -4676,10 +4669,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('favorite widget in editable list view without label', function (assert) { + QUnit.test('favorite widget in editable list view without label', async function (assert) { assert.expect(4); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -4692,17 +4685,17 @@ QUnit.module('basic_fields', { 'should be favorite'); // switch to edit mode - testUtils.dom.click(list.$('tbody td:not(.o_list_record_selector)').first()); + await testUtils.dom.click(list.$('tbody td:not(.o_list_record_selector)').first()); assert.containsOnce(list, '.o_data_row:first .o_field_widget.o_favorite > a i.fa.fa-star', 'should be favorite'); // click on favorite - testUtils.dom.click(list.$('.o_data_row:first .o_field_widget.o_favorite')); + await testUtils.dom.click(list.$('.o_data_row:first .o_field_widget.o_favorite')); assert.containsNone(list, '.o_data_row:first .o_field_widget.o_favorite > a i.fa.fa-star', 'should not be favorite'); // save - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); assert.containsOnce(list, '.o_data_row:first .o_field_widget.o_favorite > a i.fa.fa-star-o', 'should not be favorite'); @@ -4712,10 +4705,10 @@ QUnit.module('basic_fields', { QUnit.module('LabelSelectionWidget'); - QUnit.test('label_selection widget in form view', function (assert) { + QUnit.test('label_selection widget in form view', async function (assert) { assert.expect(12); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4740,7 +4733,7 @@ QUnit.module('basic_fields', { "the label should say 'Blocked' since this is the label value for that state"); // // switch to edit mode and check the result - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, '.o_field_widget.badge.badge-warning', "should have a warning status label since selection is the second, blocked state"); assert.containsNone(form, '.o_field_widget.badge.badge-secondary', @@ -4751,7 +4744,7 @@ QUnit.module('basic_fields', { "the label should say 'Blocked' since this is the label value for that state"); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.containsOnce(form, '.o_field_widget.badge.badge-warning', "should have a warning status label since selection is the second, blocked state"); assert.containsNone(form, '.o_field_widget.badge.badge-secondary', @@ -4764,10 +4757,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('label_selection widget in editable list view', function (assert) { + QUnit.test('label_selection widget in editable list view', async function (assert) { assert.expect(21); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -4794,7 +4787,7 @@ QUnit.module('basic_fields', { "the success label should read 'Done'"); // switch to edit mode and check the result - testUtils.dom.click(list.$('tbody td:not(.o_list_record_selector)').first()); + await testUtils.dom.clickFirst(list.$('tbody td:not(.o_list_record_selector)')); assert.strictEqual(list.$('.o_field_widget.badge:not(:empty)').length, 3, "should have three visible status labels"); assert.containsOnce(list, '.o_field_widget.badge.badge-warning', @@ -4811,7 +4804,7 @@ QUnit.module('basic_fields', { "the success label should read 'Done'"); // save and check the result - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); assert.strictEqual(list.$('.o_field_widget.badge:not(:empty)').length, 3, "should have three visible status labels"); assert.containsOnce(list, '.o_field_widget.badge.badge-warning', @@ -4833,7 +4826,7 @@ QUnit.module('basic_fields', { QUnit.module('StatInfo'); - QUnit.test('statinfo widget formats decimal precision', function (assert) { + QUnit.test('statinfo widget formats decimal precision', async function (assert) { // sometimes the round method can return numbers such as 14.000001 // when asked to round a number to 2 decimals, as such is the behaviour of floats. // we check that even in that eventuality, only two decimals are displayed @@ -4843,7 +4836,7 @@ QUnit.module('basic_fields', { this.data.partner.records[0].monetary = 9.999999; this.data.partner.records[0].currency_id = 1; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4867,10 +4860,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('statinfo widget in form view', function (assert) { + QUnit.test('statinfo widget in form view', async function (assert) { assert.expect(9); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4897,7 +4890,7 @@ QUnit.module('basic_fields', { 'int_field', "should have 'int_field' as text"); // switch to edit mode and check the result - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, '.oe_stat_button .o_field_widget.o_stat_info', "should still have one stat button"); assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').text(), @@ -4906,7 +4899,7 @@ QUnit.module('basic_fields', { 'int_field', "should have 'int_field' as text"); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.containsOnce(form, '.oe_stat_button .o_field_widget.o_stat_info', "should have one stat button"); assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').text(), @@ -4917,10 +4910,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('statinfo widget in form view with specific label_field', function (assert) { + QUnit.test('statinfo widget in form view with specific label_field', async function (assert) { assert.expect(9); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4949,7 +4942,7 @@ QUnit.module('basic_fields', { 'yop', "should have 'yop' as text, since it is the value of field foo"); // switch to edit mode and check the result - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, '.oe_stat_button .o_field_widget.o_stat_info', "should still have one stat button"); assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').text(), @@ -4958,7 +4951,7 @@ QUnit.module('basic_fields', { 'yop', "should have 'yop' as text, since it is the value of field foo"); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.containsOnce(form, '.oe_stat_button .o_field_widget.o_stat_info', "should have one stat button"); assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').text(), @@ -4969,10 +4962,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('statinfo widget in form view with no label', function (assert) { + QUnit.test('statinfo widget in form view with no label', async function (assert) { assert.expect(9); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5000,7 +4993,7 @@ QUnit.module('basic_fields', { '', "should not have any label"); // switch to edit mode and check the result - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, '.oe_stat_button .o_field_widget.o_stat_info', "should still have one stat button"); assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').text(), @@ -5009,7 +5002,7 @@ QUnit.module('basic_fields', { '', "should not have any label"); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.containsOnce(form, '.oe_stat_button .o_field_widget.o_stat_info', "should have one stat button"); assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').text(), @@ -5023,10 +5016,10 @@ QUnit.module('basic_fields', { QUnit.module('PercentPie'); - QUnit.test('percentpie widget in form view with value < 50%', function (assert) { + QUnit.test('percentpie widget in form view with value < 50%', async function (assert) { assert.expect(12); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5050,7 +5043,7 @@ QUnit.module('basic_fields', { 'transform: rotate(36deg);'), "right mask should be rotated from 360*(10/100) = 36 degrees"); // switch to edit mode and check the result - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, '.o_field_percent_pie.o_field_widget .o_pie', "should have a pie chart"); assert.strictEqual(form.$('.o_field_percent_pie.o_field_widget .o_pie .o_pie_value').text(), @@ -5061,7 +5054,7 @@ QUnit.module('basic_fields', { 'transform: rotate(36deg);'), "right mask should be rotated from 360*(10/100) = 36 degrees"); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.containsOnce(form, '.o_field_percent_pie.o_field_widget .o_pie', "should have a pie chart"); assert.strictEqual(form.$('.o_field_percent_pie.o_field_widget .o_pie .o_pie_value').text(), @@ -5074,10 +5067,10 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('percentpie widget in form view with value > 50%', function (assert) { + QUnit.test('percentpie widget in form view with value > 50%', async function (assert) { assert.expect(12); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5101,7 +5094,7 @@ QUnit.module('basic_fields', { "right mask should be hidden since the value > 50%"); // switch to edit mode and check the result - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, '.o_field_percent_pie.o_field_widget .o_pie', "should have a pie chart"); assert.strictEqual(form.$('.o_field_percent_pie.o_field_widget .o_pie .o_pie_value').text(), @@ -5112,7 +5105,7 @@ QUnit.module('basic_fields', { "right mask should be hidden since the value > 50%"); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.containsOnce(form, '.o_field_percent_pie.o_field_widget .o_pie', "should have a pie chart"); assert.strictEqual(form.$('.o_field_percent_pie.o_field_widget .o_pie .o_pie_value').text(), @@ -5131,10 +5124,10 @@ QUnit.module('basic_fields', { // applies inside the form view, the widget is unusable. This test can // be uncommented when we refactor the scss files so that this widget // stylesheet applies in both form and list view. - // QUnit.test('percentpie widget in editable list view', function(assert) { + // QUnit.test('percentpie widget in editable list view', async function(assert) { // assert.expect(10); // - // var list = createView({ + // var list = await createView({ // View: ListView, // model: 'partner', // data: this.data, @@ -5177,14 +5170,14 @@ QUnit.module('basic_fields', { QUnit.module('FieldDomain'); - QUnit.test('The domain editor should not crash the view when given a dynamic filter', function (assert) { + QUnit.test('The domain editor should not crash the view when given a dynamic filter', async function (assert) { //dynamic filters (containing variables, such as uid, parent or today) //are not handled by the domain editor, but it shouldn't crash the view assert.expect(1); this.data.partner.records[0].foo = '[["int_field", "=", uid]]'; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5204,12 +5197,12 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('basic domain field usage is ok', function (assert) { + QUnit.test('basic domain field usage is ok', async function (assert) { assert.expect(7); this.data.partner.records[0].foo = "[]"; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5223,7 +5216,7 @@ QUnit.module('basic_fields', { '</form>', res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // As the domain is empty, there should be a button to add the first // domain part @@ -5234,14 +5227,14 @@ QUnit.module('basic_fields', { // Clicking on the button should add the [["id", "=", "1"]] domain, so // there should be a field selector in the DOM - testUtils.dom.click($domainAddFirstNodeButton); + await testUtils.dom.click($domainAddFirstNodeButton); var $fieldSelector = $domain.find(".o_field_selector"); assert.equal($fieldSelector.length, 1, "there should be a field selector"); // Focusing the field selector input should open the field selector // popover - $fieldSelector.trigger('focusin'); + await testUtils.dom.triggerEvents($fieldSelector, ['focusin']); var $fieldSelectorPopover = $fieldSelector.find(".o_field_selector_popover"); assert.ok($fieldSelectorPopover.is(":visible"), "field selector popover should be visible"); @@ -5264,28 +5257,28 @@ QUnit.module('basic_fields', { // Clicking on this field should close the popover, then changing the // associated value should reveal one matched record - testUtils.dom.click($colorIndex); - $domain.find(".o_domain_leaf_value_input").val(2).change(); + await testUtils.dom.click($colorIndex); + await testUtils.fields.editAndTrigger($('.o_domain_leaf_value_input'), 2, ['change']); assert.equal($domain.find(".o_domain_show_selection_button").text().trim().substr(0, 2), "1 ", "changing color value to 2 should reveal only one record"); // Saving the form view should show a readonly domain containing the // "color" field - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); $domain = form.$(".o_field_domain"); assert.ok($domain.html().indexOf("Color index") >= 0, "field selector readonly value should now contain 'Color index'"); form.destroy(); }); - QUnit.test('domain field is correctly reset on every view change', function (assert) { + QUnit.test('domain field is correctly reset on every view change', async function (assert) { assert.expect(7); this.data.partner.records[0].foo = '[["id","=",1]]'; this.data.partner.fields.bar.type = "char"; this.data.partner.records[0].bar = "product"; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5300,7 +5293,7 @@ QUnit.module('basic_fields', { '</form>', res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // As the domain is equal to [["id", "=", 1]] there should be a field // selector to change this @@ -5310,7 +5303,7 @@ QUnit.module('basic_fields', { "there should be a field selector"); // Focusing its input should open the field selector popover - $fieldSelector.trigger('focusin'); + await testUtils.dom.triggerEvents($fieldSelector, ['focusin']); var $fieldSelectorPopover = $fieldSelector.find(".o_field_selector_popover"); assert.ok($fieldSelectorPopover.is(":visible"), "field selector popover should be visible"); @@ -5331,8 +5324,8 @@ QUnit.module('basic_fields', { "field selector popover should contain 'Product Name' field"); // Now change the value of the "bar" field to "partner_type" - testUtils.dom.click(form.$("input.o_field_widget")); - testUtils.fields.editInput(form.$("input.o_field_widget"), "partner_type"); + await testUtils.dom.click(form.$("input.o_field_widget")); + await testUtils.fields.editInput(form.$("input.o_field_widget"), "partner_type"); // Refocusing the field selector input should open the popover again $fieldSelector = form.$(".o_field_selector"); @@ -5357,7 +5350,7 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('domain field can be reset with a new domain (from onchange)', function (assert) { + QUnit.test('domain field can be reset with a new domain (from onchange)', async function (assert) { assert.expect(2); this.data.partner.records[0].foo = '[]'; @@ -5367,7 +5360,7 @@ QUnit.module('basic_fields', { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5386,7 +5379,7 @@ QUnit.module('basic_fields', { "the domain being empty, there should be 5 records"); // update display_name to trigger the onchange and reset foo - testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'new value'); + await testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'new value'); assert.equal(form.$('.o_domain_show_selection_button').text().trim(), '1 record(s)', "the domain has changed, there should be only 1 record"); @@ -5394,14 +5387,14 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('domain field: handle false domain as []', function (assert) { + QUnit.test('domain field: handle false domain as []', async function (assert) { assert.expect(3); this.data.partner.records[0].foo = false; this.data.partner.fields.bar.type = "char"; this.data.partner.records[0].bar = "product"; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5426,7 +5419,7 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('.o_field_widget[name=foo]:not(.o_field_empty)').length, 1, "there should be a domain field, not considered empty"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); var $warning = form.$('.o_field_widget[name=foo] .text-warning'); assert.strictEqual($warning.length, 0, "should not display that the domain is invalid"); @@ -5434,12 +5427,12 @@ QUnit.module('basic_fields', { form.destroy(); }); - QUnit.test('basic domain field: show the selection', function (assert) { + QUnit.test('basic domain field: show the selection', async function (assert) { assert.expect(2); this.data.partner.records[0].foo = "[]"; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5462,21 +5455,21 @@ QUnit.module('basic_fields', { "selection should contain 2 records"); // open the selection - testUtils.dom.click(form.$(".o_domain_show_selection_button")); + await testUtils.dom.click(form.$(".o_domain_show_selection_button")); assert.strictEqual($('.modal .o_list_view .o_data_row').length, 2, "should have open a list view with 2 records in a dialog"); // click on a record -> should not open the record // we don't actually check that it doesn't open the record because even // if it tries to, it will crash as we don't define an arch in this test - testUtils.dom.click($('.modal .o_list_view .o_data_row:first .o_data_cell')); + await testUtils.dom.click($('.modal .o_list_view .o_data_row:first .o_data_cell')); form.destroy(); }); QUnit.module('FieldProgressBar'); - QUnit.test('Field ProgressBar: max_value should update', function (assert) { + QUnit.test('Field ProgressBar: max_value should update', async function (assert) { assert.expect(2); this.data.partner.records = this.data.partner.records.slice(0,1); @@ -5489,7 +5482,7 @@ QUnit.module('basic_fields', { } }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5508,7 +5501,7 @@ QUnit.module('basic_fields', { 'The initial value of the progress bar should be correct'); // trigger the onchange - form.$('.o_input[name=display_name]').val('new name').trigger('input'); + await testUtils.fields.editInput(form.$('.o_input[name=display_name]'), 'new name'); assert.strictEqual(form.$('.o_progressbar_value').text(), '999 / 5', 'The value of the progress bar should be correct after the update'); diff --git a/addons/web/static/tests/fields/relational_fields/field_many2many_tests.js b/addons/web/static/tests/fields/relational_fields/field_many2many_tests.js index 949af90fafe..e20ad604c98 100644 --- a/addons/web/static/tests/fields/relational_fields/field_many2many_tests.js +++ b/addons/web/static/tests/fields/relational_fields/field_many2many_tests.js @@ -111,11 +111,11 @@ QUnit.module('fields', {}, function () { }] }, }; - } + }, }, function () { QUnit.module('FieldMany2Many'); - QUnit.test('many2many kanban: edition', function (assert) { + QUnit.test('many2many kanban: edition', async function (assert) { assert.expect(32); this.data.partner.records[0].timmy = [12, 14]; @@ -123,7 +123,7 @@ QUnit.module('fields', {}, function () { this.data.partner_type.records.push({ id: 18, display_name: "yellow", color: 4 }); this.data.partner_type.records.push({ id: 21, display_name: "blue", color: 1 }); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -187,7 +187,7 @@ QUnit.module('fields', {}, function () { assert.ok(!form.$('.o_field_many2many .o-kanban-button-new').length, '"Add" button should not be visible in readonly'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 2, 'should contain 2 records'); @@ -201,20 +201,20 @@ QUnit.module('fields', {}, function () { 'Create button should have "Add" label'); // edit existing subrecord - testUtils.dom.click(form.$('.oe_kanban_global_click:first()')); + await testUtils.dom.click(form.$('.oe_kanban_global_click:first()')); - testUtils.fields.editInput($('.modal .o_form_view input'), 'new name'); - testUtils.dom.click($('.modal .modal-footer .btn-primary')); + await testUtils.fields.editInput($('.modal .o_form_view input'), 'new name'); + await testUtils.dom.click($('.modal .modal-footer .btn-primary')); assert.strictEqual(form.$('.o_kanban_record:first() span').text(), 'new name', 'value of subrecord should have been updated'); // add subrecords // -> single select - testUtils.dom.click(form.$('.o_field_many2many .o-kanban-button-new')); + await testUtils.dom.click(form.$('.o_field_many2many .o-kanban-button-new')); assert.ok($('.modal .o_list_view').length, "should have opened a list view in a modal"); assert.strictEqual($('.modal .o_list_view tbody .o_list_record_selector').length, 3, "list view should contain 3 records"); - testUtils.dom.click($('.modal .o_list_view tbody tr:contains(red)')); + await testUtils.dom.click($('.modal .o_list_view tbody tr:contains(red)')); assert.ok(!$('.modal .o_list_view').length, "should have closed the modal"); assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 3, 'kanban should now contain 3 records'); @@ -222,23 +222,23 @@ QUnit.module('fields', {}, function () { 'record "red" should be in the kanban'); // -> multiple select - testUtils.dom.click(form.$('.o_field_many2many .o-kanban-button-new')); + await testUtils.dom.click(form.$('.o_field_many2many .o-kanban-button-new')); assert.ok($('.modal .o_select_button').prop('disabled'), "select button should be disabled"); assert.strictEqual($('.modal .o_list_view tbody .o_list_record_selector').length, 2, "list view should contain 2 records"); - testUtils.dom.click($('.modal .o_list_view thead .o_list_record_selector input')); - testUtils.dom.click($('.modal .o_select_button')); + await testUtils.dom.click($('.modal .o_list_view thead .o_list_record_selector input')); + await testUtils.dom.click($('.modal .o_select_button')); assert.ok(!$('.modal .o_select_button').prop('disabled'), "select button should be enabled"); assert.ok(!$('.modal .o_list_view').length, "should have closed the modal"); assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 5, 'kanban should now contain 5 records'); // -> created record - testUtils.dom.click(form.$('.o_field_many2many .o-kanban-button-new')); - testUtils.dom.click($('.modal .modal-footer .btn-primary:nth(1)')); + await testUtils.dom.click(form.$('.o_field_many2many .o-kanban-button-new')); + await testUtils.dom.click($('.modal .modal-footer .btn-primary:nth(1)')); assert.ok($('.modal .o_form_view.o_form_editable').length, "should have opened a form view in edit mode, in a modal"); - testUtils.fields.editInput($('.modal .o_form_view input'), 'A new type'); - testUtils.dom.click($('.modal:nth(1) footer .btn-primary:first()')); + await testUtils.fields.editInput($('.modal .o_form_view input'), 'A new type'); + await testUtils.dom.click($('.modal:nth(1) footer .btn-primary:first()')); assert.ok(!$('.modal').length, "should have closed both modals"); assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 6, 'kanban should now contain 6 records'); @@ -246,32 +246,32 @@ QUnit.module('fields', {}, function () { 'the newly created type should be in the kanban'); // delete subrecords - testUtils.dom.click(form.$('.o_kanban_record:contains(silver)')); + await testUtils.dom.click(form.$('.o_kanban_record:contains(silver)')); assert.strictEqual($('.modal .modal-footer .o_btn_remove').length, 1, 'There should be a modal having Remove Button'); - testUtils.dom.click($('.modal .modal-footer .o_btn_remove')); + await testUtils.dom.click($('.modal .modal-footer .o_btn_remove')); assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 5, 'should contain 5 records'); assert.ok(!form.$('.o_kanban_record:contains(silver)').length, 'the removed record should not be in kanban anymore'); - testUtils.dom.click(form.$('.o_kanban_record:contains(blue) .delete_icon')); + await testUtils.dom.click(form.$('.o_kanban_record:contains(blue) .delete_icon')); assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 4, 'should contain 4 records'); assert.ok(!form.$('.o_kanban_record:contains(blue)').length, 'the removed record should not be in kanban anymore'); // save the record - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('many2many kanban(editable): properly handle create_text node option', function (assert) { + QUnit.test('many2many kanban(editable): properly handle create_text node option', async function (assert) { assert.expect(1); this.data.partner.records[0].timmy = [12]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -291,19 +291,19 @@ QUnit.module('fields', {}, function () { res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_many2many[name="timmy"] .o-kanban-button-new').text().trim(), "Add timmy", "In M2M Kanban, Add button should have 'Add timmy' label"); form.destroy(); }); - QUnit.test('many2many kanban: create action disabled', function (assert) { + QUnit.test('many2many kanban: create action disabled', async function (assert) { assert.expect(4); this.data.partner.records[0].timmy = [12, 14]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -335,27 +335,27 @@ QUnit.module('fields', {}, function () { assert.ok(!form.$('.o-kanban-button-new').length, '"Add" button should not be available in readonly'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.ok(form.$('.o-kanban-button-new').length, '"Add" button should be available in edit'); assert.ok(form.$('.o_kanban_view .delete_icon').length, 'delete icon should be visible in edit'); - testUtils.dom.click(form.$('.o-kanban-button-new')); + await testUtils.dom.click(form.$('.o-kanban-button-new')); assert.strictEqual($('.modal .modal-footer .btn-primary').length, 1, // only button 'Select' '"Create" button should not be available in the modal'); form.destroy(); }); - QUnit.test('many2many list (non editable): edition', function (assert) { + QUnit.test('many2many list (non editable): edition', async function (assert) { assert.expect(27); this.data.partner.records[0].timmy = [12, 14]; this.data.partner_type.records.push({ id: 15, display_name: "bronze", color: 6 }); this.data.partner_type.fields.float_field = { string: 'Float', type: 'float' }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -392,7 +392,7 @@ QUnit.module('fields', {}, function () { assert.ok(!form.$('.o_field_x2many_list_row_add').length, '"Add an item" should not be visible in readonly'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsN(form, '.o_list_view td.o_list_number', 2, 'should contain 2 records'); @@ -404,34 +404,34 @@ QUnit.module('fields', {}, function () { '"Add an item" should be visible in edit'); // edit existing subrecord - testUtils.dom.click(form.$('.o_list_view tbody tr:first()')); + await testUtils.dom.click(form.$('.o_list_view tbody tr:first()')); - testUtils.fields.editInput($('.modal .o_form_view input'), 'new name'); - testUtils.dom.click($('.modal .modal-footer .btn-primary')); + await testUtils.fields.editInput($('.modal .o_form_view input'), 'new name'); + await testUtils.dom.click($('.modal .modal-footer .btn-primary')); assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name', 'value of subrecord should have been updated'); // add new subrecords - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual($('.modal .o_list_view').length, 1, "a modal should be open"); assert.strictEqual($('.modal .o_list_view .o_data_row').length, 1, "the list should contain one row"); - testUtils.dom.click($('.modal .o_list_view .o_data_row')); + await testUtils.dom.click($('.modal .o_list_view .o_data_row')); assert.strictEqual($('.modal .o_list_view').length, 0, "the modal should be closed"); assert.containsN(form, '.o_list_view td.o_list_number', 3, 'should contain 3 subrecords'); // remove subrecords - testUtils.dom.click(form.$('.o_list_record_remove:nth(1)')); + await testUtils.dom.click(form.$('.o_list_record_remove:nth(1)')); assert.containsN(form, '.o_list_view td.o_list_number', 2, 'should contain 2 subrecords'); assert.strictEqual(form.$('.o_list_view .o_data_row td:first').text(), 'new name', 'the updated row still has the correct values'); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.containsN(form, '.o_list_view td.o_list_number', 2, 'should contain 2 subrecords'); assert.strictEqual(form.$('.o_list_view .o_data_row td:first').text(), @@ -453,13 +453,13 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('many2many list (editable): edition', function (assert) { + QUnit.test('many2many list (editable): edition', async function (assert) { assert.expect(31); this.data.partner.records[0].timmy = [12, 14]; this.data.partner_type.records.push({ id: 15, display_name: "bronze", color: 6 }); this.data.partner_type.fields.float_field = { string: 'Float', type: 'float' }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -494,7 +494,7 @@ QUnit.module('fields', {}, function () { assert.ok(!form.$('.o_field_x2many_list_row_add').length, '"Add an item" should not be visible in readonly'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsN(form, '.o_list_view td.o_list_number', 2, 'should contain 2 records'); @@ -508,17 +508,17 @@ QUnit.module('fields', {}, function () { '"Add an item" should not visible in edit'); // edit existing subrecord - testUtils.dom.click(form.$('.o_list_view tbody td:first()')); + await testUtils.dom.click(form.$('.o_list_view tbody td:first()')); assert.ok(!$('.modal').length, 'in edit, clicking on a subrecord should not open a dialog'); assert.hasClass(form.$('.o_list_view tbody tr:first()'),'o_selected_row', 'first row should be in edition'); - testUtils.fields.editInput(form.$('.o_list_view input:first()'), 'new name'); + await testUtils.fields.editInput(form.$('.o_list_view input:first()'), 'new name'); assert.hasClass(form.$('.o_list_view .o_data_row:first'),'o_selected_row', 'first row should still be in edition'); assert.strictEqual(form.$('.o_list_view input[name=display_name]').get(0), document.activeElement, 'edited field should still have the focus'); - testUtils.dom.click(form.$el); + await testUtils.dom.click(form.$el); assert.doesNotHaveClass(form.$('.o_list_view tbody tr:first'), 'o_selected_row', 'first row should not be in edition anymore'); assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name', @@ -526,34 +526,32 @@ QUnit.module('fields', {}, function () { assert.verifySteps(['read', 'read']); // add new subrecords - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual($('.modal .o_list_view').length, 1, "a modal should be open"); assert.strictEqual($('.modal .o_list_view .o_data_row').length, 1, "the list should contain one row"); - testUtils.dom.click($('.modal .o_list_view .o_data_row')); + await testUtils.dom.click($('.modal .o_list_view .o_data_row')); assert.strictEqual($('.modal .o_list_view').length, 0, "the modal should be closed"); assert.containsN(form, '.o_list_view td.o_list_number', 3, 'should contain 3 subrecords'); // remove subrecords - testUtils.dom.click(form.$('.o_list_record_remove:nth(1)')); + await testUtils.dom.click(form.$('.o_list_record_remove:nth(1)')); assert.containsN(form, '.o_list_view td.o_list_number', 2, 'should contain 2 subrecord'); assert.strictEqual(form.$('.o_list_view tbody .o_data_row td:first').text(), 'new name', 'the updated row still has the correct values'); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.containsN(form, '.o_list_view td.o_list_number', 2, 'should contain 2 subrecords'); assert.strictEqual(form.$('.o_list_view .o_data_row td:first').text(), 'new name', 'the updated row still has the correct values'); assert.verifySteps([ - 'read', // main record - 'read', // relational field 'search_read', // list view in dialog 'read', // relational field (updated) 'write', // save main record @@ -564,12 +562,12 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('many2many: create & delete attributes', function (assert) { + QUnit.test('many2many: create & delete attributes', async function (assert) { assert.expect(4); this.data.partner.records[0].timmy = [12, 14]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -583,14 +581,14 @@ QUnit.module('fields', {}, function () { res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, '.o_field_x2many_list_row_add', "should have the 'Add an item' link"); assert.containsN(form, '.o_list_record_remove', 2, "should have the 'Add an item' link"); form.destroy(); - form = createView({ + form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -604,7 +602,7 @@ QUnit.module('fields', {}, function () { res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsNone(form, '.o_field_x2many_list_row_add', "should not have the 'Add an item' link"); assert.containsNone(form, '.o_list_record_remove', "should not have the 'Add an item' link"); @@ -612,9 +610,9 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('many2many list: create action disabled', function (assert) { + QUnit.test('many2many list: create action disabled', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -631,7 +629,7 @@ QUnit.module('fields', {}, function () { assert.ok(!form.$('.o_field_x2many_list_row_add').length, '"Add an item" link should not be available in readonly'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.ok(!form.$('.o_field_x2many_list_row_add').length, '"Add an item" link should not be available in edit either'); @@ -639,13 +637,13 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('many2many list: list of id as default value', function (assert) { + QUnit.test('many2many list: list of id as default value', async function (assert) { assert.expect(1); this.data.partner.fields.turtles.default = [2, 3]; this.data.partner.fields.turtles.type = "many2many"; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -664,13 +662,13 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('many2many checkboxes with default values', function (assert) { + QUnit.test('many2many checkboxes with default values', async function (assert) { assert.expect(7); this.data.partner.fields.turtles.default = [3]; this.data.partner.fields.turtles.type = "many2many"; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -694,10 +692,10 @@ QUnit.module('fields', {}, function () { assert.ok(form.$('.o_form_view .custom-checkbox input').eq(2).prop('checked'), "third checkbox should be checked"); - testUtils.dom.click(form.$('.o_form_view .custom-checkbox input:checked')); - testUtils.dom.click(form.$('.o_form_view .custom-checkbox input').first()); - testUtils.dom.click(form.$('.o_form_view .custom-checkbox input').first()); - testUtils.dom.click(form.$('.o_form_view .custom-checkbox input').first()); + await testUtils.dom.click(form.$('.o_form_view .custom-checkbox input:checked')); + await testUtils.dom.click(form.$('.o_form_view .custom-checkbox input').first()); + await testUtils.dom.click(form.$('.o_form_view .custom-checkbox input').first()); + await testUtils.dom.click(form.$('.o_form_view .custom-checkbox input').first()); assert.ok(form.$('.o_form_view .custom-checkbox input').eq(0).prop('checked'), "first checkbox should be checked"); @@ -706,12 +704,12 @@ QUnit.module('fields', {}, function () { assert.notOk(form.$('.o_form_view .custom-checkbox input').eq(2).prop('checked'), "third checkbox should not be checked"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('many2many list with x2many: add a record', function (assert) { + QUnit.test('many2many list with x2many: add a record', async function (assert) { assert.expect(18); this.data.partner_type.fields.m2m = { @@ -720,7 +718,7 @@ QUnit.module('fields', {}, function () { this.data.partner_type.records[0].m2m = [1, 2]; this.data.partner_type.records[1].m2m = [2, 3]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -742,7 +740,7 @@ QUnit.module('fields', {}, function () { assert.step(_.last(route.split('/')) + ' on ' + args.model); } if (args.model === 'turtle') { - assert.step(args.args[0]); // the read ids + assert.step(JSON.stringify(args.args[0])); // the read ids } return this._super.apply(this, arguments); }, @@ -751,16 +749,16 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.dom.click($('.modal .o_data_row:first')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click($('.modal .o_data_row:first')); assert.containsOnce(form, '.o_data_row', "the record should have been added to the relation"); assert.strictEqual(form.$('.o_data_row:first .o_badge_text').text(), 'leonardodonatello', "inner m2m should have been fetched and correctly displayed"); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.dom.click($('.modal .o_data_row:first')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click($('.modal .o_data_row:first')); assert.containsN(form, '.o_data_row', 2, "the second record should have been added to the relation"); @@ -771,27 +769,27 @@ QUnit.module('fields', {}, function () { 'read on partner', 'search_read on partner_type', 'read on turtle', - [1, 2, 3], + '[1,2,3]', 'read on partner_type', 'read on turtle', - [1, 2], + '[1,2]', 'search_read on partner_type', 'read on turtle', - [2, 3], + '[2,3]', 'read on partner_type', 'read on turtle', - [2, 3], + '[2,3]', ]); form.destroy(); }); - QUnit.test('many2many with a domain', function (assert) { + QUnit.test('many2many with a domain', async function (assert) { // The domain specified on the field should not be replaced by the potential // domain the user writes in the dialog, they should rather be concatenated assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -812,24 +810,24 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual($('.modal .o_data_row').length, 1, "should contain only one row (gold)"); - $('.modal .o_searchview_input').trigger({ type: 'keypress', which: 115 }); // s - $('.modal .o_searchview_input').trigger({ type: 'keydown', which: 13 }); // enter + await testUtils.fields.triggerKey('press', $('.modal .o_searchview_input'), 's'); + await testUtils.fields.triggerKeydown($('.modal .o_searchview_input'), 'enter'); assert.strictEqual($('.modal .o_data_row').length, 0, "should contain no row"); form.destroy(); }); - QUnit.test('many2many list with onchange and edition of a record', function (assert) { + QUnit.test('many2many list with onchange and edition of a record', async function (assert) { assert.expect(8); this.data.partner.fields.turtles.type = "many2many"; this.data.partner.onchanges.turtles = function () { }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -850,14 +848,14 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('td.o_data_cell:first')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('td.o_data_cell:first')); - testUtils.dom.click($('.modal-body input[type="checkbox"]')); - testUtils.dom.click($('.modal .modal-footer .btn-primary').first()); + await testUtils.dom.click($('.modal-body input[type="checkbox"]')); + await testUtils.dom.click($('.modal .modal-footer .btn-primary').first()); // there is nothing left to save -> should not do a 'write' RPC - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.verifySteps([ 'read', // read initial record (on partner) @@ -872,10 +870,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('onchange with 40+ commands for a many2many', function (assert) { + QUnit.test('onchange with 40+ commands for a many2many', async function (assert) { // this test ensures that the basic_model correctly handles more LINK_TO // commands than the limit of the dataPoint (40 for x2many kanban) - assert.expect(23); + assert.expect(24); // create a lot of partner_types that will be linked by the onchange var commands = [[5]]; @@ -890,7 +888,7 @@ QUnit.module('fields', {}, function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -925,44 +923,45 @@ QUnit.module('fields', {}, function () { assert.verifySteps(['read']); - testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), 'trigger onchange'); + await testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), 'trigger onchange'); - assert.verifySteps(['read', 'onchange', 'read']); + assert.verifySteps(['onchange', 'read']); assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), '1-40 / 45', "pager should be correct"); assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40, 'there should be 40 records displayed on page 1'); - testUtils.dom.click(form.$('.o_field_widget[name=timmy] .o_pager_next')); - assert.verifySteps(['read', 'onchange', 'read', 'read']); + await testUtils.dom.click(form.$('.o_field_widget[name=timmy] .o_pager_next')); + assert.verifySteps(['read']); assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), '41-45 / 45', "pager should be correct"); assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 5, 'there should be 5 records displayed on page 2'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), '1-40 / 45', "pager should be correct"); assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40, 'there should be 40 records displayed on page 1'); - testUtils.dom.click(form.$('.o_field_widget[name=timmy] .o_pager_next')); + await testUtils.dom.click(form.$('.o_field_widget[name=timmy] .o_pager_next')); assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), '41-45 / 45', "pager should be correct"); assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 5, 'there should be 5 records displayed on page 2'); - testUtils.dom.click(form.$('.o_field_widget[name=timmy] .o_pager_next')); + await testUtils.dom.click(form.$('.o_field_widget[name=timmy] .o_pager_next')); assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), '1-40 / 45', "pager should be correct"); assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40, 'there should be 40 records displayed on page 1'); + assert.verifySteps(['write', 'read', 'read', 'read']); form.destroy(); }); - QUnit.test('default_get, onchange, onchange on m2m', function (assert) { + QUnit.test('default_get, onchange, onchange on m2m', async function (assert) { assert.expect(1); this.data.partner.onchanges.int_field = function (obj) { @@ -978,7 +977,7 @@ QUnit.module('fields', {}, function () { ]; }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -994,15 +993,15 @@ QUnit.module('fields', {}, function () { '</form>', }); - testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 2); + await testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 2); form.destroy(); }); - QUnit.test('widget many2many_tags', function (assert) { + QUnit.test('widget many2many_tags', async function (assert) { assert.expect(1); this.data.turtle.records[0].partner_ids = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'turtle', data: this.data, @@ -1023,7 +1022,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('many2many list add *many* records, remove, re-add', function (assert) { + QUnit.test('many2many list add *many* records, remove, re-add', async function (assert) { assert.expect(5); this.data.partner.fields.timmy.domain = [['color', '=', 2]]; @@ -1035,7 +1034,7 @@ QUnit.module('fields', {}, function () { this.data.partner_type.records.push(new_record_partner_type); } - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1055,52 +1054,52 @@ QUnit.module('fields', {}, function () { mockRPC: function (route, args) { if (args.method === 'get_formview_id') { assert.deepEqual(args.args[0], [1], "should call get_formview_id with correct id"); - return $.when(false); + return Promise.resolve(false); } return this._super(route, args); }, }); // First round: add 51 records in batch - testUtils.dom.click(form.$buttons.find('.btn.btn-primary.o_form_button_edit')); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$buttons.find('.btn.btn-primary.o_form_button_edit')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); var $modal = $('.modal-lg'); assert.equal($modal.length, 1, 'There should be one modal'); - testUtils.dom.click($modal.find('thead input[type=checkbox]')); + await testUtils.dom.click($modal.find('thead input[type=checkbox]')); - testUtils.dom.click($modal.find('.btn.btn-primary.o_select_button')); + await testUtils.dom.click($modal.find('.btn.btn-primary.o_select_button')); var m2m_records = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_data_cell:not(.o_many2many_tags_cell)'); assert.equal(m2m_records.length, 51, 'We should have added all the records present in the search view to the m2m field'); // the 50 in batch + 'gold' - testUtils.dom.click(form.$buttons.find('.btn.btn-primary.o_form_button_save')); + await testUtils.dom.click(form.$buttons.find('.btn.btn-primary.o_form_button_save')); // Secound round: remove one record - testUtils.dom.click(form.$buttons.find('.btn.btn-primary.o_form_button_edit')); + await testUtils.dom.click(form.$buttons.find('.btn.btn-primary.o_form_button_edit')); var trash_buttons = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_list_record_remove'); - testUtils.dom.click(trash_buttons.first()); + await testUtils.dom.click(trash_buttons.first()); var pager_limit = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_pager_limit'); assert.equal(pager_limit.text(), '50', 'We should have 50 records in the m2m field'); // Third round: re-add 1 records - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); $modal = $('.modal-lg'); assert.equal($modal.length, 1, 'There should be one modal'); - testUtils.dom.click($modal.find('thead input[type=checkbox]')); + await testUtils.dom.click($modal.find('thead input[type=checkbox]')); - testUtils.dom.click($modal.find('.btn.btn-primary.o_select_button')); + await testUtils.dom.click($modal.find('.btn.btn-primary.o_select_button')); pager_limit = form.$('.o_field_many2many.o_field_widget.o_field_x2many.o_field_x2many_list .o_pager_limit'); assert.equal(pager_limit.text(), '51', diff --git a/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js b/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js index 04b58b017ba..4f2a3d03bed 100644 --- a/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js +++ b/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js @@ -150,13 +150,13 @@ QUnit.module('fields', {}, function () { }] }, }; - } + }, }, function () { QUnit.module('FieldMany2One'); - QUnit.test('many2ones in form views', function (assert) { + QUnit.test('many2ones in form views', async function (assert) { assert.expect(5); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -174,7 +174,7 @@ QUnit.module('fields', {}, function () { mockRPC: function (route, args) { if (args.method === 'get_formview_action') { assert.deepEqual(args.args[0], [4], "should call get_formview_action with correct id"); - return $.when({ + return Promise.resolve({ res_id: 17, type: 'ir.actions.act_window', target: 'current', @@ -183,7 +183,7 @@ QUnit.module('fields', {}, function () { } if (args.method === 'get_formview_id') { assert.deepEqual(args.args[0], [4], "should call get_formview_id with correct id"); - return $.when(false); + return Promise.resolve(false); } return this._super(route, args); }, @@ -196,23 +196,22 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('a.o_form_uri:contains(aaa)').length, 1, "should contain a link"); - testUtils.dom.click(form.$('a.o_form_uri')); + await testUtils.dom.click(form.$('a.o_form_uri')); - testUtils.form.clickEdit(form); - - testUtils.dom.click(form.$('.o_external_button')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_external_button')); assert.strictEqual($('.modal .modal-title').text().trim(), 'Open: custom label', "dialog title should display the custom string label"); - // TODO: test that we can edit the record in the dialog, and that the value is correctly - // updated on close + // TODO: test that we can edit the record in the dialog, and that + // the value is correctly updated on close form.destroy(); }); - QUnit.test('editing a many2one, but not changing anything', function (assert) { + QUnit.test('editing a many2one, but not changing anything', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -228,7 +227,7 @@ QUnit.module('fields', {}, function () { mockRPC: function (route, args) { if (args.method === 'get_formview_id') { assert.deepEqual(args.args[0], [4], "should call get_formview_id with correct id"); - return $.when(false); + return Promise.resolve(false); } return this._super(route, args); }, @@ -237,16 +236,16 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // click on the external button (should do an RPC) - testUtils.dom.click(form.$('.o_external_button')); + await testUtils.dom.click(form.$('.o_external_button')); // save and close modal - testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); + await testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); // save form - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); // click next on pager - testUtils.dom.click(form.pager.$('.o_pager_next')); + await testUtils.dom.click(form.pager.$('.o_pager_next')); // this checks that the view did not ask for confirmation that the // record is dirty @@ -255,13 +254,13 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('context in many2one and default get', function (assert) { + QUnit.test('context in many2one and default get', async function (assert) { assert.expect(1); this.data.partner.fields.int_field.default = 14; this.data.partner.fields.trululu.default = 2; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -280,9 +279,9 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('editing a many2one (with form view opened with external button)', function (assert) { + QUnit.test('editing a many2one (with form view opened with external button)', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -297,7 +296,7 @@ QUnit.module('fields', {}, function () { res_id: 1, mockRPC: function (route, args) { if (args.method === 'get_formview_id') { - return $.when(false); + return Promise.resolve(false); } return this._super(route, args); }, @@ -306,19 +305,19 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // click on the external button (should do an RPC) - testUtils.dom.click(form.$('.o_external_button')); + await testUtils.dom.click(form.$('.o_external_button')); - testUtils.fields.editInput($('.modal input[name="foo"]'), 'brandon'); + await testUtils.fields.editInput($('.modal input[name="foo"]'), 'brandon'); // save and close modal - testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); + await testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); // save form - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); // click next on pager - testUtils.dom.click(form.pager.$('.o_pager_next')); + await testUtils.dom.click(form.pager.$('.o_pager_next')); // this checks that the view did not ask for confirmation that the // record is dirty @@ -327,9 +326,9 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('many2ones in form views with show_adress', function (assert) { + QUnit.test('many2ones in form views with show_adress', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -359,7 +358,7 @@ QUnit.module('fields', {}, function () { assert.strictEqual($('a.o_form_uri').html(), 'aaa<br>Street<br>City ZIP', "input should have a multi-line content in readonly due to show_address"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, 'button.o_external_button:visible', "should have an open record button"); @@ -374,12 +373,12 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('show_address works in a view embedded in a view of another type', function (assert) { + QUnit.test('show_address works in a view embedded in a view of another type', async function (assert) { assert.expect(1); this.data.turtle.records[1].turtle_trululu = 2; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -410,19 +409,19 @@ QUnit.module('fields', {}, function () { }, }); // click the turtle field, opens a modal with the turtle form view - testUtils.dom.click(form.$('.o_data_row:first td.o_data_cell')); + await testUtils.dom.click(form.$('.o_data_row:first td.o_data_cell')); assert.strictEqual($('[name="turtle_trululu"]').text(), "second recordrue morgueparis 75013", "The partner's address should be displayed"); form.destroy(); }); - QUnit.test('many2one data is reloaded if there is a context to take into account', function (assert) { + QUnit.test('many2one data is reloaded if there is a context to take into account', async function (assert) { assert.expect(1); this.data.turtle.records[1].turtle_trululu = 2; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -454,14 +453,14 @@ QUnit.module('fields', {}, function () { }, }); // click the turtle field, opens a modal with the turtle form view - testUtils.dom.click(form.$('.o_data_row:first')); + await testUtils.dom.click(form.$('.o_data_row:first')); assert.strictEqual($('.modal [name=turtle_trululu]').text(), "second recordrue morgueparis 75013", "The partner's address should be displayed"); form.destroy(); }); - QUnit.test('many2ones in form views with search more', function (assert) { + QUnit.test('many2ones in form views with search more', async function (assert) { assert.expect(3); this.data.partner.records.push({ id: 5, @@ -483,7 +482,7 @@ QUnit.module('fields', {}, function () { display_name: "Partner 9", }); this.data.partner.fields.datetime.searchable = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -501,29 +500,29 @@ QUnit.module('fields', {}, function () { res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); - testUtils.fields.many2one.clickOpenDropdown('trululu'); - testUtils.fields.many2one.clickItem('trululu', 'Search'); + await testUtils.fields.many2one.clickOpenDropdown('trululu'); + await testUtils.fields.many2one.clickItem('trululu', 'Search'); assert.strictEqual($('tr.o_data_row').length, 9, "should display 9 records"); - testUtils.dom.click($('button:contains(Filters)')); - testUtils.dom.click($('.o_add_custom_filter:visible')); + await testUtils.dom.click($('button:contains(Filters)')); + await testUtils.dom.click($('.o_add_custom_filter:visible')); assert.strictEqual($('.o_filter_condition select.o_searchview_extended_prop_field').val(), 'datetime', "datetime field should be selected"); - testUtils.dom.click($('.o_apply_filter')); + await testUtils.dom.click($('.o_apply_filter')); assert.strictEqual($('tr.o_data_row').length, 0, "should display 0 records"); form.destroy(); }); - QUnit.test('onchanges on many2ones trigger when editing record in form view', function (assert) { + QUnit.test('onchanges on many2ones trigger when editing record in form view', async function (assert) { assert.expect(10); this.data.partner.onchanges.user_id = function () { }; this.data.user.fields.other_field = { string: "Other Field", type: "char" }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -541,7 +540,7 @@ QUnit.module('fields', {}, function () { mockRPC: function (route, args) { assert.step(args.method); if (args.method === 'get_formview_id') { - return $.when(false); + return Promise.resolve(false); } if (args.method === 'onchange') { assert.strictEqual(args.args[1].user_id, 17, @@ -552,25 +551,25 @@ QUnit.module('fields', {}, function () { }); // open the many2one in form view and change something - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_external_button')); - testUtils.fields.editInput($('.modal-body input[name="other_field"]'), 'wood'); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_external_button')); + await testUtils.fields.editInput($('.modal-body input[name="other_field"]'), 'wood'); // save the modal and make sure an onchange is triggered - testUtils.dom.click($('.modal .modal-footer .btn-primary').first()); + await testUtils.dom.click($('.modal .modal-footer .btn-primary').first()); assert.verifySteps(['read', 'get_formview_id', 'load_views', 'read', 'write', 'onchange', 'read']); // save the main record, and check that no extra rpcs are done (record // is not dirty, only a related record was modified) - testUtils.form.clickSave(form); - assert.verifySteps(['read', 'get_formview_id', 'load_views', 'read', 'write', 'onchange', 'read']); + await testUtils.form.clickSave(form); + assert.verifySteps([]); form.destroy(); }); - QUnit.test('many2one fields with option "no_open"', function (assert) { + QUnit.test('many2one fields with option "no_open"', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -588,13 +587,13 @@ QUnit.module('fields', {}, function () { "should be displayed inside a span (sanity check)"); assert.containsNone(form, 'span.o_form_uri', "should not have an anchor"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsNone(form, '.o_field_widget[name=trululu] .o_external_button', "should not have the button to open the record"); form.destroy(); }); - QUnit.test('many2one in edit mode', function (assert) { + QUnit.test('many2one in edit mode', async function (assert) { assert.expect(16); // create 10 partners to have the 'Search More' option in the autocomplete dropdown @@ -603,7 +602,7 @@ QUnit.module('fields', {}, function () { this.data.partner.records.push({ id: id, display_name: "Partner " + id }); } - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -635,11 +634,11 @@ QUnit.module('fields', {}, function () { event.data.callback({ user_context: {} }); }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); - testUtils.fields.many2one.clickOpenDropdown('trululu'); + await testUtils.fields.many2one.clickOpenDropdown('trululu'); assert.ok($dropdown.is(':visible'), 'clicking on the m2o input should open the dropdown if it is not open yet'); assert.strictEqual($dropdown.find('li:not(.o_m2o_dropdown_option)').length, 7, @@ -647,51 +646,50 @@ QUnit.module('fields', {}, function () { assert.strictEqual($dropdown.find('li.o_m2o_dropdown_option').length, 2, 'autocomplete should contain "Search More" and Create and Edit..."'); - testUtils.fields.many2one.clickOpenDropdown('trululu'); + await testUtils.fields.many2one.clickOpenDropdown('trululu'); assert.ok(!$dropdown.is(':visible'), 'clicking on the m2o input should close the dropdown if it is open'); // change the value of the m2o with a suggestion of the dropdown - testUtils.fields.many2one.clickOpenDropdown('trululu'); - testUtils.fields.many2one.clickHighlightedItem('trululu'); + await testUtils.fields.many2one.clickOpenDropdown('trululu'); + await testUtils.fields.many2one.clickHighlightedItem('trululu'); assert.ok(!$dropdown.is(':visible'), 'clicking on a value should close the dropdown'); assert.strictEqual(form.$('.o_field_many2one input').val(), 'first record', 'value of the m2o should have been correctly updated'); // change the value of the m2o with a record in the 'Search More' modal - testUtils.fields.many2one.clickOpenDropdown('trululu'); + await testUtils.fields.many2one.clickOpenDropdown('trululu'); // click on 'Search More' (mouseenter required by ui-autocomplete) - testUtils.fields.many2one.clickItem('trululu', 'Search'); + await testUtils.fields.many2one.clickItem('trululu', 'Search'); assert.ok($('.modal .o_list_view').length, "should have opened a list view in a modal"); assert.ok(!$('.modal .o_list_view .o_list_record_selector').length, "there should be no record selector in the list view"); assert.ok(!$('.modal .modal-footer .o_select_button').length, "there should be no 'Select' button in the footer"); assert.ok($('.modal tbody tr').length > 10, "list should contain more than 10 records"); - // filter the list using the searchview - $('.modal .o_searchview_input').trigger({ type: 'keypress', which: 80 }); // P - $('.modal .o_searchview_input').trigger({ type: 'keydown', which: 13 }); // enter + await testUtils.fields.triggerKey('press', $('.modal .o_searchview_input'), 'P'), + await testUtils.fields.triggerKeydown($('.modal .o_searchview_input'), 'enter'); assert.strictEqual($('.modal tbody tr').length, 10, "list should be restricted to records containing a P (10 records)"); // choose a record - testUtils.dom.click($('.modal tbody tr:contains(Partner 20)')); + await testUtils.dom.click($('.modal tbody tr:contains(Partner 20)')); assert.ok(!$('.modal').length, "should have closed the modal"); assert.ok(!$dropdown.is(':visible'), 'should have closed the dropdown'); assert.strictEqual(form.$('.o_field_many2one input').val(), 'Partner 20', 'value of the m2o should have been correctly updated'); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('a.o_form_uri').text(), 'Partner 20', "should display correct value after save"); form.destroy(); }); - QUnit.test('many2one in non edit mode', function (assert) { + QUnit.test('many2one in non edit mode', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -707,9 +705,9 @@ QUnit.module('fields', {}, function () { "href should contain id and model"); // Remove value from many2one and then save, there should not have href with id and model on m2o anchor - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); form.$('.o_field_many2one input').val('').trigger('keyup').trigger('focusout'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.hasAttrValue(form.$('a.o_form_uri'), 'href', "#", "href should have #"); @@ -717,14 +715,13 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('many2one searches with correct value', function (assert) { - var done = assert.async(); + QUnit.test('many2one searches with correct value', async function (assert) { assert.expect(6); var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -748,31 +745,27 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('.o_field_many2one input').val(), 'aaa', "should be initially set to 'aaa'"); - testUtils.dom.click(form.$('.o_field_many2one input')); + await testUtils.dom.click(form.$('.o_field_many2one input')); // unset the many2one -> should search again with '' form.$('.o_field_many2one input').val('').trigger('keydown'); - concurrency.delay(0).then(function () { - // write 'p' -> should search with 'p' - form.$('.o_field_many2one input').val('p').trigger('keydown').trigger('keyup'); + await testUtils.nextTick(); + form.$('.o_field_many2one input').val('p').trigger('keydown').trigger('keyup'); + await testUtils.nextTick(); - return concurrency.delay(0); - }).then(function () { - // close and re-open the dropdown -> should search with 'p' again - testUtils.dom.click(form.$('.o_field_many2one input')); - testUtils.dom.click(form.$('.o_field_many2one input')); + // close and re-open the dropdown -> should search with 'p' again + await testUtils.dom.click(form.$('.o_field_many2one input')); + await testUtils.dom.click(form.$('.o_field_many2one input')); - assert.verifySteps(['search: ', 'search: ', 'search: p', 'search: p']); + assert.verifySteps(['search: ', 'search: ', 'search: p', 'search: p']); - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - form.destroy(); - done(); - }); + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + form.destroy(); }); - QUnit.test('many2one field with option always_reload', function (assert) { + QUnit.test('many2one field with option always_reload', async function (assert) { assert.expect(4); var count = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -783,7 +776,7 @@ QUnit.module('fields', {}, function () { mockRPC: function (route, args) { if (args.method === 'name_get') { count++; - return $.when([[1, "first record\nand some address"]]); + return Promise.resolve([[1, "first record\nand some address"]]); } return this._super(route, args); }, @@ -793,22 +786,22 @@ QUnit.module('fields', {}, function () { assert.ok(form.$('a:contains(and some address)').length, "should display additional result"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_widget[name=trululu] input').val(), "first record", "actual field value should be displayed to be edited"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.ok(form.$('a:contains(and some address)').length, "should still display additional result"); form.destroy(); }); - QUnit.test('many2one field and list navigation', function (assert) { + QUnit.test('many2one field and list navigation', async function (assert) { assert.expect(3); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -816,14 +809,11 @@ QUnit.module('fields', {}, function () { }); // edit first input, to trigger autocomplete - testUtils.dom.click(list.$('.o_data_row .o_data_cell').first()); - testUtils.fields.editInput(list.$('.o_data_cell input'), ''); + await testUtils.dom.click(list.$('.o_data_row .o_data_cell').first()); + await testUtils.fields.editInput(list.$('.o_data_cell input'), ''); // press keydown, to select first choice - list.$('.o_data_cell input').focus().trigger($.Event('keydown', { - which: $.ui.keyCode.DOWN, - keyCode: $.ui.keyCode.DOWN, - })); + await testUtils.fields.triggerKeydown(list.$('.o_data_cell input').focus(), 'down'); // we now check that the dropdown is open (and that the focus did not go // to the next line) @@ -839,7 +829,6 @@ QUnit.module('fields', {}, function () { QUnit.test('standalone many2one field', async function (assert) { assert.expect(4); - var done = assert.async(); var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; @@ -851,54 +840,53 @@ QUnit.module('fields', {}, function () { Model: BasicModel, data: this.data, }); - + var record; model.makeRecord('coucou', [{ name: 'partner_id', relation: 'partner', type: 'many2one', value: [1, 'first partner'], }]).then(function (recordID) { - var record = model.get(recordID); - // create a new widget that uses the StandaloneFieldManagerMixin - var StandaloneWidget = Widget.extend(StandaloneFieldManagerMixin, { - init: function (parent) { - this._super.apply(this, arguments); - StandaloneFieldManagerMixin.init.call(this, parent); - }, - }); - var parent = new StandaloneWidget(model); - testUtils.mock.addMockEnvironment(parent, { - data: self.data, - mockRPC: function (route, args) { - assert.step(args.method); - return this._super.apply(this, arguments); - }, - }); + record = model.get(recordID); + }); + await testUtils.nextTick(); + // create a new widget that uses the StandaloneFieldManagerMixin + var StandaloneWidget = Widget.extend(StandaloneFieldManagerMixin, { + init: function (parent) { + this._super.apply(this, arguments); + StandaloneFieldManagerMixin.init.call(this, parent); + }, + }); + var parent = new StandaloneWidget(model); + testUtils.mock.addMockEnvironment(parent, { + data: self.data, + mockRPC: function (route, args) { + assert.step(args.method); + return this._super.apply(this, arguments); + }, + }); - var relField = new relationalFields.FieldMany2One(parent, 'partner_id', record, { - mode: 'edit', - noOpen: true, - }); + var relField = new relationalFields.FieldMany2One(parent, 'partner_id', record, { + mode: 'edit', + noOpen: true, + }); - relField.appendTo(fixture); - testUtils.fields.editInput($('input.o_input'), 'xyzzrot'); + relField.appendTo(fixture); + await testUtils.nextTick(); + await testUtils.fields.editInput($('input.o_input'), 'xyzzrot'); - concurrency.delay(0).then(function () { - testUtils.fields.many2one.clickItem('partner_id', 'Create'); + await testUtils.fields.many2one.clickItem('partner_id', 'Create'); - assert.containsNone(relField, '.o_external_button', - "should not have the button to open the record"); - assert.verifySteps(['name_search', 'name_create']); + assert.containsNone(relField, '.o_external_button', + "should not have the button to open the record"); + assert.verifySteps(['name_search', 'name_create']); - parent.destroy(); - model.destroy(); - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - done(); - }); - }); + parent.destroy(); + model.destroy(); + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; }); - // QUnit.test('onchange on a many2one to a different model', function (assert) { + // QUnit.test('onchange on a many2one to a different model', async function (assert) { // This test is commented because the mock server does not give the correct response. // It should return a couple [id, display_name], but I don't know the logic used // by the server, so it's hard to emulate it correctly @@ -911,7 +899,7 @@ QUnit.module('fields', {}, function () { // }, // }; - // var form = createView({ + // var form = await createView({ // View: FormView, // model: 'partner', // data: this.data, @@ -921,7 +909,7 @@ QUnit.module('fields', {}, function () { // '</form>', // res_id: 1, // }); - // testUtils.form.clickEdit(form); + // await testUtils.form.clickEdit(form); // assert.strictEqual(form.$('input').eq(1).val(), 'xpad', "initial product_id val should be xpad"); // testUtils.fields.editInput(form.$('input').eq(0), "let us trigger an onchange"); @@ -932,9 +920,9 @@ QUnit.module('fields', {}, function () { QUnit.test('form: quick create then save directly', async function (assert) { assert.expect(5); - var def = $.Deferred(); + var prom = testUtils.makeTestPromise(); var newRecordID; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -945,7 +933,7 @@ QUnit.module('fields', {}, function () { var result = this._super.apply(this, arguments); if (args.method === 'name_create') { assert.step('name_create'); - return def.then(_.constant(result)).then(function (nameGet) { + return prom.then(_.constant(result)).then(function (nameGet) { newRecordID = nameGet[0]; return nameGet; }); @@ -959,23 +947,24 @@ QUnit.module('fields', {}, function () { }, }); await testUtils.fields.many2one.searchAndClickItem('trululu', {search: 'b'}); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.verifySteps(['name_create'], "should wait for the name_create before creating the record"); - def.resolve(); + await prom.resolve(); + await testUtils.nextTick(); - assert.verifySteps(['name_create', 'create']); + assert.verifySteps(['create']); form.destroy(); }); QUnit.test('list: quick create then save directly', async function (assert) { assert.expect(8); - var def = $.Deferred(); + var prom = testUtils.makeTestPromise(); var newRecordID; - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -986,7 +975,7 @@ QUnit.module('fields', {}, function () { var result = this._super.apply(this, arguments); if (args.method === 'name_create') { assert.step('name_create'); - return def.then(_.constant(result)).then(function (nameGet) { + return prom.then(_.constant(result)).then(function (nameGet) { newRecordID = nameGet[0]; return nameGet; }); @@ -1000,7 +989,7 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); await testUtils.fields.many2one.searchAndClickItem('trululu', {search:'b'}); list.$buttons.find('.o_list_button_add').show(); @@ -1011,9 +1000,10 @@ QUnit.module('fields', {}, function () { assert.containsN(list, '.o_data_row', 4, "should wait for the name_create before adding the new row"); - def.resolve(); + await prom.resolve(); + await testUtils.nextTick(); - assert.verifySteps(['name_create', 'create']); + assert.verifySteps(['create']); assert.strictEqual(list.$('.o_data_row:nth(1) .o_data_cell').text(), 'b', "created row should have the correct m2o value"); assert.containsN(list, '.o_data_row', 5, "should have added the fifth row"); @@ -1024,9 +1014,9 @@ QUnit.module('fields', {}, function () { QUnit.test('list in form: quick create then save directly', async function (assert) { assert.expect(6); - var def = $.Deferred(); + var prom = testUtils.makeTestPromise(); var newRecordID; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1043,7 +1033,7 @@ QUnit.module('fields', {}, function () { var result = this._super.apply(this, arguments); if (args.method === 'name_create') { assert.step('name_create'); - return def.then(_.constant(result)).then(function (nameGet) { + return prom.then(_.constant(result)).then(function (nameGet) { newRecordID = nameGet[0]; return nameGet; }); @@ -1057,28 +1047,28 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); await testUtils.fields.many2one.searchAndClickItem('trululu', {search: 'b'}); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.verifySteps(['name_create'], "should wait for the name_create before creating the record"); - def.resolve(); + await prom.resolve(); + await testUtils.nextTick(); - assert.verifySteps(['name_create', 'create']); + assert.verifySteps(['create']); assert.strictEqual(form.$('.o_data_row:first .o_data_cell').text(), 'b', "first row should have the correct m2o value"); form.destroy(); }); - QUnit.test('list in form: quick create then add a new line directly', function (assert) { + QUnit.test('list in form: quick create then add a new line directly', async function (assert) { // required many2one inside a one2many list: directly after quick creating // a new many2one value (before the name_create returns), click on add an item: // at this moment, the many2one has still no value, and as it is required, // the row is discarded if a saveLine is requested. However, it should // wait for the name_create to return before trying to save the line. - var done = assert.async(); assert.expect(8); this.data.partner.onchanges = { @@ -1088,9 +1078,9 @@ QUnit.module('fields', {}, function () { var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - var def = $.Deferred(); + var prom = testUtils.makeTestPromise(); var newRecordID; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1106,7 +1096,7 @@ QUnit.module('fields', {}, function () { mockRPC: function (route, args) { var result = this._super.apply(this, arguments); if (args.method === 'name_create') { - return def.then(_.constant(result)).then(function (nameGet) { + return prom.then(_.constant(result)).then(function (nameGet) { newRecordID = nameGet[0]; return nameGet; }); @@ -1118,43 +1108,42 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - form.$('.o_field_many2one input').val('b').trigger('keydown'); - concurrency.delay(0).then(function () { - testUtils.fields.many2one.clickHighlightedItem('trululu'); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editAndTrigger(form.$('.o_field_many2one input'), + 'b', 'keydown'); + await testUtils.fields.many2one.clickHighlightedItem('trululu'); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - assert.containsOnce(form, '.o_data_row', - "there should still be only one row"); - assert.hasClass(form.$('.o_data_row'),'o_selected_row', - "the row should still be in edition"); + assert.containsOnce(form, '.o_data_row', + "there should still be only one row"); + assert.hasClass(form.$('.o_data_row'),'o_selected_row', + "the row should still be in edition"); - def.resolve(); + await prom.resolve(); + await testUtils.nextTick(); - assert.strictEqual(form.$('.o_data_row:first .o_data_cell').text(), 'b', - "first row should have the correct m2o value"); - assert.containsN(form, '.o_data_row', 2, - "there should now be 2 rows"); - assert.hasClass(form.$('.o_data_row:nth(1)'),'o_selected_row', - "the second row should be in edition"); + assert.strictEqual(form.$('.o_data_row:first .o_data_cell').text(), 'b', + "first row should have the correct m2o value"); + assert.containsN(form, '.o_data_row', 2, + "there should now be 2 rows"); + assert.hasClass(form.$('.o_data_row:nth(1)'),'o_selected_row', + "the second row should be in edition"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); - assert.containsOnce(form, '.o_data_row', - "there should be 1 row saved (the second one was empty and invalid)"); - assert.strictEqual(form.$('.o_data_row .o_data_cell').text(), 'b', - "should have the correct m2o value"); + assert.containsOnce(form, '.o_data_row', + "there should be 1 row saved (the second one was empty and invalid)"); + assert.strictEqual(form.$('.o_data_row .o_data_cell').text(), 'b', + "should have the correct m2o value"); - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - form.destroy(); - done(); - }); + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + form.destroy(); }); - QUnit.test('list in form: create with one2many with many2one', function (assert) { + QUnit.test('list in form: create with one2many with many2one', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1170,7 +1159,7 @@ QUnit.module('fields', {}, function () { '</form>', mockRPC: function (route, args) { if (args.method === 'default_get') { - return $.when({ p: [[0, 0, { display_name: 'new record' }]] }); + return Promise.resolve({ p: [[0, 0, { display_name: 'new record' }]] }); } else if (args.method === 'name_get') { // This should not be called at all and thus is not accounted for // in the assert.expect. If this is called, you broke this test. @@ -1187,13 +1176,13 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('list in form: create with one2many with many2one (version 2)', function (assert) { + QUnit.test('list in form: create with one2many with many2one (version 2)', async function (assert) { // This test simulates the exact same scenario as the previous one, // except that the value for the many2one is explicitely set to false, // which is stupid, but this happens, so we have to handle it assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1209,7 +1198,7 @@ QUnit.module('fields', {}, function () { '</form>', mockRPC: function (route, args) { if (args.method === 'default_get') { - return $.when({ p: [[0, 0, { display_name: 'new record', trululu: false }]] }); + return Promise.resolve({ p: [[0, 0, { display_name: 'new record', trululu: false }]] }); } else if (args.method === 'name_get') { // This should not be called at all and thus is not accounted for // in the assert.expect. If this is called, you broke this test. @@ -1226,14 +1215,14 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('item not dropped on discard with empty required field (default_get)', function (assert) { + QUnit.test('item not dropped on discard with empty required field (default_get)', async function (assert) { // This test simulates discarding a record that has been created with // one of its required field that is empty. When we discard the changes // on this empty field, it should not assume that this record should be // abandonned, since it has been added (even though it is a new record). assert.expect(8); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1249,7 +1238,7 @@ QUnit.module('fields', {}, function () { '</form>', mockRPC: function (route, args) { if (args.method === 'default_get') { - return $.when({ p: [[0, 0, { display_name: 'new record', trululu: false }]] }); + return Promise.resolve({ p: [[0, 0, { display_name: 'new record', trululu: false }]] }); } return this._super.apply(this, arguments); }, @@ -1284,12 +1273,12 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('list in form: name_get with unique ids (default_get)', function (assert) { + QUnit.test('list in form: name_get with unique ids (default_get)', async function (assert) { assert.expect(2); this.data.partner.records[0].display_name = "MyTrululu"; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1304,7 +1293,7 @@ QUnit.module('fields', {}, function () { '</form>', mockRPC: function (route, args) { if (args.method === 'default_get') { - return $.when({ + return Promise.resolve({ p: [ [0, 0, { trululu: 1 }], [0, 0, { trululu: 1 }] @@ -1325,10 +1314,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('list in form: show name of many2one fields in multi-page (default_get)', function (assert) { + QUnit.test('list in form: show name of many2one fields in multi-page (default_get)', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1344,7 +1333,7 @@ QUnit.module('fields', {}, function () { '</form>', mockRPC: function (route, args) { if (args.method === 'default_get') { - return $.when({ + return Promise.resolve({ p: [ [0, 0, { display_name: 'record1', trululu: 1 }], [0, 0, { display_name: 'record2', trululu: 2 }] @@ -1360,7 +1349,7 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('td.o_data_cell').first().next().text(), "first record", "should show display_name of trululu of 1st record"); - testUtils.dom.click(form.$('button.o_pager_next')); + await testUtils.dom.click(form.$('button.o_pager_next')); assert.strictEqual(form.$('td.o_data_cell').first().text(), "record2", "should show display_name of 2nd record"); @@ -1370,7 +1359,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('list in form: item not dropped on discard with empty required field (onchange in default_get)', function (assert) { + QUnit.test('list in form: item not dropped on discard with empty required field (onchange in default_get)', async function (assert) { // variant of the test "list in form: discard newly added element with // empty required field (default_get)", in which the `default_get` // performs an `onchange` at the same time. This `onchange` may create @@ -1389,7 +1378,7 @@ QUnit.module('fields', {}, function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1404,7 +1393,7 @@ QUnit.module('fields', {}, function () { '</form>', mockRPC: function (route, args) { if (args.method === 'default_get') { - return $.when({ + return Promise.resolve({ product_id: 37, }); } @@ -1440,7 +1429,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('list in form: item not dropped on discard with empty required field (onchange on list after default_get)', function (assert) { + QUnit.test('list in form: item not dropped on discard with empty required field (onchange on list after default_get)', async function (assert) { // discarding a record from an `onchange` in a `default_get` should not // abandon the record. This should not be the case for following // `onchange`, except if an onchange make some changes on the list: @@ -1459,7 +1448,7 @@ QUnit.module('fields', {}, function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1479,8 +1468,8 @@ QUnit.module('fields', {}, function () { "should have no row in the editable list"); // select product_id to force on_change in editable list - testUtils.dom.click(form.$('.o_field_widget[name="product_id"] .o_input')); - testUtils.dom.click($('.ui-menu-item').first()); + await testUtils.dom.click(form.$('.o_field_widget[name="product_id"] .o_input')); + await testUtils.dom.click($('.ui-menu-item').first()); // check that there is a record in the editable list with empty string as required field assert.containsOnce(form, '.o_data_row', @@ -1494,9 +1483,9 @@ QUnit.module('fields', {}, function () { "should have empty string in the required field on this record"); // click on empty required field in editable list record - testUtils.dom.click(requiredField); + await testUtils.dom.click(requiredField); // click off so that the required field still stay empty - testUtils.dom.click($('body')); + await testUtils.dom.click($('body')); // record should not be dropped assert.containsOnce(form, '.o_data_row', @@ -1510,13 +1499,13 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('item dropped on discard with empty required field with "Add an item" (invalid on "ADD")', function (assert) { + QUnit.test('item dropped on discard with empty required field with "Add an item" (invalid on "ADD")', async function (assert) { // when a record in a list is added with "Add an item", it should // always be dropped on discard if some required field are empty // at the record creation. assert.expect(6); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1531,7 +1520,7 @@ QUnit.module('fields', {}, function () { }); // Click on "Add an item" - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); var charField = form.$('.o_field_widget.o_field_char[name="display_name"]'); var requiredField = form.$('.o_field_widget.o_required_modifier[name="trululu"]'); charField.val("some text"); @@ -1547,9 +1536,9 @@ QUnit.module('fields', {}, function () { "should have empty string in the required field on this record"); // click on empty required field in editable list record - testUtils.dom.click(requiredField); + await testUtils.dom.click(requiredField); // click off so that the required field still stay empty - testUtils.dom.click($('body')); + await testUtils.dom.click($('body')); // record should be dropped assert.containsNone(form, '.o_data_row', @@ -1558,7 +1547,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('item not dropped on discard with empty required field with "Add an item" (invalid on "UPDATE")', function (assert) { + QUnit.test('item not dropped on discard with empty required field with "Add an item" (invalid on "UPDATE")', async function (assert) { // when a record in a list is added with "Add an item", it should // be temporarily added to the list when it is valid (e.g. required // fields are non-empty). If the record is updated so that the required @@ -1569,7 +1558,7 @@ QUnit.module('fields', {}, function () { var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1587,7 +1576,7 @@ QUnit.module('fields', {}, function () { "should initially not have any record in the list"); // Click on "Add an item" - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.containsOnce(form, '.o_data_row', "should have a temporary record in the list"); @@ -1598,9 +1587,9 @@ QUnit.module('fields', {}, function () { "should have empty string in the required field on this record"); // add something to required field and leave edit mode of the record - testUtils.dom.click($inputEditMode); - testUtils.dom.click($('li.ui-menu-item').first()); - testUtils.dom.click($('body')); + await testUtils.dom.click($inputEditMode); + await testUtils.dom.click($('li.ui-menu-item').first()); + await testUtils.dom.click($('body')); var $inputReadonlyMode = form.$('.o_data_cell.o_required_modifier'); assert.containsOnce(form, '.o_data_row', @@ -1609,7 +1598,7 @@ QUnit.module('fields', {}, function () { "should have put some content in the required field on this record"); // remove the required field and leave edit mode of the record - testUtils.dom.click($('.o_data_row')); + await testUtils.dom.click($('.o_data_row')); assert.containsOnce(form, '.o_data_row', "should not have dropped record in the list on discard (invalid on UPDATE)"); assert.strictEqual($inputReadonlyMode.text(), "first record", @@ -1619,7 +1608,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('list in form: default_get with x2many create', function (assert) { + QUnit.test('list in form: default_get with x2many create', async function (assert) { assert.expect(5); var displayName = 'brandon is the new timmy'; @@ -1634,7 +1623,7 @@ QUnit.module('fields', {}, function () { obj.int_field = obj.timmy.length; }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1650,7 +1639,7 @@ QUnit.module('fields', {}, function () { '</form>', mockRPC: function (route, args) { if (args.method === 'default_get') { - return $.when({ timmy: [[0, 0, { display_name: 'brandon is the new timmy', name: 'brandon' }]] }); + return Promise.resolve({ timmy: [[0, 0, { display_name: 'brandon is the new timmy', name: 'brandon' }]] }); } if (args.method === 'create') { assert.deepEqual(args.args[0], { @@ -1672,14 +1661,14 @@ QUnit.module('fields', {}, function () { // edit the subrecord and save displayName = 'new value'; - testUtils.dom.click(form.$('.o_data_cell')); - testUtils.fields.editInput(form.$('.o_data_cell input'), displayName); - testUtils.form.clickSave(form); + await testUtils.dom.click(form.$('.o_data_cell')); + await testUtils.fields.editInput(form.$('.o_data_cell input'), displayName); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('list in form: default_get with x2many create and onchange', function (assert) { + QUnit.test('list in form: default_get with x2many create and onchange', async function (assert) { assert.expect(2); this.data.partner.onchanges.turtles = function (obj) { @@ -1694,7 +1683,7 @@ QUnit.module('fields', {}, function () { "should have properly created the x2many command list"); }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1710,7 +1699,7 @@ QUnit.module('fields', {}, function () { '</form>', mockRPC: function (route, args) { if (args.method === 'default_get') { - return $.when({ turtles: [[6, 0, [2, 3]]] }); + return Promise.resolve({ turtles: [[6, 0, [2, 3]]] }); } if (args.method === 'create') { // it would be even better if we did not send the current @@ -1727,16 +1716,16 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('list in form: call button in sub view', function (assert) { + QUnit.test('list in form: call button in sub view', async function (assert) { assert.expect(6); this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1752,7 +1741,7 @@ QUnit.module('fields', {}, function () { res_id: 1, mockRPC: function (route, args) { if (route === '/web/dataset/call_kw/product/get_formview_id') { - return $.when(false); + return Promise.resolve(false); } return this._super.apply(this, arguments); }, @@ -1777,16 +1766,16 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('td.o_data_cell:first')); - testUtils.dom.click(form.$('.o_external_button')); - testUtils.dom.click($('button:contains("Just do it !")')); - testUtils.dom.click($('button:contains("Just don\'t do it !")')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('td.o_data_cell:first')); + await testUtils.dom.click(form.$('.o_external_button')); + await testUtils.dom.click($('button:contains("Just do it !")')); + await testUtils.dom.click($('button:contains("Just don\'t do it !")')); form.destroy(); }); - QUnit.test('X2Many sequence list in modal', function (assert) { + QUnit.test('X2Many sequence list in modal', async function (assert) { assert.expect(5); this.data.partner.fields.sequence = { string: 'Sequence', type: 'integer' }; @@ -1810,7 +1799,7 @@ QUnit.module('fields', {}, function () { this.data.partner_type.fields.partner_ids = { string: "Partner", type: "one2many", relation: 'partner' }; this.data.partner_type.records[0].partner_ids = [1, 2]; - testUtils.createAsyncView({ + var form = await createView({ View: FormView, model: 'product', data: this.data, @@ -1832,52 +1821,52 @@ QUnit.module('fields', {}, function () { res_id: 37, mockRPC: function (route, args) { if (route === '/web/dataset/call_kw/product/read') { - return $.when([{ id: 37, name: 'xphone', display_name: 'leonardo', turtle_ids: [1] }]); + return Promise.resolve([{ id: 37, name: 'xphone', display_name: 'leonardo', turtle_ids: [1] }]); } if (route === '/web/dataset/call_kw/turtle/read') { - return $.when([{ id: 1, type_id: [12, 'gold'] }]); + return Promise.resolve([{ id: 1, type_id: [12, 'gold'] }]); } if (route === '/web/dataset/call_kw/partner_type/get_formview_id') { - return $.when(false); + return Promise.resolve(false); } if (route === '/web/dataset/call_kw/partner_type/read') { - return $.when([{ id: 12, partner_ids: [1, 2], display_name: 'gold' }]); + return Promise.resolve([{ id: 12, partner_ids: [1, 2], display_name: 'gold' }]); } if (route === '/web/dataset/call_kw/partner_type/write') { assert.step('partner_type write'); } return this._super.apply(this, arguments); }, - }).then(function (form) { - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_data_cell')); - testUtils.dom.click(form.$('.o_external_button')); + }); - var $modal = $('.modal'); - assert.equal($modal.length, 1, - 'There should be 1 modal opened'); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_data_cell')); + await testUtils.dom.click(form.$('.o_external_button')); - var $handles = $modal.find('.ui-sortable-handle'); - assert.equal($handles.length, 2, - 'There should be 2 sequence handlers'); + var $modal = $('.modal'); + assert.equal($modal.length, 1, + 'There should be 1 modal opened'); - testUtils.dom.dragAndDrop($handles.eq(1), $modal.find('tbody tr').first(), - { position: 'top' }); + var $handles = $modal.find('.ui-sortable-handle'); + assert.equal($handles.length, 2, + 'There should be 2 sequence handlers'); - // Saving the modal and then the original model - testUtils.dom.click($modal.find('.modal-footer .btn-primary')); - testUtils.form.clickSave(form); + await testUtils.dom.dragAndDrop($handles.eq(1), + $modal.find('tbody tr').first(), { position: 'top' }); - assert.verifySteps(['onchange sequence', 'partner_type write']); + // Saving the modal and then the original model + await testUtils.dom.click($modal.find('.modal-footer .btn-primary')); + await testUtils.form.clickSave(form); - form.destroy(); - }); + assert.verifySteps(['onchange sequence', 'partner_type write']); + + form.destroy(); }); - QUnit.test('autocompletion in a many2one, in form view with a domain', function (assert) { + QUnit.test('autocompletion in a many2one, in form view with a domain', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1895,16 +1884,16 @@ QUnit.module('fields', {}, function () { return this._super(route, args); } }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); testUtils.dom.click(form.$('.o_field_widget[name=product_id] input')); form.destroy(); }); - QUnit.test('autocompletion in a many2one, in form view with a date field', function (assert) { + QUnit.test('autocompletion in a many2one, in form view with a date field', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1921,13 +1910,13 @@ QUnit.module('fields', {}, function () { return this._super(route, args); }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); testUtils.dom.click(form.$('.o_field_widget[name=trululu] input')); form.destroy(); }); - QUnit.test('creating record with many2one with option always_reload', function (assert) { + QUnit.test('creating record with many2one with option always_reload', async function (assert) { assert.expect(2); this.data.partner.fields.trululu.default = 1; @@ -1939,7 +1928,7 @@ QUnit.module('fields', {}, function () { var count = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1949,7 +1938,7 @@ QUnit.module('fields', {}, function () { mockRPC: function (route, args) { count++; if (args.method === 'name_get' && args.args[0][0] === 2) { - return $.when([[2, "hello world\nso much noise"]]); + return Promise.resolve([[2, "hello world\nso much noise"]]); } return this._super(route, args); }, @@ -1961,10 +1950,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('selecting a many2one, then discarding', function (assert) { + QUnit.test('selecting a many2one, then discarding', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1974,23 +1963,23 @@ QUnit.module('fields', {}, function () { res_id: 1, }); assert.strictEqual(form.$('a[name=product_id]').text(), '', 'the tag a should be empty'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); - testUtils.fields.many2one.clickOpenDropdown('product_id'); - testUtils.fields.many2one.clickItem('product_id','xphone'); + await testUtils.fields.many2one.clickOpenDropdown('product_id'); + await testUtils.fields.many2one.clickItem('product_id','xphone'); assert.strictEqual(form.$('.o_field_widget[name=product_id] input').val(), "xphone", "should have selected xphone"); - testUtils.form.clickDiscard(form); + await testUtils.form.clickDiscard(form); assert.strictEqual(form.$('a[name=product_id]').text(), '', 'the tag a should be empty'); form.destroy(); }); - QUnit.test('domain and context are correctly used when doing a name_search in a m2o', function (assert) { + QUnit.test('domain and context are correctly used when doing a name_search in a m2o', async function (assert) { assert.expect(4); this.data.partner.records[0].timmy = [12]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2016,20 +2005,20 @@ QUnit.module('fields', {}, function () { { hey: "ho", hello: "world", test: "yop" }, 'the field attr context should have been used for the ' + 'RPC (evaluated and merged with the session one)'); - return $.when([]); + return Promise.resolve([]); } if (args.method === 'name_search' && args.model === 'partner') { assert.deepEqual(args.kwargs.args, [['id', 'in', [12]]], 'the field attr domain should have been used for the RPC (and evaluated)'); assert.deepEqual(args.kwargs.context, { hey: 'ho', timmy: [[6, false, [12]]] }, 'the field attr context should have been used for the RPC (and evaluated)'); - return $.when([]); + return Promise.resolve([]); } return this._super.apply(this, arguments); }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); testUtils.dom.click(form.$('.o_field_widget[name=product_id] input')); testUtils.dom.click(form.$('.o_field_widget[name=trululu] input')); @@ -2037,10 +2026,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('quick create on a many2one', function (assert) { + QUnit.test('quick create on a many2one', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2059,17 +2048,17 @@ QUnit.module('fields', {}, function () { }); form.$('.o_field_many2one input').focus(); - form.$('.o_field_many2one input').val('new partner').trigger('keyup').trigger('focusout'); - - testUtils.dom.click($('.modal .modal-footer .btn-primary').first()); + await testUtils.fields.editAndTrigger(form.$('.o_field_many2one input'), + 'new partner', ['keyup', 'focusout']); + await testUtils.dom.click($('.modal .modal-footer .btn-primary').first()); form.destroy(); }); - QUnit.test('slow create on a many2one', function (assert) { - assert.expect(7); + QUnit.test('slow create on a many2one', async function (assert) { + assert.expect(11); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2089,41 +2078,55 @@ QUnit.module('fields', {}, function () { // cancel the many2one creation with Cancel button form.$('.o_field_many2one input').focus().val('new product').trigger('keyup').trigger('blur'); + await testUtils.nextTick(); assert.strictEqual($('.modal').length, 1, "there should be one opened modal"); - testUtils.dom.click($('.modal .modal-footer .btn:contains(Cancel)')); + + await testUtils.dom.click($('.modal .modal-footer .btn:contains(Cancel)')); + assert.strictEqual($('.modal').length, 0, "the modal should be closed"); assert.strictEqual(form.$('.o_field_many2one input').val(), "", 'the many2one should not set a value as its creation has been cancelled (with Cancel button)'); // cancel the many2one creation with Close button - form.$('.o_field_many2one input').focus().val('new product').trigger('keyup').trigger('blur'); - testUtils.dom.click($('.modal .modal-header button')); + await testUtils.fields.editAndTrigger(form.$('.o_field_many2one input'), + 'new product', ['keyup', 'blur']); + assert.strictEqual($('.modal').length, 1, "there should be one opened modal"); + await testUtils.dom.click($('.modal .modal-header button')); assert.strictEqual(form.$('.o_field_many2one input').val(), "", 'the many2one should not set a value as its creation has been cancelled (with Close button)'); + assert.strictEqual($('.modal').length, 0, "the modal should be closed"); // select a new value then cancel the creation of the new one --> restore the previous - testUtils.fields.many2one.clickOpenDropdown('product_id'); - testUtils.fields.many2one.clickItem('product_id','o'); + await testUtils.fields.many2one.clickOpenDropdown('product_id'); + await testUtils.fields.many2one.clickItem('product_id','o'); assert.strictEqual(form.$('.o_field_many2one input').val(), "xphone", "should have selected xphone"); + form.$('.o_field_many2one input').focus().val('new product').trigger('keyup').trigger('blur'); + await testUtils.nextTick(); assert.strictEqual($('.modal').length, 1, "there should be one opened modal"); - testUtils.dom.click($('.modal .modal-footer .btn:contains(Cancel)')); + + await testUtils.dom.click($('.modal .modal-footer .btn:contains(Cancel)')); assert.strictEqual(form.$('.o_field_many2one input').val(), "xphone", 'should have restored the many2one with its previous selected value (xphone)'); // confirm the many2one creation - form.$('.o_field_many2one input').focus(); - form.$('.o_field_many2one input').val('new partner').trigger('keyup').trigger('blur'); - testUtils.dom.clickFirst($('.modal .modal-footer .btn-primary')); + form.$('.o_field_many2one input').focus().val('new partner').trigger('keyup').trigger('blur'); + await testUtils.nextTick(); + assert.strictEqual($('.modal').length, 1, "there should be one opened modal"); + + await testUtils.dom.click($('.modal .modal-footer .btn-primary:contains(Create and edit)')); + await testUtils.nextTick(); assert.strictEqual($('.modal .o_form_view').length, 1, 'a new modal should be opened and contain a form view'); + await testUtils.dom.click($('.modal .o_form_button_cancel')); + form.destroy(); }); - QUnit.test('no_create option on a many2one', function (assert) { + QUnit.test('no_create option on a many2one', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2134,14 +2137,14 @@ QUnit.module('fields', {}, function () { '</form>', }); - form.$('.o_field_many2one input').focus(); - form.$('.o_field_many2one input').val('new partner').trigger('keyup').trigger('focusout'); - + await testUtils.fields.editAndTrigger(form.$('.o_field_many2one input'), + 'new partner', ['keyup', 'focusout']); + await testUtils.nextTick(); assert.strictEqual($('.modal').length, 0, "should not display the create modal"); form.destroy(); }); - QUnit.test('can_create and can_write option on a many2one', function (assert) { + QUnit.test('can_create and can_write option on a many2one', async function (assert) { assert.expect(5); this.data.product.options = { @@ -2149,7 +2152,7 @@ QUnit.module('fields', {}, function () { can_write: "false", }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2163,117 +2166,98 @@ QUnit.module('fields', {}, function () { }, mockRPC: function (route) { if (route === '/web/dataset/call_kw/product/get_formview_id') { - return $.when(false); + return Promise.resolve(false); } return this._super.apply(this, arguments); }, }); - testUtils.dom.click(form.$('.o_field_many2one input')); + await testUtils.dom.click(form.$('.o_field_many2one input')); assert.strictEqual($('.ui-autocomplete .o_m2o_dropdown_option:contains(Create)').length, 0, "there shouldn't be any option to search and create"); - testUtils.dom.click($('.ui-autocomplete li:contains(xpad)').mouseenter()); + await testUtils.dom.click($('.ui-autocomplete li:contains(xpad)').mouseenter()); assert.strictEqual(form.$('.o_field_many2one input').val(), "xpad", "the correct record should be selected"); assert.containsOnce(form, '.o_field_many2one .o_external_button', "there should be an external button displayed"); - testUtils.dom.click(form.$('.o_field_many2one .o_external_button')); + await testUtils.dom.click(form.$('.o_field_many2one .o_external_button')); assert.strictEqual($('.modal .o_form_view.o_form_readonly').length, 1, "there should be a readonly form view opened"); - testUtils.dom.click($('.modal .o_form_button_cancel')); - - form.$('.o_field_many2one input').val('new product').trigger('keyup').trigger('focusout'); + await testUtils.dom.click($('.modal .o_form_button_cancel')); + await testUtils.fields.editAndTrigger(form.$('.o_field_many2one input'), + 'new product', ['keyup', 'focusout']); assert.strictEqual($('.modal').length, 0, "should not display the create modal"); form.destroy(); }); - QUnit.test('pressing enter in a m2o in an editable list', function (assert) { + QUnit.test('pressing enter in a m2o in an editable list', async function (assert) { assert.expect(9); - var done = assert.async(); var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, arch: '<tree editable="bottom"><field name="product_id"/></tree>', }); - testUtils.dom.click(list.$('td.o_data_cell:first')); + await testUtils.dom.click(list.$('td.o_data_cell:first')); assert.containsOnce(list, '.o_selected_row', "should have a row in edit mode"); // we now write 'a' and press enter to check that the selection is // working, and prevent the navigation - testUtils.fields.editInput(list.$('td.o_data_cell input:first'), 'a'); - concurrency.delay(0).then(function () { - var $input = list.$('td.o_data_cell input:first'); - var $dropdown = $input.autocomplete('widget'); - assert.ok($dropdown.is(':visible'), "autocomplete dropdown should be visible"); - - // we now trigger ENTER to select first choice - $input.trigger($.Event('keydown', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - assert.strictEqual($input[0], document.activeElement, - "input should still be focused"); - - // we now trigger again ENTER to make sure we can move to next line - $input.trigger($.Event('keydown', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - - assert.notOk(document.contains($input[0]), - "input should no longer be in dom"); - assert.hasClass(list.$('tr.o_data_row:eq(1)'),'o_selected_row', - "second row should now be selected"); - - // we now write again 'a' in the cell to select xpad. We will now - // test with the tab key - testUtils.fields.editInput(list.$('td.o_data_cell input:first'), 'a'); - return concurrency.delay(0); - }).then(function () { - var $input = list.$('td.o_data_cell input:first'); - var $dropdown = $input.autocomplete('widget'); - assert.ok($dropdown.is(':visible'), "autocomplete dropdown should be visible"); - $input.trigger($.Event('keydown', { - which: $.ui.keyCode.TAB, - keyCode: $.ui.keyCode.TAB, - })); - assert.strictEqual($input[0], document.activeElement, - "input should still be focused"); - - // we now trigger again ENTER to make sure we can move to next line - $input.trigger($.Event('keydown', { - which: $.ui.keyCode.TAB, - keyCode: $.ui.keyCode.TAB, - })); - - assert.notOk(document.contains($input[0]), - "input should no longer be in dom"); - assert.hasClass(list.$('tr.o_data_row:eq(2)'),'o_selected_row', - "third row should now be selected"); - list.destroy(); - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - done(); - }); + await testUtils.fields.editInput(list.$('td.o_data_cell input:first'), 'a'); + var $input = list.$('td.o_data_cell input:first'); + var $dropdown = $input.autocomplete('widget'); + assert.ok($dropdown.is(':visible'), "autocomplete dropdown should be visible"); + + // we now trigger ENTER to select first choice + await testUtils.fields.triggerKeydown($input, 'enter'); + assert.strictEqual($input[0], document.activeElement, + "input should still be focused"); + + // we now trigger again ENTER to make sure we can move to next line + await testUtils.fields.triggerKeydown($input, 'enter'); + + assert.notOk(document.contains($input[0]), + "input should no longer be in dom"); + assert.hasClass(list.$('tr.o_data_row:eq(1)'),'o_selected_row', + "second row should now be selected"); + + // we now write again 'a' in the cell to select xpad. We will now + // test with the tab key + await testUtils.fields.editInput(list.$('td.o_data_cell input:first'), 'a'); + var $input = list.$('td.o_data_cell input:first'); + var $dropdown = $input.autocomplete('widget'); + assert.ok($dropdown.is(':visible'), "autocomplete dropdown should be visible"); + await testUtils.fields.triggerKeydown($input, 'tab'); + assert.strictEqual($input[0], document.activeElement, + "input should still be focused"); + + // we now trigger again ENTER to make sure we can move to next line + await testUtils.fields.triggerKeydown($input, 'tab'); + + assert.notOk(document.contains($input[0]), + "input should no longer be in dom"); + assert.hasClass(list.$('tr.o_data_row:eq(2)'),'o_selected_row', + "third row should now be selected"); + list.destroy(); + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; }); - QUnit.test('pressing ENTER on a \'no_quick_create\' many2one should open a M2ODialog', function (assert) { - var done = assert.async(); + QUnit.test('pressing ENTER on a \'no_quick_create\' many2one should open a M2ODialog', async function (assert) { assert.expect(2); var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2287,47 +2271,33 @@ QUnit.module('fields', {}, function () { }); var $input = form.$('.o_field_many2one input'); - testUtils.fields.editInput($input, "Something that does not exist"); + await testUtils.fields.editInput($input, "Something that does not exist"); $('.ui-autocomplete .ui-menu-item a:contains(Create and)').trigger('mouseenter'); - concurrency.delay(0).then(function () { - $input.trigger($.Event('keydown', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - $input.trigger($.Event('keypress', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - $input.trigger($.Event('keyup', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - concurrency.delay(0).then(function () { - $input.blur(); - assert.strictEqual($('.modal').length, 1, - "should have one modal in body"); - // Check that discarding clears $input - testUtils.dom.click($('.modal .o_form_button_cancel')); - assert.strictEqual($input.val(), '', - "the field should be empty"); - form.destroy(); - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - done(); - }); - }); + await testUtils.nextTick(); + await testUtils.fields.triggerKey('down', $input, 'enter') + await testUtils.fields.triggerKey('press', $input, 'enter') + await testUtils.fields.triggerKey('up', $input, 'enter') + $input.blur(); + assert.strictEqual($('.modal').length, 1, + "should have one modal in body"); + // Check that discarding clears $input + await testUtils.dom.click($('.modal .o_form_button_cancel')); + assert.strictEqual($input.val(), '', + "the field should be empty"); + form.destroy(); + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; }); - QUnit.test('select a value by pressing TAB on a many2one with onchange', function (assert) { - var done = assert.async(); + QUnit.test('select a value by pressing TAB on a many2one with onchange', async function (assert) { assert.expect(3); this.data.partner.onchanges.trululu = function () { }; var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - var def = $.Deferred(); + var prom = testUtils.makeTestPromise(); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2338,7 +2308,7 @@ QUnit.module('fields', {}, function () { mockRPC: function (route, args) { var result = this._super.apply(this, arguments); if (args.method === 'onchange') { - return $.when(def).then(_.constant(result)); + return prom.then(_.constant(result)); } return result; }, @@ -2349,39 +2319,32 @@ QUnit.module('fields', {}, function () { }); var $input = form.$('.o_field_many2one input'); - testUtils.fields.editInput($input, "first"); - concurrency.delay(0).then(function () { - var evOptions = { - which: $.ui.keyCode.TAB, - keyCode: $.ui.keyCode.TAB, - }; - $input.trigger($.Event('keydown', evOptions)); - $input.trigger($.Event('keypress', evOptions)); - $input.trigger($.Event('keyup', evOptions)); - - // simulate a focusout (e.g. because the user clicks outside) - // before the onchange returns - form.$('.o_field_char').focus(); - - assert.strictEqual($('.modal').length, 0, - "there shouldn't be any modal in body"); - - // unlock the onchange - def.resolve(); - - assert.strictEqual($input.val(), 'first record', - "first record should have been selected"); - assert.strictEqual($('.modal').length, 0, - "there shouldn't be any modal in body"); - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - form.destroy(); - done(); - }); + await testUtils.fields.editInput($input, "first"); + await testUtils.fields.triggerKey('down', $input, 'tab'); + await testUtils.fields.triggerKey('press', $input, 'tab'); + await testUtils.fields.triggerKey('up', $input, 'tab'); + + // simulate a focusout (e.g. because the user clicks outside) + // before the onchange returns + form.$('.o_field_char').focus(); + + assert.strictEqual($('.modal').length, 0, + "there shouldn't be any modal in body"); + + // unlock the onchange + prom.resolve(); + await testUtils.nextTick(); + + assert.strictEqual($input.val(), 'first record', + "first record should have been selected"); + assert.strictEqual($('.modal').length, 0, + "there shouldn't be any modal in body"); + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + form.destroy(); }); - QUnit.test('many2one in editable list + onchange, with enter [REQUIRE FOCUS]', function (assert) { + QUnit.test('many2one in editable list + onchange, with enter [REQUIRE FOCUS]', async function (assert) { assert.expect(6); - var done = assert.async(); var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; @@ -2389,9 +2352,9 @@ QUnit.module('fields', {}, function () { obj.int_field = obj.product_id || 0; }; - var def = $.Deferred(); + var prom = testUtils.makeTestPromise(); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -2402,42 +2365,30 @@ QUnit.module('fields', {}, function () { } var result = this._super.apply(this, arguments); if (args.method === 'onchange') { - return def.then(_.constant(result)); + return prom.then(_.constant(result)); } return result; }, }); - testUtils.dom.click(list.$('td.o_data_cell:first')); - testUtils.fields.editInput(list.$('td.o_data_cell input:first'), 'a'); - concurrency.delay(0).then(function () { - var $input = list.$('td.o_data_cell input:first'); - $input.trigger($.Event('keydown', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - $input.trigger($.Event('keyup', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - def.resolve(); - $input.trigger($.Event('keydown', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - assert.strictEqual($('.modal').length, 0, "should not have any modal in DOM"); - assert.verifySteps(['name_search', 'onchange', 'write', 'read']); - list.destroy(); - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - done(); - }); + await testUtils.dom.click(list.$('td.o_data_cell:first')); + await testUtils.fields.editInput(list.$('td.o_data_cell input:first'), 'a'); + var $input = list.$('td.o_data_cell input:first'); + await testUtils.fields.triggerKeydown($input, 'enter'); + await testUtils.fields.triggerKey('up', $input, 'enter'); + prom.resolve(); + await testUtils.nextTick(); + await testUtils.fields.triggerKeydown($input, 'enter'); + assert.strictEqual($('.modal').length, 0, "should not have any modal in DOM"); + assert.verifySteps(['name_search', 'onchange', 'write', 'read']); + list.destroy(); + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; }); - QUnit.test('many2one in editable list + onchange, with enter, part 2 [REQUIRE FOCUS]', function (assert) { + QUnit.test('many2one in editable list + onchange, with enter, part 2 [REQUIRE FOCUS]', async function (assert) { // this is the same test as the previous one, but the onchange is just // resolved slightly later assert.expect(6); - var done = assert.async(); var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; @@ -2445,9 +2396,9 @@ QUnit.module('fields', {}, function () { obj.int_field = obj.product_id || 0; }; - var def = $.Deferred(); + var prom = testUtils.makeTestPromise(); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -2458,38 +2409,27 @@ QUnit.module('fields', {}, function () { } var result = this._super.apply(this, arguments); if (args.method === 'onchange') { - return def.then(_.constant(result)); + return prom.then(_.constant(result)); } return result; }, }); - testUtils.dom.click(list.$('td.o_data_cell:first')); - testUtils.fields.editInput(list.$('td.o_data_cell input:first'), 'a'); - concurrency.delay(0).then(function () { - var $input = list.$('td.o_data_cell input:first'); - $input.trigger($.Event('keydown', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - $input.trigger($.Event('keyup', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - $input.trigger($.Event('keydown', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - def.resolve(); - assert.strictEqual($('.modal').length, 0, "should not have any modal in DOM"); - assert.verifySteps(['name_search', 'onchange', 'write', 'read']); - list.destroy(); - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - done(); - }); + await testUtils.dom.click(list.$('td.o_data_cell:first')); + await testUtils.fields.editInput(list.$('td.o_data_cell input:first'), 'a'); + var $input = list.$('td.o_data_cell input:first'); + await testUtils.fields.triggerKeydown($input, 'enter'); + await testUtils.fields.triggerKey('up', $input, 'enter'); + await testUtils.fields.triggerKeydown($input, 'enter'); + prom.resolve(); + await testUtils.nextTick(); + assert.strictEqual($('.modal').length, 0, "should not have any modal in DOM"); + assert.verifySteps(['name_search', 'onchange', 'write', 'read']); + list.destroy(); + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; }); - QUnit.test('many2one: domain updated by an onchange', function (assert) { + QUnit.test('many2one: domain updated by an onchange', async function (assert) { assert.expect(2); this.data.partner.onchanges = { @@ -2497,7 +2437,7 @@ QUnit.module('fields', {}, function () { }; var domain = []; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2509,7 +2449,7 @@ QUnit.module('fields', {}, function () { mockRPC: function (route, args) { if (args.method === 'onchange') { domain = [['id', 'in', [10]]]; - return $.when({ + return Promise.resolve({ domain: { trululu: domain, unexisting_field: domain, @@ -2528,18 +2468,18 @@ QUnit.module('fields', {}, function () { }); // trigger a name_search (domain should be []) - testUtils.dom.click(form.$('.o_field_widget[name=trululu] input')); + await testUtils.dom.click(form.$('.o_field_widget[name=trululu] input')); // close the dropdown - testUtils.dom.click(form.$('.o_field_widget[name=trululu] input')); + await testUtils.dom.click(form.$('.o_field_widget[name=trululu] input')); // trigger an onchange that will update the domain - testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 2); + await testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 2); // trigger a name_search (domain should be [['id', 'in', [10]]]) - testUtils.dom.click(form.$('.o_field_widget[name=trululu] input')); + await testUtils.dom.click(form.$('.o_field_widget[name=trululu] input')); form.destroy(); }); - QUnit.test('many2one in one2many: domain updated by an onchange', function (assert) { + QUnit.test('many2one in one2many: domain updated by an onchange', async function (assert) { assert.expect(3); this.data.partner.onchanges = { @@ -2547,7 +2487,7 @@ QUnit.module('fields', {}, function () { }; var domain = []; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2561,7 +2501,7 @@ QUnit.module('fields', {}, function () { res_id: 1, mockRPC: function (route, args) { if (args.method === 'onchange') { - return $.when({ + return Promise.resolve({ domain: { trululu: domain, }, @@ -2580,18 +2520,18 @@ QUnit.module('fields', {}, function () { // add a first row with a specific domain for the m2o domain = [['id', 'in', [10]]]; // domain for subrecord 1 - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.dom.click(form.$('.o_field_widget[name=trululu] input')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_widget[name=trululu] input')); // add a second row with another domain for the m2o domain = [['id', 'in', [5]]]; // domain for subrecord 2 - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.dom.click(form.$('.o_field_widget[name=trululu] input')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_widget[name=trululu] input')); // check again the first row to ensure that the domain hasn't change domain = [['id', 'in', [10]]]; // domain for subrecord 1 should have been kept - testUtils.dom.click(form.$('.o_data_row:first .o_data_cell')); - testUtils.dom.click(form.$('.o_field_widget[name=trululu] input')); + await testUtils.dom.click(form.$('.o_data_row:first .o_data_cell')); + await testUtils.dom.click(form.$('.o_field_widget[name=trululu] input')); form.destroy(); }); @@ -2606,7 +2546,7 @@ QUnit.module('fields', {}, function () { this.data.partner.records.push({id: 100 + i, display_name: 'test_' + i}); } - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2652,7 +2592,7 @@ QUnit.module('fields', {}, function () { } var expectedDomain; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2682,7 +2622,7 @@ QUnit.module('fields', {}, function () { // remove the filter on ids expectedDomain = []; - testUtils.dom.click($('.modal .o_cp_searchview .o_facet_remove')); + await testUtils.dom.click($('.modal .o_cp_searchview .o_facet_remove')); assert.verifySteps([ 'default_get', @@ -2697,12 +2637,12 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('updating a many2one from a many2many', function (assert) { + QUnit.test('updating a many2one from a many2many', async function (assert) { assert.expect(4); this.data.turtle.records[1].turtle_trululu = 1; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2723,22 +2663,22 @@ QUnit.module('fields', {}, function () { mockRPC: function (route, args) { if (args.method === 'get_formview_id') { assert.deepEqual(args.args[0], [1], "should call get_formview_id with correct id"); - return $.when(false); + return Promise.resolve(false); } return this._super(route, args); }, }); // Opening the modal - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_data_row td:contains(first record)')); - testUtils.dom.click(form.$('.o_external_button')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_data_row td:contains(first record)')); + await testUtils.dom.click(form.$('.o_external_button')); assert.strictEqual($('.modal').length, 1, "should have one modal in body"); // Changing the 'trululu' value - testUtils.fields.editInput($('.modal input[name="display_name"]'), 'test'); - testUtils.dom.click($('.modal button.btn-primary')); + await testUtils.fields.editInput($('.modal input[name="display_name"]'), 'test'); + await testUtils.dom.click($('.modal button.btn-primary')); // Test whether the value has changed assert.strictEqual($('.modal').length, 0, @@ -2749,13 +2689,13 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('x2many list sorted by many2one', function (assert) { + QUnit.test('x2many list sorted by many2one', async function (assert) { assert.expect(3); this.data.partner.records[0].p = [1, 2, 4]; this.data.partner.fields.trululu.sortable = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2773,12 +2713,12 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('.o_data_row .o_list_number').text(), '124', "should have correct order initially"); - testUtils.dom.click(form.$('.o_list_view thead th:nth(1)')); + await testUtils.dom.click(form.$('.o_list_view thead th:nth(1)')); assert.strictEqual(form.$('.o_data_row .o_list_number').text(), '412', "should have correct order (ASC)"); - testUtils.dom.click(form.$('.o_list_view thead th:nth(1)')); + await testUtils.dom.click(form.$('.o_list_view thead th:nth(1)')); assert.strictEqual(form.$('.o_data_row .o_list_number').text(), '214', "should have correct order (DESC)"); @@ -2786,10 +2726,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many with extra field from server not in form', function (assert) { + QUnit.test('one2many with extra field from server not in form', async function (assert) { assert.expect(6); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2814,20 +2754,20 @@ QUnit.module('fields', {}, function () { } }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); var x2mList = form.$('.o_field_x2many_list[name=p]'); // Add a record in the list - testUtils.dom.click(x2mList.find('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(x2mList.find('.o_field_x2many_list_row_add a')); var modal = $('.modal-lg'); var nameInput = modal.find('input.o_input[name=display_name]'); - testUtils.fields.editInput(nameInput, 'michelangelo'); + await testUtils.fields.editInput(nameInput, 'michelangelo'); // Save the record in the modal (though it is still virtual) - testUtils.dom.click(modal.find('.btn-primary').first()); + await testUtils.dom.click(modal.find('.btn-primary').first()); assert.equal(x2mList.find('.o_data_row').length, 1, 'There should be 1 records in the x2m list'); @@ -2840,7 +2780,7 @@ QUnit.module('fields', {}, function () { 'The display name field should have the right value'); // Save the whole thing - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); x2mList = form.$('.o_field_x2many_list[name=p]'); @@ -2858,10 +2798,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many with extra field from server not in (inline) form', function (assert) { + QUnit.test('one2many with extra field from server not in (inline) form', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2885,15 +2825,15 @@ QUnit.module('fields', {}, function () { var x2mList = form.$('.o_field_x2many_list[name=p]'); // Add a record in the list - testUtils.dom.click(x2mList.find('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(x2mList.find('.o_field_x2many_list_row_add a')); var modal = $('.modal-lg'); var nameInput = modal.find('input.o_input[name=display_name]'); - testUtils.fields.editInput(nameInput, 'michelangelo'); + await testUtils.fields.editInput(nameInput, 'michelangelo'); // Save the record in the modal (though it is still virtual) - testUtils.dom.click(modal.find('.btn-primary').first()); + await testUtils.dom.click(modal.find('.btn-primary').first()); assert.equal(x2mList.find('.o_data_row').length, 1, 'There should be 1 records in the x2m list'); @@ -2901,10 +2841,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many with extra X2many field from server not in inline form', function (assert) { + QUnit.test('one2many with extra X2many field from server not in inline form', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2928,13 +2868,13 @@ QUnit.module('fields', {}, function () { var x2mList = form.$('.o_field_x2many_list[name=p]'); // Add a first record in the list - testUtils.dom.click(x2mList.find('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(x2mList.find('.o_field_x2many_list_row_add a')); // Save & New - testUtils.dom.click($('.modal-lg').find('.btn-primary').eq(1)); + await testUtils.dom.click($('.modal-lg').find('.btn-primary').eq(1)); // Save & Close - testUtils.dom.click($('.modal-lg').find('.btn-primary').eq(0)); + await testUtils.dom.click($('.modal-lg').find('.btn-primary').eq(0)); assert.equal(x2mList.find('.o_data_row').length, 2, 'There should be 2 records in the x2m list'); @@ -2942,11 +2882,11 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many invisible depends on parent field', function (assert) { + QUnit.test('one2many invisible depends on parent field', async function (assert) { assert.expect(4); this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2972,21 +2912,22 @@ QUnit.module('fields', {}, function () { }); assert.containsN(form, 'th', 2, "should be 2 columns in the one2many"); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_many2one[name="product_id"] input')); - testUtils.dom.click($('li.ui-menu-item a:contains(xpad)').trigger('mouseenter')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_many2one[name="product_id"] input')); + await testUtils.dom.click($('li.ui-menu-item a:contains(xpad)').trigger('mouseenter')); assert.containsOnce(form, 'th', "should be 1 column when the product_id is set"); - form.$('.o_field_many2one[name="product_id"] input').val('').trigger('keyup'); + await testUtils.fields.editAndTrigger(form.$('.o_field_many2one[name="product_id"] input'), + '', 'keyup'); assert.containsN(form, 'th', 2, "should be 2 columns in the one2many when product_id is not set"); - testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input')); + await testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input')); assert.containsOnce(form, 'th', "should be 1 column after the value change"); form.destroy(); }); - QUnit.test('one2many column visiblity depends on onchange of parent field', function (assert) { + QUnit.test('one2many column visiblity depends on onchange of parent field', async function (assert) { assert.expect(3); this.data.partner.records[0].p = [2]; @@ -2999,7 +2940,7 @@ QUnit.module('fields', {}, function () { } }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3020,24 +2961,24 @@ QUnit.module('fields', {}, function () { "should be only 1 column ('foo') in the one2many"); assert.containsOnce(form, '.o_list_view .o_data_row', "should contain one row"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // add a new o2m record - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); form.$('.o_field_one2many input:first').focus(); - testUtils.fields.editInput(form.$('.o_field_one2many input:first'), 'New line'); - testUtils.dom.click(form.$el); + await testUtils.fields.editInput(form.$('.o_field_one2many input:first'), 'New line'); + await testUtils.dom.click(form.$el); assert.containsN(form, 'th', 2, "should be 2 columns('foo' + 'int_field')"); form.destroy(); }); - QUnit.test('one2many column_invisible on view not inline', function (assert) { + QUnit.test('one2many column_invisible on view not inline', async function (assert) { assert.expect(4); this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3064,15 +3005,16 @@ QUnit.module('fields', {}, function () { }); assert.containsN(form, 'th', 2, "should be 2 columns in the one2many"); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_many2one[name="product_id"] input')); - testUtils.dom.click($('li.ui-menu-item a:contains(xpad)').trigger('mouseenter')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_many2one[name="product_id"] input')); + await testUtils.dom.click($('li.ui-menu-item a:contains(xpad)').trigger('mouseenter')); assert.containsOnce(form, 'th', "should be 1 column when the product_id is set"); - form.$('.o_field_many2one[name="product_id"] input').val('').trigger('keyup'); + await testUtils.fields.editAndTrigger(form.$('.o_field_many2one[name="product_id"] input'), + '', 'keyup'); assert.containsN(form, 'th', 2, "should be 2 columns in the one2many when product_id is not set"); - testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input')); + await testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input')); assert.containsOnce(form, 'th', "should be 1 column after the value change"); form.destroy(); diff --git a/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js b/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js index 18581b88078..c29bf4f1ec9 100644 --- a/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js +++ b/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js @@ -159,7 +159,7 @@ QUnit.module('fields', {}, function () { }, function () { QUnit.module('FieldOne2Many'); - QUnit.test('New record with a o2m also with 2 new records, ordered, and resequenced', function (assert) { + QUnit.test('New record with a o2m also with 2 new records, ordered, and resequenced', async function (assert) { assert.expect(3); // Needed to have two new records in a single stroke @@ -169,11 +169,11 @@ QUnit.module('fields', {}, function () { [5], [0, 0, { trululu: false }], [0, 0, { trululu: false }], - ] + ]; } }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -190,7 +190,7 @@ QUnit.module('fields', {}, function () { mode: 'create', }, mockRPC: function (route, args) { - assert.step(args.method + ' ' + args.model) + assert.step(args.method + ' ' + args.model); return this._super(route, args); }, }); @@ -198,7 +198,7 @@ QUnit.module('fields', {}, function () { // change the int_field through drag and drop // that way, we'll trigger the sorting and the name_get // of the lines of "p" - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( form.$('.ui-sortable-handle').eq(1), form.$('tbody tr').first(), { position: 'top' } @@ -211,7 +211,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('O2M List with pager, decoration and default_order: add and cancel adding', function (assert) { + QUnit.test('O2M List with pager, decoration and default_order: add and cancel adding', async function (assert) { assert.expect(3); // The decoration on the list implies that its condition will be evaluated @@ -222,7 +222,7 @@ QUnit.module('fields', {}, function () { // and evaluation of the decoration against *visible records* this.data.partner.records[0].p = [2, 4]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -240,30 +240,25 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(form.$('.o_field_x2many_list .o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list .o_field_x2many_list_row_add a')); - var $dataRows = form.$('.o_field_x2many_list .o_data_row'); - assert.equal($dataRows.length, 2, + assert.containsN(form, '.o_field_x2many_list .o_data_row', 2, 'There should be 2 rows'); - var $expectedSelectedRow = $dataRows.eq(1); + var $expectedSelectedRow = form.$('.o_field_x2many_list .o_data_row').eq(1); var $actualSelectedRow = form.$('.o_selected_row'); assert.equal($actualSelectedRow[0], $expectedSelectedRow[0], 'The selected row should be the new one'); // Cancel Creation - var escapeKey = $.ui.keyCode.ESCAPE; - $actualSelectedRow.find('input').trigger( - $.Event('keydown', { which: escapeKey, keyCode: escapeKey })); - - $dataRows = form.$('.o_field_x2many_list .o_data_row'); - assert.equal($dataRows.length, 1, + await testUtils.fields.triggerKeydown($actualSelectedRow.find('input'), 'escape'); + assert.containsOnce(form, '.o_field_x2many_list .o_data_row', 'There should be 1 row'); form.destroy(); }); - QUnit.test('O2M with parented m2o and domain on parent.m2o', function (assert) { + QUnit.test('O2M with parented m2o and domain on parent.m2o', async function (assert) { assert.expect(3); /* records in an o2m can have a m2o pointing to themselves @@ -272,7 +267,7 @@ QUnit.module('fields', {}, function () { */ this.data.turtle.fields.parent_id = { string: "Parent", type: "many2one", relation: 'turtle' }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -297,31 +292,31 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(form.$('.o_field_x2many_list[name=turtles] .o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list[name=turtles] .o_field_x2many_list_row_add a')); - testUtils.fields.many2one.clickOpenDropdown('parent_id'); - testUtils.fields.many2one.clickHighlightedItem('parent_id'); + await testUtils.fields.many2one.clickOpenDropdown('parent_id'); + await testUtils.fields.many2one.clickHighlightedItem('parent_id'); var $modal = $('.modal-content'); - testUtils.dom.click($modal.eq(1).find('.modal-footer .btn-primary').eq(0)); - testUtils.dom.click($modal.eq(0).find('.modal-footer .btn-primary').eq(1)); + await testUtils.dom.click($modal.eq(1).find('.modal-footer .btn-primary').eq(0)); + await testUtils.dom.click($modal.eq(0).find('.modal-footer .btn-primary').eq(1)); - assert.equal(form.$('.o_data_row').length, 1, + assert.containsOnce(form, '.o_data_row', 'The main record should have the new record in its o2m'); $modal = $('.modal-content'); - testUtils.dom.click($modal.find('.o_field_many2one input')); + await testUtils.dom.click($modal.find('.o_field_many2one input')); form.destroy(); }); - QUnit.test('one2many list editable with cell readonly modifier', function (assert) { + QUnit.test('one2many list editable with cell readonly modifier', async function (assert) { assert.expect(4); this.data.partner.records[0].p = [2]; this.data.partner.records[1].turtles = [1, 2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -344,22 +339,22 @@ QUnit.module('fields', {}, function () { } }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); var $targetInput = $('.o_selected_row .o_input[name=foo]'); assert.equal($targetInput[0], document.activeElement, 'The first input of the line should have the focus'); // Simulating hitting the 'f' key twice - testUtils.fields.editInput($targetInput, 'f'); - testUtils.fields.editInput($targetInput, $targetInput.val() + 'f'); + await testUtils.fields.editInput($targetInput, 'f'); + await testUtils.fields.editInput($targetInput, $targetInput.val() + 'f'); assert.equal($targetInput[0], document.activeElement, 'The first input of the line should still have the focus'); // Simulating a TAB key - $targetInput.trigger($.Event('keydown', { which: 9, keyCode: 9 })); + await testUtils.fields.triggerKeydown($targetInput, 'tab'); var $secondTarget = $('.o_selected_row .o_input[name=qux]'); @@ -367,19 +362,19 @@ QUnit.module('fields', {}, function () { 'The second input of the line should have the focus after the TAB press'); - testUtils.fields.editInput($secondTarget, 9); - testUtils.fields.editInput($secondTarget, $secondTarget.val() + 9); + await testUtils.fields.editInput($secondTarget, 9); + await testUtils.fields.editInput($secondTarget, $secondTarget.val() + 9); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('one2many basic properties', function (assert) { + QUnit.test('one2many basic properties', async function (assert) { assert.expect(6); this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -412,7 +407,7 @@ QUnit.module('fields', {}, function () { assert.ok(!form.$('td.o_list_record_remove').length, "embedded one2many records should not have a remove icon"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.ok(form.$('.o_field_x2many_list_row_add').length, "embedded one2many should now be editable"); @@ -425,10 +420,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('transferring class attributes in one2many sub fields', function (assert) { + QUnit.test('transferring class attributes in one2many sub fields', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -445,12 +440,12 @@ QUnit.module('fields', {}, function () { assert.containsOnce(form, 'td.hey', 'should have a td with the desired class'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, 'td.hey', 'should have a td with the desired class'); - testUtils.dom.click(form.$('td.o_data_cell')); + await testUtils.dom.click(form.$('td.o_data_cell')); assert.containsOnce(form, 'input[name="turtle_foo"].hey', 'should have an input with the desired class'); @@ -458,11 +453,11 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many with date and datetime', function (assert) { + QUnit.test('one2many with date and datetime', async function (assert) { assert.expect(2); this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -494,11 +489,11 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('rendering with embedded one2many', function (assert) { + QUnit.test('rendering with embedded one2many', async function (assert) { assert.expect(2); this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -526,11 +521,11 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('use the limit attribute in arch (in field o2m inline tree view)', function (assert) { + QUnit.test('use the limit attribute in arch (in field o2m inline tree view)', async function (assert) { assert.expect(2); this.data.partner.records[0].turtles = [1, 2, 3]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -556,11 +551,11 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('use the limit attribute in arch (in field o2m non inline tree view)', function (assert) { + QUnit.test('use the limit attribute in arch (in field o2m non inline tree view)', async function (assert) { assert.expect(2); this.data.partner.records[0].turtles = [1, 2, 3]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -585,11 +580,11 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many with default_order on view not inline', function (assert) { + QUnit.test('one2many with default_order on view not inline', async function (assert) { assert.expect(1); this.data.partner.records[0].turtles = [1, 2, 3]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -615,11 +610,11 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('embedded one2many with widget', function (assert) { + QUnit.test('embedded one2many with widget', async function (assert) { assert.expect(1); this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -644,7 +639,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('embedded one2many with handle widget', function (assert) { + QUnit.test('embedded one2many with handle widget', async function (assert) { assert.expect(10); var nbConfirmChange = 0; @@ -657,7 +652,7 @@ QUnit.module('fields', {}, function () { this.data.partner.records[0].turtles = [1, 2, 3]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -685,14 +680,14 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "yopblipkawa", "should have the 3 rows in the correct order"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "yopblipkawa", "should still have the 3 rows in the correct order"); assert.strictEqual(nbConfirmChange, 0, "should not have confirmed any change yet"); // Drag and drop the second line in first position - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( form.$('.ui-sortable-handle').eq(1), form.$('tbody tr').first(), { position: 'top' } @@ -705,7 +700,7 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "blipyopkawa", "should have the 3 rows in the new order"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.deepEqual(_.map(this.data.turtle.records, function (turtle) { return _.pick(turtle, 'id', 'turtle_foo', 'turtle_int'); @@ -723,7 +718,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('onchange for embedded one2many in a one2many with a second page', function (assert) { + QUnit.test('onchange for embedded one2many in a one2many with a second page', async function (assert) { assert.expect(1); this.data.turtle.fields.partner_ids.type = 'one2many'; @@ -747,7 +742,7 @@ QUnit.module('fields', {}, function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -778,16 +773,16 @@ QUnit.module('fields', {}, function () { } }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_data_cell').eq(1)); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_data_cell').eq(1)); var $cell = form.$('.o_selected_row .o_input[name=turtle_foo]'); - testUtils.fields.editSelect($cell, "hop"); - testUtils.form.clickSave(form); + await testUtils.fields.editSelect($cell, "hop"); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('onchange for embedded one2many in a one2many updated by server', function (assert) { + QUnit.test('onchange for embedded one2many in a one2many updated by server', async function (assert) { // here we test that after an onchange, the embedded one2many field has // been updated by a new list of ids by the server response, to this new // list should be correctly sent back at save time @@ -809,7 +804,7 @@ QUnit.module('fields', {}, function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -840,11 +835,11 @@ QUnit.module('fields', {}, function () { assert.deepEqual(form.$('.o_many2many_tags_cell').text().trim(), "second record", "the partner_ids should be as specified at initialization"); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_data_cell').eq(1)); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_data_cell').eq(1)); var $cell = form.$('.o_selected_row .o_input[name=turtle_foo]'); - testUtils.fields.editSelect($cell, "hop"); - testUtils.form.clickSave(form); + await testUtils.fields.editSelect($cell, "hop"); + await testUtils.form.clickSave(form); assert.deepEqual(form.$('.o_many2many_tags_cell').text().trim().split(/\s+/), ["second", "record", "aaa"], @@ -853,24 +848,24 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('onchange for embedded one2many with handle widget', function (assert) { + QUnit.test('onchange for embedded one2many with handle widget', async function (assert) { assert.expect(2); this.data.partner.records[0].turtles = [1, 2, 3]; var partnerOnchange = 0; this.data.partner.onchanges = { - turtles: function (obj) { + turtles: function () { partnerOnchange++; }, }; var turtleOnchange = 0; this.data.turtle.onchanges = { - turtle_int: function (obj) { + turtle_int: function () { turtleOnchange++; }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -891,10 +886,10 @@ QUnit.module('fields', {}, function () { res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // Drag and drop the second line in first position - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( form.$('.ui-sortable-handle').eq(1), form.$('tbody tr').first(), { position: 'top' } @@ -906,7 +901,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('onchange for embedded one2many with handle widget using same sequence', function (assert) { + QUnit.test('onchange for embedded one2many with handle widget using same sequence', async function (assert) { assert.expect(4); this.data.turtle.records[0].turtle_int = 1; @@ -920,7 +915,7 @@ QUnit.module('fields', {}, function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -948,14 +943,14 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "yopblipkawa", "should have the 3 rows in the correct order"); // Drag and drop the second line in first position - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( form.$('.ui-sortable-handle').eq(1), form.$('tbody tr').first(), { position: 'top' } @@ -965,11 +960,11 @@ QUnit.module('fields', {}, function () { "should still have the 3 rows in the correct order"); assert.strictEqual(turtleOnchange, 3, "should update all lines"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('onchange (with command 5) for embedded one2many with handle widget', function (assert) { + QUnit.test('onchange (with command 5) for embedded one2many with handle widget', async function (assert) { assert.expect(3); var ids = []; @@ -990,7 +985,7 @@ QUnit.module('fields', {}, function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1009,16 +1004,16 @@ QUnit.module('fields', {}, function () { res_id: 1, }); - testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); + await testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "yopblipkawa", "should have the 3 rows in the correct order"); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_one2many .o_list_view tbody tr:first td:first')); - testUtils.fields.editInput(form.$('.o_field_one2many .o_list_view tbody tr:first input:first'), 'blurp'); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_one2many .o_list_view tbody tr:first td:first')); + await testUtils.fields.editInput(form.$('.o_field_one2many .o_list_view tbody tr:first input:first'), 'blurp'); // Drag and drop the third line in second position - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( form.$('.ui-sortable-handle').eq(2), form.$('.o_field_one2many tbody tr').eq(1), { position: 'top' } @@ -1026,8 +1021,8 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('.o_data_cell').text(), "blurpkawablip", "should display to record in 'turtle_int' order"); - testUtils.form.clickSave(form); - testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); + await testUtils.form.clickSave(form); + await testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); assert.strictEqual(form.$('.o_data_cell:not(.o_handle_cell)').text(), "blurpkawablip", "should display to record in 'turtle_int' order"); @@ -1035,7 +1030,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('onchange with modifiers for embedded one2many on the second page', function (assert) { + QUnit.test('onchange with modifiers for embedded one2many on the second page', async function (assert) { assert.expect(7); var data = this.data; @@ -1074,7 +1069,7 @@ QUnit.module('fields', {}, function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: data, @@ -1094,29 +1089,29 @@ QUnit.module('fields', {}, function () { res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.equal(form.$('.o_field_one2many td[class="o_data_cell"]').text(), "#20#21#22#23#24#25#26#27#28#29", "should display the records in order"); - testUtils.dom.click(form.$('.o_field_one2many .o_list_view tbody tr:first td:first')); - testUtils.fields.editInput(form.$('.o_field_one2many .o_list_view tbody tr:first input:first'), 'blurp'); + await testUtils.dom.click(form.$('.o_field_one2many .o_list_view tbody tr:first td:first')); + await testUtils.fields.editInput(form.$('.o_field_one2many .o_list_view tbody tr:first input:first'), 'blurp'); // the domain fail if the widget does not use the allready loaded data. - testUtils.form.clickDiscard(form); + await testUtils.form.clickDiscard(form); assert.equal(form.$('.o_field_one2many td[class="o_data_cell"]').text(), "blurp#21#22#23#24#25#26#27#28#29", "should display the records in order with the changes"); - testUtils.dom.click($('.modal .modal-footer button:first')); + await testUtils.dom.click($('.modal .modal-footer button:first')); assert.equal(form.$('.o_field_one2many td[class="o_data_cell"]').text(), "#20#21#22#23#24#25#26#27#28#29", "should cancel changes and display the records in order"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // Drag and drop the third line in second position - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( form.$('.ui-sortable-handle').eq(2), form.$('.o_field_one2many tbody tr').eq(1), { position: 'top' } @@ -1126,7 +1121,7 @@ QUnit.module('fields', {}, function () { "should display the records in order after resequence (display record with turtle_int=0)"); // Drag and drop the third line in second position - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( form.$('.ui-sortable-handle').eq(2), form.$('.o_field_one2many tbody tr').eq(1), { position: 'top' } @@ -1135,12 +1130,12 @@ QUnit.module('fields', {}, function () { assert.equal(form.$('.o_field_one2many td[class="o_data_cell"]').text(), "#20#39#40#41#42#43#44#45#46#47", "should display the records in order after resequence (display record with turtle_int=0)"); - testUtils.form.clickDiscard(form); + await testUtils.form.clickDiscard(form); assert.equal(form.$('.o_field_one2many td[class="o_data_cell"]').text(), "#20#39#40#41#42#43#44#45#46#47", "should display the records in order after resequence"); - testUtils.dom.click($('.modal .modal-footer button:first')); + await testUtils.dom.click($('.modal .modal-footer button:first')); assert.equal(form.$('.o_field_one2many td[class="o_data_cell"]').text(), "#20#21#22#23#24#25#26#27#28#29", "should cancel changes and display the records in order"); @@ -1148,7 +1143,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('onchange followed by edition on the second page', function (assert) { + QUnit.test('onchange followed by edition on the second page', async function (assert) { assert.expect(12); var ids = []; @@ -1169,7 +1164,7 @@ QUnit.module('fields', {}, function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1188,30 +1183,30 @@ QUnit.module('fields', {}, function () { res_id: 1, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); - testUtils.dom.click(form.$('.o_field_one2many .o_list_view tbody tr:eq(1) td:first')); - testUtils.fields.editInput(form.$('.o_field_one2many .o_list_view tbody tr:eq(1) input:first'), 'value 1'); - testUtils.dom.click(form.$('.o_field_one2many .o_list_view tbody tr:eq(2) td:first')); - testUtils.fields.editInput(form.$('.o_field_one2many .o_list_view tbody tr:eq(2) input:first'), 'value 2'); + await testUtils.dom.click(form.$('.o_field_one2many .o_list_view tbody tr:eq(1) td:first')); + await testUtils.fields.editInput(form.$('.o_field_one2many .o_list_view tbody tr:eq(1) input:first'), 'value 1'); + await testUtils.dom.click(form.$('.o_field_one2many .o_list_view tbody tr:eq(2) td:first')); + await testUtils.fields.editInput(form.$('.o_field_one2many .o_list_view tbody tr:eq(2) input:first'), 'value 2'); assert.containsN(form, '.o_data_row', 40, "should display 40 records"); assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#39))').index(), 0, "should display '#39' at the first line"); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.containsN(form, '.o_data_row', 40, "should display 39 records and the create line"); assert.containsOnce(form, '.o_data_row:first .o_field_char', "should display the create line in first position"); assert.strictEqual(form.$('.o_data_row:first .o_field_char').val(), "", "should an empty input"); assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#39))').index(), 1, "should display '#39' at the second line"); - testUtils.fields.editInput(form.$('.o_data_row input:first'), 'value 3'); + await testUtils.fields.editInput(form.$('.o_data_row input:first'), 'value 3'); assert.containsOnce(form, '.o_data_row:first .o_field_char', "should display the create line in first position after onchange"); assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#39))').index(), 1, "should display '#39' at the second line after onchange"); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.containsN(form, '.o_data_row', 40, "should display 39 records and the create line"); assert.containsOnce(form, '.o_data_row:first .o_field_char', "should display the create line in first position"); @@ -1221,7 +1216,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('onchange followed by edition on the second page (part 2)', function (assert) { + QUnit.test('onchange followed by edition on the second page (part 2)', async function (assert) { assert.expect(8); var ids = []; @@ -1244,7 +1239,7 @@ QUnit.module('fields', {}, function () { // bottom order - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1263,25 +1258,25 @@ QUnit.module('fields', {}, function () { res_id: 1, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); - testUtils.dom.click(form.$('.o_field_one2many .o_list_view tbody tr:eq(1) td:first')); - testUtils.fields.editInput(form.$('.o_field_one2many .o_list_view tbody tr:eq(1) input:first'), 'value 1'); - testUtils.dom.click(form.$('.o_field_one2many .o_list_view tbody tr:eq(2) td:first')); - testUtils.fields.editInput(form.$('.o_field_one2many .o_list_view tbody tr:eq(2) input:first'), 'value 2'); + await testUtils.dom.click(form.$('.o_field_one2many .o_list_view tbody tr:eq(1) td:first')); + await testUtils.fields.editInput(form.$('.o_field_one2many .o_list_view tbody tr:eq(1) input:first'), 'value 1'); + await testUtils.dom.click(form.$('.o_field_one2many .o_list_view tbody tr:eq(2) td:first')); + await testUtils.fields.editInput(form.$('.o_field_one2many .o_list_view tbody tr:eq(2) input:first'), 'value 2'); assert.containsN(form, '.o_data_row', 40, "should display 40 records"); assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#77))').index(), 39, "should display '#77' at the last line"); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.containsN(form, '.o_data_row', 41, "should display 41 records and the create line"); assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#76))').index(), 38, "should display '#76' at the penultimate line"); assert.strictEqual(form.$('.o_data_row:has(.o_field_char)').index(), 40, "should display the create line at the last position"); - testUtils.fields.editInput(form.$('.o_data_row input:first'), 'value 3'); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('.o_data_row input:first'), 'value 3'); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.containsN(form, '.o_data_row', 42, "should display 42 records and the create line"); assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#76))').index(), 38, "should display '#76' at the penultimate line"); @@ -1290,7 +1285,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('onchange returning a command 6 for an x2many', function (assert) { + QUnit.test('onchange returning a command 6 for an x2many', async function (assert) { assert.expect(2); this.data.partner.onchanges = { @@ -1299,7 +1294,7 @@ QUnit.module('fields', {}, function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1321,7 +1316,7 @@ QUnit.module('fields', {}, function () { "there should be one record in the relation"); // change the value of foo to trigger the onchange - testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), 'some value'); + await testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), 'some value'); assert.containsN(form, '.o_data_row', 3, "there should be three records in the relation"); @@ -1329,7 +1324,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('x2many fields inside x2manys are fetched after an onchange', function (assert) { + QUnit.test('x2many fields inside x2manys are fetched after an onchange', async function (assert) { assert.expect(6); this.data.turtle.records[0].partner_ids = [1]; @@ -1340,7 +1335,7 @@ QUnit.module('fields', {}, function () { }; var checkRPC = false; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1379,7 +1374,7 @@ QUnit.module('fields', {}, function () { // change the value of foo to trigger the onchange checkRPC = true; // enable flag to check read RPC for the m2m field - testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), 'some value'); + await testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), 'some value'); assert.containsN(form, '.o_data_row', 3, "there should be three records in the relation"); @@ -1389,7 +1384,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('reference fields inside x2manys are fetched after an onchange', function (assert) { + QUnit.test('reference fields inside x2manys are fetched after an onchange', async function (assert) { assert.expect(5); this.data.turtle.records[1].turtle_ref = 'product,41'; @@ -1400,7 +1395,7 @@ QUnit.module('fields', {}, function () { }; var checkRPC = false; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1437,7 +1432,7 @@ QUnit.module('fields', {}, function () { // change the value of foo to trigger the onchange checkRPC = true; // enable flag to check read RPC for reference field - testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), 'some value'); + await testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), 'some value'); assert.containsN(form, '.o_data_row', 3, "there should be three records in the relation"); @@ -1447,7 +1442,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('onchange on one2many containing x2many in form view', function (assert) { + QUnit.test('onchange on one2many containing x2many in form view', async function (assert) { assert.expect(16); this.data.partner.onchanges = { @@ -1456,7 +1451,7 @@ QUnit.module('fields', {}, function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1486,20 +1481,20 @@ QUnit.module('fields', {}, function () { // open the created o2m record in a form view, and add a m2m subrecord // in its relation - testUtils.dom.click(form.$('.o_data_row')); + await testUtils.dom.click(form.$('.o_data_row')); assert.strictEqual($('.modal').length, 1, "should have opened a dialog"); assert.strictEqual($('.modal .o_data_row').length, 0, "there should be no record in the one2many in the dialog"); // add a many2many subrecord - testUtils.dom.click($('.modal .o_field_x2many_list_row_add a')); + await testUtils.dom.click($('.modal .o_field_x2many_list_row_add a')); assert.strictEqual($('.modal').length, 2, "should have opened a second dialog"); // select a many2many subrecord - testUtils.dom.click($('.modal:nth(1) .o_list_view .o_data_cell:first')); + await testUtils.dom.click($('.modal:nth(1) .o_list_view .o_data_cell:first')); assert.strictEqual($('.modal').length, 1, "second dialog should be closed"); @@ -1509,25 +1504,25 @@ QUnit.module('fields', {}, function () { 'm2m pager should be hidden'); // click on 'Save & Close' - testUtils.dom.click($('.modal-footer .btn-primary:first')); + await testUtils.dom.click($('.modal-footer .btn-primary:first')); assert.strictEqual($('.modal').length, 0, "dialog should be closed"); // reopen o2m record, and another m2m subrecord in its relation, but // discard the changes - testUtils.dom.click(form.$('.o_data_row')); + await testUtils.dom.click(form.$('.o_data_row')); assert.strictEqual($('.modal').length, 1, "should have opened a dialog"); assert.strictEqual($('.modal .o_data_row').length, 1, "there should be one record in the one2many in the dialog"); // add another m2m subrecord - testUtils.dom.click($('.modal .o_field_x2many_list_row_add a')); + await testUtils.dom.click($('.modal .o_field_x2many_list_row_add a')); assert.strictEqual($('.modal').length, 2, "should have opened a second dialog"); - testUtils.dom.click($('.modal:nth(1) .o_list_view .o_data_cell:first')); + await testUtils.dom.click($('.modal:nth(1) .o_list_view .o_data_cell:first')); assert.strictEqual($('.modal').length, 1, "second dialog should be closed"); @@ -1535,12 +1530,12 @@ QUnit.module('fields', {}, function () { "there should be two records in the one2many in the dialog"); // click on 'Discard' - testUtils.dom.clickFirst($('.modal-footer .btn-secondary')); + await testUtils.dom.clickFirst($('.modal-footer .btn-secondary')); assert.strictEqual($('.modal').length, 0, "dialog should be closed"); // reopen o2m record to check that second changes have properly been discarded - testUtils.dom.click(form.$('.o_data_row')); + await testUtils.dom.click(form.$('.o_data_row')); assert.strictEqual($('.modal').length, 1, "should have opened a dialog"); assert.strictEqual($('.modal .o_data_row').length, 1, @@ -1549,7 +1544,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('embedded one2many with handle widget with minimum setValue calls', function (assert) { + QUnit.test('embedded one2many with handle widget with minimum setValue calls', async function (assert) { var done = assert.async(); assert.expect(20); @@ -1574,7 +1569,7 @@ QUnit.module('fields', {}, function () { }); this.data.partner.records[0].turtles = [1, 2, 3, 4, 5, 6, 7]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1596,28 +1591,26 @@ QUnit.module('fields', {}, function () { }); testUtils.mock.intercept(form, "field_changed", function (event) { - assert.step(form.model.get(event.data.changes.turtles.id).res_id); + assert.step(String(form.model.get(event.data.changes.turtles.id).res_id)); }, true); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); - var steps = []; var positions = [ - [6, 0, 'top', [3, 6, 1, 2, 5, 7, 4]], // move the last to the first line - [5, 1, 'top', [7, 6, 1, 2, 5]], // move the penultimate to the second line - [2, 5, 'center', [1, 2, 5, 6]], // move the third to the penultimate line + [6, 0, 'top', ['3', '6', '1', '2', '5', '7', '4']], // move the last to the first line + [5, 1, 'top', ['7', '6', '1', '2', '5']], // move the penultimate to the second line + [2, 5, 'center', ['1', '2', '5', '6']], // move the third to the penultimate line ]; - function dragAndDrop() { + async function dragAndDrop() { var pos = positions.shift(); - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( form.$('.ui-sortable-handle').eq(pos[0]), form.$('tbody tr').eq(pos[1]), { position: pos[2] } ); - steps = steps.concat(pos[3]); - assert.verifySteps(steps, + assert.verifySteps(pos[3], "sequences values should be apply from the begin index to the drop index"); if (positions.length) { @@ -1644,11 +1637,11 @@ QUnit.module('fields', {}, function () { dragAndDrop(); }); - QUnit.test('embedded one2many (editable list) with handle widget', function (assert) { + QUnit.test('embedded one2many (editable list) with handle widget', async function (assert) { assert.expect(8); this.data.partner.records[0].p = [1, 2, 4]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1676,12 +1669,12 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "My little Foo Valueblipyop", "should have the 3 rows in the correct order"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "My little Foo Valueblipyop", "should still have the 3 rows in the correct order"); // Drag and drop the second line in first position - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( form.$('.ui-sortable-handle').eq(1), form.$('tbody tr').first(), { position: 'top' } @@ -1693,19 +1686,19 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "blipMy little Foo Valueyop", "should have the 3 rows in the new order"); - testUtils.dom.click(form.$('tbody tr:first td:first')); + await testUtils.dom.click(form.$('tbody tr:first td:first')); assert.strictEqual(form.$('tbody tr:first td.o_data_cell:not(.o_handle_cell) input').val(), "blip", "should edit the correct row"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "blipMy little Foo Valueyop", "should still have the 3 rows in the new order"); form.destroy(); }); - QUnit.test('one2many field when using the pager', function (assert) { + QUnit.test('one2many field when using the pager', async function (assert) { assert.expect(13); var ids = []; @@ -1721,7 +1714,7 @@ QUnit.module('fields', {}, function () { this.data.partner.records[1].p = ids.slice(42); var count = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1759,6 +1752,7 @@ QUnit.module('fields', {}, function () { // related records of record 1 anymore). Two additional RPCs should have // been done form.pager.next(); + await testUtils.nextTick(); assert.strictEqual(count, 4, 'two RPCs should have been done'); assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 3, 'one2many kanban should contain 3 cards for record 2'); @@ -1766,19 +1760,21 @@ QUnit.module('fields', {}, function () { // move back to record 1, which should contain again its first 40 related // records form.pager.previous(); + await testUtils.nextTick(); assert.strictEqual(count, 6, 'two RPCs should have been done'); assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 40, 'one2many kanban should contain 40 cards for record 1'); // move to the second page of the o2m: 1 RPC should have been done to fetch // the 2 subrecords of page 2, and those records should now be displayed - testUtils.dom.click(form.$('.o_x2m_control_panel .o_pager_next')); + await testUtils.dom.click(form.$('.o_x2m_control_panel .o_pager_next')); assert.strictEqual(count, 7, 'one RPC should have been done'); assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 2, 'one2many kanban should contain 2 cards for record 1 at page 2'); // move to record 2 again and check that everything is correctly updated form.pager.next(); + await testUtils.nextTick(); assert.strictEqual(count, 9, 'two RPCs should have been done'); assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 3, 'one2many kanban should contain 3 cards for record 2'); @@ -1786,15 +1782,16 @@ QUnit.module('fields', {}, function () { // move back to record 1 and move to page 2 again: all data should have // been correctly reloaded form.pager.previous(); + await testUtils.nextTick(); assert.strictEqual(count, 11, 'two RPCs should have been done'); - testUtils.dom.click(form.$('.o_x2m_control_panel .o_pager_next')); + await testUtils.dom.click(form.$('.o_x2m_control_panel .o_pager_next')); assert.strictEqual(count, 12, 'one RPC should have been done'); assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 2, 'one2many kanban should contain 2 cards for record 1 at page 2'); form.destroy(); }); - QUnit.test('edition of one2many field with pager', function (assert) { + QUnit.test('edition of one2many field with pager', async function (assert) { assert.expect(31); var ids = []; @@ -1811,7 +1808,7 @@ QUnit.module('fields', {}, function () { var saveCount = 0; var checkRead = false; var readIDs; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1894,10 +1891,10 @@ QUnit.module('fields', {}, function () { // add a record on page one checkRead = true; - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o-kanban-button-new')); - testUtils.fields.editInput($('.modal input'), 'new record'); - testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o-kanban-button-new')); + await testUtils.fields.editInput($('.modal input'), 'new record'); + await testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); // checks assert.strictEqual(readIDs, undefined, "should not have read any record"); assert.strictEqual(form.$('span:contains(new record)').length, 0, @@ -1909,14 +1906,14 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('.o_kanban_record:first span:contains(new record)').length, 0, 'new record should not be on page 1'); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); // delete a record on page one checkRead = true; - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_kanban_record:first span:contains(relational record 10)').length, 1, 'first record should be the one with id 10 (next checks rely on that)'); - testUtils.dom.click(form.$('.delete_icon:first')); + await testUtils.dom.click(form.$('.delete_icon:first')); // checks assert.deepEqual(readIDs, [50], "should have read a record (to display 40 records on page 1)"); @@ -1925,31 +1922,31 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('.o_x2m_control_panel .o_pager_counter').text().trim(), '1-40 / 45', "pager range should be correct"); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); // add and delete records in both pages - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); checkRead = true; readIDs = undefined; // add and delete a record in page 1 - testUtils.dom.click(form.$('.o-kanban-button-new')); - testUtils.fields.editInput($('.modal input'), 'new record page 1'); - testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); + await testUtils.dom.click(form.$('.o-kanban-button-new')); + await testUtils.fields.editInput($('.modal input'), 'new record page 1'); + await testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); assert.strictEqual(form.$('.o_kanban_record:first span:contains(relational record 11)').length, 1, 'first record should be the one with id 11 (next checks rely on that)'); - testUtils.dom.click(form.$('.delete_icon:first')); + await testUtils.dom.click(form.$('.delete_icon:first')); assert.deepEqual(readIDs, [51], "should have read a record (to display 40 records on page 1)"); // add and delete a record in page 2 - testUtils.dom.click(form.$('.o_x2m_control_panel .o_pager_next')); + await testUtils.dom.click(form.$('.o_x2m_control_panel .o_pager_next')); assert.strictEqual(form.$('.o_kanban_record:first span:contains(relational record 52)').length, 1, 'first record should be the one with id 52 (next checks rely on that)'); checkRead = true; readIDs = undefined; - testUtils.dom.click(form.$('.delete_icon:first')); - testUtils.dom.click(form.$('.o-kanban-button-new')); - testUtils.fields.editInput($('.modal input'), 'new record page 2'); - testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); + await testUtils.dom.click(form.$('.delete_icon:first')); + await testUtils.dom.click(form.$('.o-kanban-button-new')); + await testUtils.fields.editInput($('.modal input'), 'new record page 2'); + await testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); assert.strictEqual(readIDs, undefined, "should not have read any record"); // checks assert.strictEqual(form.$('.o_kanban_record:not(".o_kanban_ghost")').length, 5, @@ -1961,12 +1958,12 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('.o_kanban_record span:contains(new record page 2)').length, 1, 'new records should be on page 2'); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('edition of one2many field, with onchange and not inline sub view', function (assert) { + QUnit.test('edition of one2many field, with onchange and not inline sub view', async function (assert) { assert.expect(2); this.data.turtle.onchanges.turtle_int = function (obj) { @@ -1974,7 +1971,7 @@ QUnit.module('fields', {}, function () { }; this.data.partner.onchanges.turtles = function () { }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1985,27 +1982,27 @@ QUnit.module('fields', {}, function () { 'turtle,false,list': '<tree><field name="turtle_foo"/></tree>', 'turtle,false,form': '<form><group><field name="turtle_foo"/><field name="turtle_int"/></group></form>', }, - mockRPC: function (route, args) { + mockRPC: function () { return this._super.apply(this, arguments); }, res_id: 1, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput($('input[name="turtle_int"]'), '5'); - testUtils.dom.click($('.modal-footer button.btn-primary').first()); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput($('input[name="turtle_int"]'), '5'); + await testUtils.dom.click($('.modal-footer button.btn-primary').first()); assert.strictEqual(form.$('tbody tr:eq(1) td.o_data_cell').text(), '5', 'should display 5 in the foo field'); - testUtils.dom.click(form.$('tbody tr:eq(1) td.o_data_cell')); + await testUtils.dom.click(form.$('tbody tr:eq(1) td.o_data_cell')); - testUtils.fields.editInput($('input[name="turtle_int"]'), '3'); - testUtils.dom.click($('.modal-footer button.btn-primary').first()); + await testUtils.fields.editInput($('input[name="turtle_int"]'), '3'); + await testUtils.dom.click($('.modal-footer button.btn-primary').first()); assert.strictEqual(form.$('tbody tr:eq(1) td.o_data_cell').text(), '3', 'should now display 3 in the foo field'); form.destroy(); }); - QUnit.test('sorting one2many fields', function (assert) { + QUnit.test('sorting one2many fields', async function (assert) { assert.expect(4); this.data.partner.fields.foo.sortable = true; @@ -2015,7 +2012,7 @@ QUnit.module('fields', {}, function () { this.data.partner.records[0].p = [23, 24, 25]; var rpcCount = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2040,20 +2037,20 @@ QUnit.module('fields', {}, function () { throw "should not render the whole form"; }; - testUtils.dom.click(form.$('table thead th:contains(Foo)')); + await testUtils.dom.click(form.$('table thead th:contains(Foo)')); assert.strictEqual(rpcCount, 0, 'sort should be in memory, no extra RPCs should have been done'); assert.ok(form.$('table tbody tr:eq(2) td:contains(xyz)').length, "the 3rd record is the one with 'xyz' value"); - testUtils.dom.click(form.$('table thead th:contains(Foo)')); + await testUtils.dom.click(form.$('table thead th:contains(Foo)')); assert.ok(form.$('table tbody tr:eq(2) td:contains(abc)').length, "the 3rd record is the one with 'abc' value"); form.destroy(); }); - QUnit.test('one2many list field edition', function (assert) { + QUnit.test('one2many list field edition', async function (assert) { assert.expect(6); this.data.partner.records.push({ @@ -2062,7 +2059,7 @@ QUnit.module('fields', {}, function () { }); this.data.partner.records[1].p = [3]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2079,33 +2076,33 @@ QUnit.module('fields', {}, function () { // edit the first line of the o2m assert.strictEqual(form.$('.o_field_one2many tbody td').first().text(), 'relational record 1', "display name of first record in o2m list should be 'relational record 1'"); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_one2many tbody td').first()); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_one2many tbody td').first()); assert.hasClass(form.$('.o_field_one2many tbody td').first().parent(),'o_selected_row', "first row of o2m should be in edition"); - testUtils.fields.editInput(form.$('.o_field_one2many tbody td').first().find('input'), "new value"); + await testUtils.fields.editInput(form.$('.o_field_one2many tbody td').first().find('input'), "new value"); assert.hasClass(form.$('.o_field_one2many tbody td').first().parent(),'o_selected_row', "first row of o2m should still be in edition"); // // leave o2m edition - testUtils.dom.click(form.$el); + await testUtils.dom.click(form.$el); assert.doesNotHaveClass(form.$('.o_field_one2many tbody td').first().parent(), 'o_selected_row', "first row of o2m should be readonly again"); // discard changes - testUtils.form.clickDiscard(form); + await testUtils.form.clickDiscard(form); assert.strictEqual(form.$('.o_field_one2many tbody td').first().text(), 'new value', "changes shouldn't have been discarded yet, waiting for user confirmation"); - testUtils.dom.click($('.modal .modal-footer .btn-primary')); + await testUtils.dom.click($('.modal .modal-footer .btn-primary')); assert.strictEqual(form.$('.o_field_one2many tbody td').first().text(), 'relational record 1', "display name of first record in o2m list should be 'relational record 1'"); // edit again and save - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_one2many tbody td').first()); - testUtils.fields.editInput(form.$('.o_field_one2many tbody td').first().find('input'), "new value"); - testUtils.dom.click(form.$el); - testUtils.form.clickSave(form); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_one2many tbody td').first()); + await testUtils.fields.editInput(form.$('.o_field_one2many tbody td').first().find('input'), "new value"); + await testUtils.dom.click(form.$el); + await testUtils.form.clickSave(form); // FIXME: this next test doesn't pass as the save of updates of // relational data is temporarily disabled // assert.strictEqual(form.$('.o_field_one2many tbody td').first().text(), 'new value', @@ -2114,9 +2111,9 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many list: create action disabled', function (assert) { + QUnit.test('one2many list: create action disabled', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2133,17 +2130,17 @@ QUnit.module('fields', {}, function () { assert.ok(!form.$('.o_field_x2many_list_row_add').length, '"Add an item" link should not be available in readonly'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.ok(!form.$('.o_field_x2many_list_row_add').length, '"Add an item" link should not be available in readonly'); form.destroy(); }); - QUnit.test('one2many list: unlink two records', function (assert) { + QUnit.test('one2many list: unlink two records', async function (assert) { assert.expect(9); this.data.partner.records[0].p = [1, 2, 4]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2174,7 +2171,7 @@ QUnit.module('fields', {}, function () { '<form string="Partner"><field name="display_name"/></form>', }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsN(form, 'td.o_list_record_remove button', 3, "should have 3 remove buttons"); @@ -2182,28 +2179,28 @@ QUnit.module('fields', {}, function () { assert.hasClass(form.$('td.o_list_record_remove button').first(),'fa fa-times', "should have X icons to remove (unlink) records"); - testUtils.dom.click(form.$('td.o_list_record_remove button').first()); + await testUtils.dom.click(form.$('td.o_list_record_remove button').first()); assert.containsN(form, 'td.o_list_record_remove button', 2, "should have 2 remove buttons (a record is supposed to have been unlinked)"); - testUtils.dom.click(form.$('tr.o_data_row').first()); + await testUtils.dom.click(form.$('tr.o_data_row').first()); assert.strictEqual($('.modal .modal-footer .o_btn_remove').length, 1, 'there should be a modal having Remove Button'); - testUtils.dom.click($('.modal .modal-footer .o_btn_remove')); + await testUtils.dom.click($('.modal .modal-footer .o_btn_remove')); assert.containsOnce(form, 'td.o_list_record_remove button', "should have 1 delete button (another record is supposed to have been unlinked)"); // save and check that the correct command has been generated - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('one2many list: deleting two records', function (assert) { + QUnit.test('one2many list: deleting two records', async function (assert) { assert.expect(9); this.data.partner.records[0].p = [1, 2, 4]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2234,7 +2231,7 @@ QUnit.module('fields', {}, function () { '<form string="Partner"><field name="display_name"/></form>', }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsN(form, 'td.o_list_record_remove button', 3, "should have 3 remove buttons"); @@ -2242,21 +2239,21 @@ QUnit.module('fields', {}, function () { assert.hasClass(form.$('td.o_list_record_remove button').first(),'fa fa-trash-o', "should have trash bin icons to remove (delete) records"); - testUtils.dom.click(form.$('td.o_list_record_remove button').first()); + await testUtils.dom.click(form.$('td.o_list_record_remove button').first()); assert.containsN(form, 'td.o_list_record_remove button', 2, "should have 2 remove buttons (a record is supposed to have been deleted)"); - testUtils.dom.click(form.$('tr.o_data_row').first()); + await testUtils.dom.click(form.$('tr.o_data_row').first()); assert.strictEqual($('.modal .modal-footer .o_btn_remove').length, 1, 'there should be a modal having Remove Button'); - testUtils.dom.click($('.modal .modal-footer .o_btn_remove')); + await testUtils.dom.click($('.modal .modal-footer .o_btn_remove')); assert.containsOnce(form, 'td.o_list_record_remove button', "should have 1 remove button (another record is supposed to have been deleted)"); // save and check that the correct command has been generated - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); // FIXME: it would be nice to test that the view is re-rendered correctly, // but as the relational data isn't re-fetched, the rendering is ok even @@ -2264,11 +2261,11 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many kanban: edition', function (assert) { + QUnit.test('one2many kanban: edition', async function (assert) { assert.expect(17); this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2303,7 +2300,7 @@ QUnit.module('fields', {}, function () { assert.ok(!form.$('.o_field_one2many .o-kanban-button-new').length, '"Create" button should not be visible in readonly'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 1, 'should contain 1 record'); @@ -2321,40 +2318,40 @@ QUnit.module('fields', {}, function () { 'Create button should have "Add" label'); // edit existing subrecord - testUtils.dom.click(form.$('.oe_kanban_global_click')); + await testUtils.dom.click(form.$('.oe_kanban_global_click')); - testUtils.fields.editInput($('.modal .o_form_view input').first(), 'new name'); - testUtils.dom.click($('.modal .modal-footer .btn-primary')); + await testUtils.fields.editInput($('.modal .o_form_view input').first(), 'new name'); + await testUtils.dom.click($('.modal .modal-footer .btn-primary')); assert.strictEqual(form.$('.o_kanban_record span:first').text(), 'new name', 'value of subrecord should have been updated'); // create a new subrecord - testUtils.dom.click(form.$('.o-kanban-button-new')); - testUtils.fields.editInput($('.modal .o_form_view input').first(), 'new subrecord 1'); - testUtils.dom.clickFirst($('.modal .modal-footer .btn-primary')); + await testUtils.dom.click(form.$('.o-kanban-button-new')); + await testUtils.fields.editInput($('.modal .o_form_view input').first(), 'new subrecord 1'); + await testUtils.dom.clickFirst($('.modal .modal-footer .btn-primary')); assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 2, 'should contain 2 records'); assert.strictEqual(form.$('.o_kanban_record:nth(1) span').text(), 'new subrecord 1', 'value of newly created subrecord should be "new subrecord 1"'); // create two new subrecords - testUtils.dom.click(form.$('.o-kanban-button-new')); - testUtils.fields.editInput($('.modal .o_form_view input').first(), 'new subrecord 2'); - testUtils.dom.click($('.modal .modal-footer .btn-primary:nth(1)')); - testUtils.fields.editInput($('.modal .o_form_view input').first(), 'new subrecord 3'); - testUtils.dom.clickFirst($('.modal .modal-footer .btn-primary')); + await testUtils.dom.click(form.$('.o-kanban-button-new')); + await testUtils.fields.editInput($('.modal .o_form_view input').first(), 'new subrecord 2'); + await testUtils.dom.click($('.modal .modal-footer .btn-primary:nth(1)')); + await testUtils.fields.editInput($('.modal .o_form_view input').first(), 'new subrecord 3'); + await testUtils.dom.clickFirst($('.modal .modal-footer .btn-primary')); assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 4, 'should contain 4 records'); // delete subrecords - testUtils.dom.click(form.$('.oe_kanban_global_click').first()); + await testUtils.dom.click(form.$('.oe_kanban_global_click').first()); assert.strictEqual($('.modal .modal-footer .o_btn_remove').length, 1, 'There should be a modal having Remove Button'); - testUtils.dom.click($('.modal .modal-footer .o_btn_remove')); + await testUtils.dom.click($('.modal .modal-footer .o_btn_remove')); assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 3, 'should contain 3 records'); - testUtils.dom.click(form.$('.o_kanban_view .delete_icon:first()')); - testUtils.dom.click(form.$('.o_kanban_view .delete_icon:first()')); + await testUtils.dom.click(form.$('.o_kanban_view .delete_icon:first()')); + await testUtils.dom.click(form.$('.o_kanban_view .delete_icon:first()')); assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 1, 'should contain 1 records'); assert.strictEqual(form.$('.o_kanban_record span:first').text(), 'new subrecord 3', @@ -2362,10 +2359,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many kanban (editable): properly handle create_text node option', function (assert) { + QUnit.test('one2many kanban (editable): properly handle create_text node option', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2385,19 +2382,19 @@ QUnit.module('fields', {}, function () { res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_one2many[name="turtles"] .o-kanban-button-new').text().trim(), "Add turtle", "In O2M Kanban, Add button should have 'Add turtle' label"); form.destroy(); }); - QUnit.test('one2many kanban: create action disabled', function (assert) { + QUnit.test('one2many kanban: create action disabled', async function (assert) { assert.expect(3); this.data.partner.records[0].p = [4]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2422,7 +2419,7 @@ QUnit.module('fields', {}, function () { assert.ok(!form.$('.o-kanban-button-new').length, '"Add" button should not be available in readonly'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.ok(!form.$('.o-kanban-button-new').length, '"Add" button should not be available in edit'); @@ -2431,13 +2428,13 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('editable one2many list, pager is updated', function (assert) { + QUnit.test('editable one2many list, pager is updated', async function (assert) { assert.expect(1); this.data.turtle.records.push({ id: 4, turtle_foo: 'stephen hawking' }); this.data.partner.records[0].turtles = [1, 2, 3, 4]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2452,21 +2449,21 @@ QUnit.module('fields', {}, function () { }); // add a record, then click in form view to confirm it - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.dom.click(form.$el); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$el); assert.strictEqual(form.$('.o_field_widget[name=turtles] .o_cp_pager').text().trim(), '1-3 / 5', "pager should display the correct total"); form.destroy(); }); - QUnit.test('one2many list (non editable): edition', function (assert) { + QUnit.test('one2many list (non editable): edition', async function (assert) { assert.expect(12); var nbWrite = 0; this.data.partner.records[0].p = [2, 4]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2497,7 +2494,7 @@ QUnit.module('fields', {}, function () { assert.ok(!form.$('.o_field_x2many_list_row_add').length, '"Add an item" should not be visible in readonly'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsN(form, '.o_list_view td.o_list_number', 2, 'should contain 2 records'); @@ -2509,10 +2506,10 @@ QUnit.module('fields', {}, function () { '"Add an item" should not visible in edit'); // edit existing subrecord - testUtils.dom.click(form.$('.o_list_view tbody tr:first() td:eq(1)')); + await testUtils.dom.click(form.$('.o_list_view tbody tr:first() td:eq(1)')); - testUtils.fields.editInput($('.modal .o_form_view input'), 'new name'); - testUtils.dom.click($('.modal .modal-footer .btn-primary')); + await testUtils.fields.editInput($('.modal .o_form_view input'), 'new name'); + await testUtils.dom.click($('.modal .modal-footer .btn-primary')); assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name', 'value of subrecord should have been updated'); assert.strictEqual(nbWrite, 0, "should not have write anything in DB"); @@ -2521,23 +2518,23 @@ QUnit.module('fields', {}, function () { // TODO when 'Add an item' will be implemented // remove subrecords - testUtils.dom.click(form.$('.o_list_record_remove:nth(1)')); + await testUtils.dom.click(form.$('.o_list_record_remove:nth(1)')); assert.containsOnce(form, '.o_list_view td.o_list_number', 'should contain 1 subrecord'); assert.strictEqual(form.$('.o_list_view tbody td:first()').text(), 'new name', 'the remaining subrecord should be "new name"'); - testUtils.form.clickSave(form); // save the record + await testUtils.form.clickSave(form); // save the record assert.strictEqual(nbWrite, 1, "should have write the changes in DB"); form.destroy(); }); - QUnit.test('one2many list (editable): edition', function (assert) { + QUnit.test('one2many list (editable): edition', async function (assert) { assert.expect(7); this.data.partner.records[0].p = [2, 4]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2557,25 +2554,25 @@ QUnit.module('fields', {}, function () { assert.ok(!form.$('.o_field_x2many_list_row_add').length, '"Add an item" link should not be available in readonly'); - testUtils.dom.click(form.$('.o_list_view tbody td:first()')); + await testUtils.dom.click(form.$('.o_list_view tbody td:first()')); assert.ok($('.modal .o_form_readonly').length, 'in readonly, clicking on a subrecord should open it in readonly in a dialog'); - testUtils.dom.click($('.modal .o_form_button_cancel')); + await testUtils.dom.click($('.modal .o_form_button_cancel')); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.ok(form.$('.o_field_x2many_list_row_add').length, '"Add an item" link should be available in edit'); // edit existing subrecord - testUtils.dom.click(form.$('.o_list_view tbody td:first()')); + await testUtils.dom.click(form.$('.o_list_view tbody td:first()')); assert.strictEqual($('.modal').length, 0, 'in edit, clicking on a subrecord should not open a dialog'); assert.hasClass(form.$('.o_list_view tbody tr:first()'),'o_selected_row', 'first row should be in edition'); - testUtils.fields.editInput(form.$('.o_list_view input:first()'), 'new name'); + await testUtils.fields.editInput(form.$('.o_list_view input:first()'), 'new name'); - testUtils.dom.click(form.$('.o_list_view tbody tr:nth(1) td:first')); + await testUtils.dom.click(form.$('.o_list_view tbody tr:nth(1) td:first')); assert.doesNotHaveClass(form.$('.o_list_view tbody tr:first'), 'o_selected_row', 'first row should not be in edition anymore'); assert.strictEqual(form.$('.o_list_view tbody td:first').text(), 'new name', @@ -2586,10 +2583,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many list (editable): edition, part 2', function (assert) { + QUnit.test('one2many list (editable): edition, part 2', async function (assert) { assert.expect(8); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2613,12 +2610,12 @@ QUnit.module('fields', {}, function () { }); // edit mode, then click on Add an item and enter a value - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput(form.$('.o_selected_row > td input'), 'kartoffel'); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('.o_selected_row > td input'), 'kartoffel'); // click again on Add an item - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual(form.$('td:contains(kartoffel)').length, 1, "should have one td with the new value"); assert.containsOnce(form, '.o_selected_row > td input', @@ -2626,8 +2623,8 @@ QUnit.module('fields', {}, function () { assert.containsN(form, 'tr.o_data_row', 2, "should have 2 data rows"); // enter another value and save - testUtils.fields.editInput(form.$('.o_selected_row > td input'), 'gemuse'); - testUtils.form.clickSave(form); + await testUtils.fields.editInput(form.$('.o_selected_row > td input'), 'gemuse'); + await testUtils.form.clickSave(form); assert.containsN(form, 'tr.o_data_row', 2, "should have 2 data rows"); assert.strictEqual(form.$('td:contains(kartoffel)').length, 1, "should have one td with the new value"); @@ -2637,10 +2634,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many list (editable): edition, part 3', function (assert) { + QUnit.test('one2many list (editable): edition, part 3', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2659,22 +2656,22 @@ QUnit.module('fields', {}, function () { // edit mode, then click on Add an item 2 times assert.containsOnce(form, 'tr.o_data_row', "should have 1 data rows"); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.containsN(form, 'tr.o_data_row', 3, "should have 3 data rows"); // cancel the edition - testUtils.form.clickDiscard(form); - testUtils.dom.click($('.modal-footer button.btn-primary').first()); + await testUtils.form.clickDiscard(form); + await testUtils.dom.click($('.modal-footer button.btn-primary').first()); assert.containsOnce(form, 'tr.o_data_row', "should have 1 data rows"); form.destroy(); }); - QUnit.test('one2many list (editable): edition, part 4', function (assert) { + QUnit.test('one2many list (editable): edition, part 4', async function (assert) { assert.expect(3); var i = 0; @@ -2687,7 +2684,7 @@ QUnit.module('fields', {}, function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2707,26 +2704,26 @@ QUnit.module('fields', {}, function () { // edit mode, then click on Add an item assert.containsNone(form, 'tr.o_data_row', "should have 0 data rows"); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual(form.$('textarea').val(), "", "field turtle_description should be empty"); // add a value in the turtle_trululu field to trigger an onchange - testUtils.fields.many2one.clickOpenDropdown('turtle_trululu'); - testUtils.fields.many2one.clickHighlightedItem('turtle_trululu'); + await testUtils.fields.many2one.clickOpenDropdown('turtle_trululu'); + await testUtils.fields.many2one.clickHighlightedItem('turtle_trululu'); assert.strictEqual(form.$('textarea').val(), "Some Description", "field turtle_description should be set to the result of the onchange"); form.destroy(); }); - QUnit.test('one2many list (editable): discarding required empty data', function (assert) { + QUnit.test('one2many list (editable): discarding required empty data', async function (assert) { assert.expect(7); this.data.turtle.fields.turtle_foo.required = true; delete this.data.turtle.fields.turtle_foo.default; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2751,15 +2748,15 @@ QUnit.module('fields', {}, function () { // edit mode, then click on Add an item, then click elsewhere assert.containsNone(form, 'tr.o_data_row', "should have 0 data rows"); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.dom.click(form.$('label.o_form_label').first()); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('label.o_form_label').first()); assert.containsNone(form, 'tr.o_data_row', "should still have 0 data rows"); // click on Add an item again, then click on save - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.form.clickSave(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickSave(form); assert.containsNone(form, 'tr.o_data_row', "should still have 0 data rows"); @@ -2767,11 +2764,11 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('editable one2many list, adding line when only one page', function (assert) { + QUnit.test('editable one2many list, adding line when only one page', async function (assert) { assert.expect(1); this.data.partner.records[0].turtles = [1, 2, 3]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2786,22 +2783,21 @@ QUnit.module('fields', {}, function () { }); // add a record, to reach the page size limit - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); // the record currently being added should not count in the pager assert.isNotVisible(form.$('.o_field_widget[name=turtles] .o_cp_pager')); form.destroy(); }); - QUnit.test('editable one2many list, adding line, then discarding', function (assert) { + QUnit.test('editable one2many list, adding line, then discarding', async function (assert) { assert.expect(2); this.data.turtle.records.push({ id: 4, turtle_foo: 'stephen hawking' }); this.data.partner.records[0].turtles = [1, 2, 3, 4]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2816,12 +2812,12 @@ QUnit.module('fields', {}, function () { }); // add a record, then discard - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.form.clickDiscard(form); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickDiscard(form); // confirm the discard operation - testUtils.dom.click($('.modal .modal-footer .btn-primary')); + await testUtils.dom.click($('.modal .modal-footer .btn-primary')); assert.isVisible(form.$('.o_field_widget[name=turtles] .o_cp_pager')); assert.strictEqual(form.$('.o_field_widget[name=turtles] .o_cp_pager').text().trim(), '1-3 / 4', @@ -2830,14 +2826,14 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('editable one2many list, required field and pager', function (assert) { + QUnit.test('editable one2many list, required field and pager', async function (assert) { assert.expect(1); this.data.turtle.records.push({ id: 4, turtle_foo: 'stephen hawking' }); this.data.turtle.fields.turtle_foo.required = true; this.data.partner.records[0].turtles = [1, 2, 3, 4]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2852,24 +2848,24 @@ QUnit.module('fields', {}, function () { }); // add a (empty) record - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); // go on next page. The new record is not valid and should be discarded - testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); + await testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); assert.containsOnce(form, 'tr.o_data_row'); form.destroy(); }); - QUnit.test('editable one2many list, required field, pager and confirm discard', function (assert) { + QUnit.test('editable one2many list, required field, pager and confirm discard', async function (assert) { assert.expect(3); this.data.turtle.records.push({ id: 4, turtle_foo: 'stephen hawking' }); this.data.turtle.fields.turtle_foo.required = true; this.data.partner.records[0].turtles = [1, 2, 3, 4]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2885,19 +2881,19 @@ QUnit.module('fields', {}, function () { }); // add a record with a dirty state, but not valid - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput(form.$('input[name="turtle_int"]'), 4321); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('input[name="turtle_int"]'), 4321); // go to next page. The new record is not valid, but dirty. we should // see a confirm dialog - testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); + await testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); assert.strictEqual(form.$('.o_field_widget[name=turtles] .o_cp_pager').text().trim(), '1-3 / 4', "pager should still display the correct total"); // click on cancel - testUtils.dom.click($('.modal .modal-footer .btn-secondary')); + await testUtils.dom.click($('.modal .modal-footer .btn-secondary')); assert.strictEqual(form.$('.o_field_widget[name=turtles] .o_cp_pager').text().trim(), '1-3 / 4', "pager should again display the correct total"); @@ -2906,12 +2902,12 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('editable one2many list, adding, discarding, and pager', function (assert) { + QUnit.test('editable one2many list, adding, discarding, and pager', async function (assert) { assert.expect(2); this.data.partner.records[0].turtles = [1]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2926,18 +2922,18 @@ QUnit.module('fields', {}, function () { }); // add a 4 records record (to make the pager appear) - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); // go on next page - testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); + await testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); // discard - testUtils.form.clickDiscard(form); - testUtils.dom.click($('.modal .modal-footer .btn-primary')); + await testUtils.form.clickDiscard(form); + await testUtils.dom.click($('.modal .modal-footer .btn-primary')); assert.containsOnce(form, 'tr.o_data_row'); assert.isNotVisible(form.$('.o_field_widget[name=turtles] .o_cp_pager')); @@ -2945,13 +2941,13 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('unselecting a line with missing required data', function (assert) { + QUnit.test('unselecting a line with missing required data', async function (assert) { assert.expect(5); this.data.turtle.fields.turtle_foo.required = true; delete this.data.turtle.fields.turtle_foo.default; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2971,40 +2967,40 @@ QUnit.module('fields', {}, function () { // edit mode, then click on Add an item, then click elsewhere assert.containsNone(form, 'tr.o_data_row', "should have 0 data rows"); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.containsOnce(form, 'tr.o_data_row', "should have 1 data rows"); // adding a value in the non required field, so it is dirty, but with // a missing required field - testUtils.fields.editInput(form.$('input[name="turtle_int"]'), '12345'); + await testUtils.fields.editInput(form.$('input[name="turtle_int"]'), '12345'); // click elsewhere, - testUtils.dom.click(form.$('label.o_form_label')); + await testUtils.dom.click(form.$('label.o_form_label')); assert.strictEqual($('.modal').length, 1, 'a confirmation model should be opened'); // click on cancel, the line should still be selected - testUtils.dom.click($('.modal .modal-footer button.btn-secondary')); + await testUtils.dom.click($('.modal .modal-footer button.btn-secondary')); assert.containsOnce(form, 'tr.o_data_row.o_selected_row', "should still have 1 selected data row"); // click elsewhere, and click on ok (on the confirmation dialog) - testUtils.dom.click(form.$('label.o_form_label')); - testUtils.dom.click($('.modal .modal-footer button.btn-primary')); + await testUtils.dom.click(form.$('label.o_form_label')); + await testUtils.dom.click($('.modal .modal-footer button.btn-primary')); assert.containsNone(form, 'tr.o_data_row', "should have 0 data rows (invalid line has been discarded"); form.destroy(); }); - QUnit.test('pressing enter in a o2m with a required empty field', function (assert) { + QUnit.test('pressing enter in a o2m with a required empty field', async function (assert) { assert.expect(4); this.data.turtle.fields.turtle_foo.required = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3025,19 +3021,16 @@ QUnit.module('fields', {}, function () { }); // edit mode, then click on Add an item, then click elsewhere - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - form.$('input[name="turtle_foo"]').trigger($.Event('keydown', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.triggerKeydown(form.$('input[name="turtle_foo"]'), 'enter'); assert.hasClass(form.$('input[name="turtle_foo"]'),'o_field_invalid', "input should be marked invalid"); assert.verifySteps(['read', 'default_get']); form.destroy(); }); - QUnit.test('editing a o2m, with required field and onchange', function (assert) { + QUnit.test('editing a o2m, with required field and onchange', async function (assert) { assert.expect(12); this.data.turtle.fields.turtle_foo.required = true; @@ -3048,7 +3041,7 @@ QUnit.module('fields', {}, function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3074,16 +3067,16 @@ QUnit.module('fields', {}, function () { // edit mode, then click on Add an item assert.containsNone(form, 'tr.o_data_row', "should have 0 data rows"); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); // input some text in required turtle_foo field - testUtils.fields.editInput(form.$('input[name="turtle_foo"]'), 'aubergine'); + await testUtils.fields.editInput(form.$('input[name="turtle_foo"]'), 'aubergine'); assert.strictEqual(form.$('input[name="turtle_int"]').val(), "9", "onchange should have been triggered"); // save and check everything is fine - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_data_row td:contains(aubergine)').length, 1, "should have one row with turtle_foo value"); assert.strictEqual(form.$('.o_data_row td:contains(9)').length, 1, @@ -3093,10 +3086,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('editable o2m, pressing ESC discard current changes', function (assert) { + QUnit.test('editable o2m, pressing ESC discard current changes', async function (assert) { assert.expect(5); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3114,24 +3107,24 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.containsOnce(form, 'tr.o_data_row', "there should be one data row"); - form.$('input[name="turtle_foo"]').trigger({ type: 'keydown', which: $.ui.keyCode.ESCAPE }); + await testUtils.fields.triggerKeydown(form.$('input[name="turtle_foo"]'), 'escape'); assert.containsNone(form, 'tr.o_data_row', "data row should have been discarded"); assert.verifySteps(['read', 'default_get']); form.destroy(); }); - QUnit.test('editable o2m with required field, pressing ESC discard current changes', function (assert) { + QUnit.test('editable o2m with required field, pressing ESC discard current changes', async function (assert) { assert.expect(5); this.data.turtle.fields.turtle_foo.required = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3149,22 +3142,22 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.containsOnce(form, 'tr.o_data_row', "there should be one data row"); - form.$('input[name="turtle_foo"]').trigger({ type: 'keydown', which: $.ui.keyCode.ESCAPE }); + await testUtils.fields.triggerKeydown(form.$('input[name="turtle_foo"]'), 'escape'); assert.containsNone(form, 'tr.o_data_row', "data row should have been discarded"); assert.verifySteps(['read', 'default_get']); form.destroy(); }); - QUnit.test('pressing escape in editable o2m list in dialog', function (assert) { + QUnit.test('pressing escape in editable o2m list in dialog', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3192,14 +3185,13 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.dom.click($('.modal .o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click($('.modal .o_field_x2many_list_row_add a')); assert.strictEqual($('.modal .o_data_row.o_selected_row').length, 1, "there should be a row in edition in the dialog"); - // trigger keydown ESCAPE in the edited row - $('.modal .o_data_cell input').trigger({ type: 'keydown', which: $.ui.keyCode.ESCAPE }); + await testUtils.fields.triggerKeydown($('.modal .o_data_cell input'), 'escape'); assert.strictEqual($('.modal').length, 1, "dialog should still be open"); @@ -3209,7 +3201,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('editable o2m with onchange and required field: delete an invalid line', function (assert) { + QUnit.test('editable o2m with onchange and required field: delete an invalid line', async function (assert) { assert.expect(5); this.data.partner.onchanges = { @@ -3218,7 +3210,7 @@ QUnit.module('fields', {}, function () { this.data.partner.records[0].turtles = [1]; this.data.turtle.records[0].product_id = 37; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3239,16 +3231,16 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(form.$('.o_data_cell:first')); + await testUtils.dom.click(form.$('.o_data_cell:first')); form.$('.o_field_widget[name="product_id"] input').val('').trigger('keyup'); assert.verifySteps(['read', 'read'], 'no onchange should be done as line is invalid'); - testUtils.dom.click(form.$('.o_list_record_remove')); - assert.verifySteps(['read', 'read', 'onchange'], 'onchange should have been done'); + await testUtils.dom.click(form.$('.o_list_record_remove')); + assert.verifySteps(['onchange'], 'onchange should have been done'); form.destroy(); }); - QUnit.test('onchange in a one2many', function (assert) { + QUnit.test('onchange in a one2many', async function (assert) { assert.expect(1); this.data.partner.records.push({ @@ -3258,7 +3250,7 @@ QUnit.module('fields', {}, function () { this.data.partner.records[1].p = [3]; this.data.partner.onchanges = { p: true }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3272,7 +3264,7 @@ QUnit.module('fields', {}, function () { res_id: 2, mockRPC: function (route, args) { if (args.method === 'onchange') { - return $.when({ + return Promise.resolve({ value: { p: [ [5], // delete all @@ -3285,17 +3277,17 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_one2many tbody td').first()); - testUtils.fields.editInput(form.$('.o_field_one2many tbody td').first().find('input'), "new value"); - testUtils.form.clickSave(form); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_one2many tbody td').first()); + await testUtils.fields.editInput(form.$('.o_field_one2many tbody td').first().find('input'), "new value"); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_one2many tbody td').first().text(), 'from onchange', "display name of first record in o2m list should be 'new value'"); form.destroy(); }); - QUnit.test('one2many, default_get and onchange (basic)', function (assert) { + QUnit.test('one2many, default_get and onchange (basic)', async function (assert) { assert.expect(1); this.data.partner.fields.p.default = [ @@ -3303,7 +3295,7 @@ QUnit.module('fields', {}, function () { ]; this.data.partner.onchanges = { p: true }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3316,7 +3308,7 @@ QUnit.module('fields', {}, function () { '</form>', mockRPC: function (route, args) { if (args.method === 'onchange') { - return $.when({ + return Promise.resolve({ value: { p: [ [5], // delete all @@ -3334,14 +3326,14 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many and default_get (with date)', function (assert) { + QUnit.test('one2many and default_get (with date)', async function (assert) { assert.expect(1); this.data.partner.fields.p.default = [ [0, false, { date: '2017-10-08' }], ]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3360,14 +3352,14 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many and onchange (with integer)', function (assert) { + QUnit.test('one2many and onchange (with integer)', async function (assert) { assert.expect(4); this.data.turtle.onchanges = { - turtle_int: function (obj) { } + turtle_int: function () { } }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3384,10 +3376,10 @@ QUnit.module('fields', {}, function () { return this._super.apply(this, arguments); }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('td:contains(9)')); - testUtils.fields.editInput(form.$('td input[name="turtle_int"]'), "3"); + await testUtils.dom.click(form.$('td:contains(9)')); + await testUtils.fields.editInput(form.$('td input[name="turtle_int"]'), "3"); // the 'change' event is triggered on the input when we focus somewhere // else, for example by clicking in the body. However, if we try to @@ -3399,15 +3391,15 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many and onchange (with date)', function (assert) { + QUnit.test('one2many and onchange (with date)', async function (assert) { assert.expect(7); this.data.partner.onchanges = { - date: function (obj) { } + date: function () { } }; this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3424,23 +3416,28 @@ QUnit.module('fields', {}, function () { return this._super.apply(this, arguments); }, }); - testUtils.form.clickEdit(form); - - testUtils.dom.click(form.$('td:contains(01/25/2017)')); - testUtils.dom.click(form.$('.o_datepicker_input')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first()); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .year:contains(2017)')); - testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(1)); - testUtils.dom.click($('.day:contains(22)')); + await testUtils.form.clickEdit(form); - testUtils.form.clickSave(form); + await testUtils.dom.click(form.$('td:contains(01/25/2017)')); + await testUtils.dom.click(form.$('.o_datepicker_input')); + await testUtils.nextTick(); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch').first()); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch:eq(1)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .year:contains(2017)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .month').eq(1)); + await testUtils.dom.click($('.day:contains(22)')); + // trigger the blur of input before the click on Save, to closely mock what actually + // happens when we really click on Save + // this is required to close the datepicker before removing the input from the DOM + // (because of the re-rendering in readonly), otherwise tempusdominus crashes + await testUtils.dom.triggerEvents(form.$('.o_datepicker_input'), 'blur'); + await testUtils.form.clickSave(form); assert.verifySteps(['read', 'read', 'onchange', 'write', 'read', 'read']); form.destroy(); }); - QUnit.test('one2many and onchange (with command DELETE_ALL)', function (assert) { + QUnit.test('one2many and onchange (with command DELETE_ALL)', async function (assert) { assert.expect(5); this.data.partner.onchanges = { @@ -3451,7 +3448,7 @@ QUnit.module('fields', {}, function () { }; this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3482,34 +3479,34 @@ QUnit.module('fields', {}, function () { "o2m should contain one row"); // empty o2m by triggering the onchange - testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), 'trigger onchange'); + await testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), 'trigger onchange'); assert.containsNone(form, '.o_data_row', "rows of the o2m should have been deleted"); // add two new subrecords - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'x'); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'y'); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'x'); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'y'); assert.containsN(form, '.o_data_row', 2, "o2m should contain two rows"); // empty o2m by triggering the onchange - testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), 'trigger onchange again'); + await testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), 'trigger onchange again'); assert.containsNone(form, '.o_data_row', "rows of the o2m should have been deleted"); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'z'); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'z'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('one2many and onchange only write modified field', function (assert) { + QUnit.test('one2many and onchange only write modified field', async function (assert) { assert.expect(2); this.data.partner.onchanges = { @@ -3532,7 +3529,7 @@ QUnit.module('fields', {}, function () { this.data.partner.records[0].turtles = [3]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3567,14 +3564,14 @@ QUnit.module('fields', {}, function () { assert.containsOnce(form, '.o_data_row', "o2m should contain one row"); - testUtils.dom.click(form.$('.o_field_one2many .o_list_view tbody tr:first td:first')); - testUtils.fields.editInput(form.$('.o_field_one2many .o_list_view tbody tr:first input:first'), 'blurp'); + await testUtils.dom.click(form.$('.o_field_one2many .o_list_view tbody tr:first td:first')); + await testUtils.fields.editInput(form.$('.o_field_one2many .o_list_view tbody tr:first input:first'), 'blurp'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('one2many with CREATE onchanges correctly refreshed', function (assert) { + QUnit.test('one2many with CREATE onchanges correctly refreshed', async function (assert) { assert.expect(5); var delta = 0; @@ -3626,7 +3623,7 @@ QUnit.module('fields', {}, function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3648,27 +3645,27 @@ QUnit.module('fields', {}, function () { assert.containsNone(form, '.o_data_row', "o2m shouldn't contain any row"); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); // trigger the first onchange deactiveOnchange = false; - testUtils.fields.editInput(form.$('input[name="turtle_int"]'), '10'); + await testUtils.fields.editInput(form.$('input[name="turtle_int"]'), '10'); // put the list back in non edit mode - testUtils.dom.click(form.$('input[name="foo"]')); + await testUtils.dom.click(form.$('input[name="foo"]')); assert.strictEqual(form.$('.o_data_row').text(), "first10second-10", "should correctly refresh the records"); // trigger the second onchange - testUtils.dom.click(form.$('.o_field_x2many_list tbody tr:first td:first')); - testUtils.fields.editInput(form.$('input[name="turtle_int"]'), '20'); + await testUtils.dom.click(form.$('.o_field_x2many_list tbody tr:first td:first')); + await testUtils.fields.editInput(form.$('input[name="turtle_int"]'), '20'); - testUtils.dom.click(form.$('input[name="foo"]')); + await testUtils.dom.click(form.$('input[name="foo"]')); assert.strictEqual(form.$('.o_data_row').text(), "first20second-20", "should correctly refresh the records"); assert.containsN(form, '.o_field_widget', delta, "all (non visible) field widgets should have been destroyed"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_data_row').text(), "first20second-20", "should correctly refresh the records after save"); @@ -3677,7 +3674,7 @@ QUnit.module('fields', {}, function () { testUtils.mock.unpatch(AbstractField); }); - QUnit.test('editable one2many with sub widgets are rendered in readonly', function (assert) { + QUnit.test('editable one2many with sub widgets are rendered in readonly', async function (assert) { assert.expect(2); var editableWidgets = 0; @@ -3690,7 +3687,7 @@ QUnit.module('fields', {}, function () { }, }); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3710,7 +3707,7 @@ QUnit.module('fields', {}, function () { assert.strictEqual(editableWidgets, 1, "o2m is only widget in edit mode"); - testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); assert.strictEqual(editableWidgets, 3, "3 widgets currently in edit mode"); @@ -3719,7 +3716,7 @@ QUnit.module('fields', {}, function () { testUtils.mock.unpatch(AbstractField); }); - QUnit.test('one2many editable list with onchange keeps the order', function (assert) { + QUnit.test('one2many editable list with onchange keeps the order', async function (assert) { assert.expect(2); this.data.partner.records[0].p = [1, 2, 4]; @@ -3727,7 +3724,7 @@ QUnit.module('fields', {}, function () { p: function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3747,9 +3744,9 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('.o_data_cell').text(), 'first recordsecond recordaaa', "records should be display in the correct order"); - testUtils.dom.click(form.$('.o_data_row:first .o_data_cell')); - testUtils.fields.editInput(form.$('.o_selected_row .o_field_widget[name=display_name]'), 'new'); - testUtils.dom.click(form.$el); + await testUtils.dom.click(form.$('.o_data_row:first .o_data_cell')); + await testUtils.fields.editInput(form.$('.o_selected_row .o_field_widget[name=display_name]'), 'new'); + await testUtils.dom.click(form.$el); assert.strictEqual(form.$('.o_data_cell').text(), 'newsecond recordaaa', "records should be display in the correct order"); @@ -3757,14 +3754,14 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many list (editable): readonly domain is evaluated', function (assert) { + QUnit.test('one2many list (editable): readonly domain is evaluated', async function (assert) { assert.expect(2); this.data.partner.records[0].p = [2, 4]; this.data.partner.records[1].product_id = false; this.data.partner.records[2].product_id = 37; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3779,7 +3776,7 @@ QUnit.module('fields', {}, function () { res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.hasClass(form.$('.o_list_view tbody tr:eq(0) td:first'),'o_readonly_modifier', "first record should have display_name in readonly mode"); @@ -3789,12 +3786,12 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('pager of one2many field in new record', function (assert) { + QUnit.test('pager of one2many field in new record', async function (assert) { assert.expect(3); this.data.partner.records[0].p = []; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3811,7 +3808,7 @@ QUnit.module('fields', {}, function () { 'o2m pager should be hidden'); // click to create a subrecord - testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); assert.containsOnce(form, 'tr.o_data_row'); assert.ok(!form.$('.o_x2m_control_panel .o_cp_pager div').is(':visible'), @@ -3819,7 +3816,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many list with a many2one', function (assert) { + QUnit.test('one2many list with a many2one', async function (assert) { assert.expect(5); this.data.partner.records[0].p = [2]; @@ -3833,7 +3830,7 @@ QUnit.module('fields', {}, function () { // }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3863,14 +3860,14 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('tbody td:contains(xpad)').length, 0, "should not display 'xpad' anywhere"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); - testUtils.fields.many2one.clickOpenDropdown('product_id'); + await testUtils.fields.many2one.clickOpenDropdown('product_id'); testUtils.fields.many2one.clickItem('product_id', 'xpad'); - testUtils.dom.click($('.modal .modal-footer button:eq(0)')); + await testUtils.dom.click($('.modal .modal-footer button:eq(0)')); assert.strictEqual(form.$('tbody td:contains(xpad)').length, 1, "should display 'xpad' on a td"); @@ -3879,14 +3876,12 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many list with inline form view', function (assert) { + QUnit.test('one2many list with inline form view', async function (assert) { assert.expect(5); this.data.partner.records[0].p = []; - var rpcCount = 0; - - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3907,7 +3902,6 @@ QUnit.module('fields', {}, function () { '</form>', res_id: 1, mockRPC: function (route, args) { - rpcCount++; if (args.method === 'write') { assert.deepEqual(args.args[1].p, [[0, args.args[1].p[0][1], { foo: false, int_field: 123, product_id: 41, @@ -3917,63 +3911,63 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); // write in the many2one field, value = 37 (xphone) - testUtils.fields.many2one.clickOpenDropdown('product_id'); - testUtils.fields.many2one.clickHighlightedItem('product_id'); + await testUtils.fields.many2one.clickOpenDropdown('product_id'); + await testUtils.fields.many2one.clickHighlightedItem('product_id'); // write in the integer field - testUtils.fields.editInput($('.modal .modal-body input.o_field_widget'), '123'); + await testUtils.fields.editInput($('.modal .modal-body input.o_field_widget'), '123'); // save and close - testUtils.dom.click($('.modal .modal-footer button:eq(0)')); + await testUtils.dom.click($('.modal .modal-footer button:eq(0)')); assert.strictEqual(form.$('tbody td:contains(xphone)').length, 1, "should display 'xphone' in a td"); // reopen the record in form view - testUtils.dom.click(form.$('tbody td:contains(xphone)')); + await testUtils.dom.click(form.$('tbody td:contains(xphone)')); assert.strictEqual($('.modal .modal-body input').val(), "xphone", "should display 'xphone' in an input"); - testUtils.fields.editInput($('.modal .modal-body input.o_field_widget'), '456'); + await testUtils.fields.editInput($('.modal .modal-body input.o_field_widget'), '456'); // discard - testUtils.dom.click($('.modal .modal-footer span:contains(Discard)')); + await testUtils.dom.click($('.modal .modal-footer span:contains(Discard)')); // reopen the record in form view - testUtils.dom.click(form.$('tbody td:contains(xphone)')); + await testUtils.dom.click(form.$('tbody td:contains(xphone)')); assert.strictEqual($('.modal .modal-body input.o_field_widget').val(), "123", "should display 123 (previous change has been discarded)"); // write in the many2one field, value = 41 (xpad) - testUtils.fields.many2one.clickOpenDropdown('product_id'); + await testUtils.fields.many2one.clickOpenDropdown('product_id'); testUtils.fields.many2one.clickItem('product_id', 'xpad'); // save and close - testUtils.dom.click($('.modal .modal-footer button:eq(0)')); + await testUtils.dom.click($('.modal .modal-footer button:eq(0)')); assert.strictEqual(form.$('tbody td:contains(xpad)').length, 1, "should display 'xpad' in a td"); // save the record - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('one2many list with inline form view with context with parent key', function (assert) { + QUnit.test('one2many list with inline form view with context with parent key', async function (assert) { assert.expect(2); this.data.partner.records[0].p = [2]; this.data.partner.records[0].product_id = 41; this.data.partner.records[1].product_id = 37; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4001,23 +3995,23 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // open a modal - testUtils.dom.click(form.$('tr.o_data_row:eq(0) td:contains(xphone)')); + await testUtils.dom.click(form.$('tr.o_data_row:eq(0) td:contains(xphone)')); // write in the many2one field - testUtils.dom.click($('.modal .o_field_many2one input')); + await testUtils.dom.click($('.modal .o_field_many2one input')); form.destroy(); }); - QUnit.test('value of invisible x2many fields is correctly evaluated in context', function (assert) { + QUnit.test('value of invisible x2many fields is correctly evaluated in context', async function (assert) { assert.expect(1); this.data.partner.records[0].timmy = [12]; this.data.partner.records[0].p = [2, 3]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4040,19 +4034,19 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_widget[name=product_id] input')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_widget[name=product_id] input')); form.destroy(); }); - QUnit.test('one2many list, editable, with many2one and with context with parent key', function (assert) { + QUnit.test('one2many list, editable, with many2one and with context with parent key', async function (assert) { assert.expect(1); this.data.partner.records[0].p = [2]; this.data.partner.records[1].product_id = 37; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4074,23 +4068,23 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('tr.o_data_row:eq(0) td:contains(xphone)')); + await testUtils.dom.click(form.$('tr.o_data_row:eq(0) td:contains(xphone)')); // trigger a name search - testUtils.dom.click(form.$('table td input')); + await testUtils.dom.click(form.$('table td input')); form.destroy(); }); - QUnit.test('one2many list, editable, with a date in the context', function (assert) { + QUnit.test('one2many list, editable, with a date in the context', async function (assert) { assert.expect(1); this.data.partner.records[0].p = [2]; this.data.partner.records[1].product_id = 37; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4114,18 +4108,18 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); form.destroy(); }); - QUnit.test('one2many field with context', function (assert) { + QUnit.test('one2many field with context', async function (assert) { assert.expect(2); var counter = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4152,19 +4146,19 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput(form.$('input[name="turtle_foo"]'), 'hammer'); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('input[name="turtle_foo"]'), 'hammer'); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); form.destroy(); }); - QUnit.test('one2many list edition, some basic functionality', function (assert) { + QUnit.test('one2many list edition, some basic functionality', async function (assert) { assert.expect(3); this.data.partner.fields.foo.default = false; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4177,30 +4171,30 @@ QUnit.module('fields', {}, function () { '</form>', res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); assert.containsOnce(form, 'td input.o_field_widget', "should have created a row in edit mode"); - testUtils.fields.editInput(form.$('td input.o_field_widget'), 'a'); + await testUtils.fields.editInput(form.$('td input.o_field_widget'), 'a'); assert.containsOnce(form, 'td input.o_field_widget', "should not have unselected the row after edition"); - testUtils.fields.editInput(form.$('td input.o_field_widget'), 'abc'); - testUtils.form.clickSave(form); + await testUtils.fields.editInput(form.$('td input.o_field_widget'), 'abc'); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('td:contains(abc)').length, 1, "should have a row with the correct value"); form.destroy(); }); - QUnit.test('one2many list, the context is properly evaluated and sent', function (assert) { + QUnit.test('one2many list, the context is properly evaluated and sent', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4223,15 +4217,15 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); form.destroy(); }); - QUnit.test('one2many with many2many widget: create', function (assert) { + QUnit.test('one2many with many2many widget: create', async function (assert) { assert.expect(10); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4271,28 +4265,28 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual($('.modal .o_data_row').length, 2, "sould have 2 records in the select view (the last one is not displayed because it is already selected)"); - testUtils.dom.click($('.modal .o_data_row:first .o_list_record_selector input')); - testUtils.dom.click($('.modal .o_select_button')); - testUtils.dom.click($('.o_form_button_save')); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click($('.modal .o_data_row:first .o_list_record_selector input')); + await testUtils.dom.click($('.modal .o_select_button')); + await testUtils.dom.click($('.o_form_button_save')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual($('.modal .o_data_row').length, 1, "sould have 1 record in the select view"); - testUtils.dom.click($('.modal-footer button:eq(1)')); - testUtils.fields.editInput($('.modal input.o_field_widget[name="turtle_foo"]'), 'tototo'); - testUtils.fields.editInput($('.modal input.o_field_widget[name="turtle_int"]'), 50); - testUtils.fields.many2one.clickOpenDropdown('product_id'); - testUtils.fields.many2one.clickHighlightedItem('product_id'); + await testUtils.dom.click($('.modal-footer button:eq(1)')); + await testUtils.fields.editInput($('.modal input.o_field_widget[name="turtle_foo"]'), 'tototo'); + await testUtils.fields.editInput($('.modal input.o_field_widget[name="turtle_int"]'), 50); + await testUtils.fields.many2one.clickOpenDropdown('product_id'); + await testUtils.fields.many2one.clickHighlightedItem('product_id'); - testUtils.dom.click($('.modal-footer button:contains(&):first')); + await testUtils.dom.click($('.modal-footer button:contains(&):first')); assert.strictEqual($('.modal').length, 0, "sould close the modals"); @@ -4301,15 +4295,15 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('.o_data_row').text(), "blip1.59yop1.50tototo1.550xphone", "should display the record values in one2many list"); - testUtils.dom.click($('.o_form_button_save')); + await testUtils.dom.click($('.o_form_button_save')); form.destroy(); }); - QUnit.test('one2many with many2many widget: edition', function (assert) { + QUnit.test('one2many with many2many widget: edition', async function (assert) { assert.expect(7); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4351,44 +4345,44 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(form.$('.o_data_row:first')); + await testUtils.dom.click(form.$('.o_data_row:first')); assert.strictEqual($('.modal .modal-title').first().text().trim(), 'Open: one2many turtle field', "modal should use the python field string as title"); - testUtils.dom.click($('.modal .o_form_button_cancel')); - testUtils.form.clickEdit(form); + await testUtils.dom.click($('.modal .o_form_button_cancel')); + await testUtils.form.clickEdit(form); // edit the first one2many record - testUtils.dom.click(form.$('.o_data_row:first')); - testUtils.fields.many2one.clickOpenDropdown('product_id'); - testUtils.fields.many2one.clickHighlightedItem('product_id'); - testUtils.dom.click($('.modal-footer button:first')); + await testUtils.dom.click(form.$('.o_data_row:first')); + await testUtils.fields.many2one.clickOpenDropdown('product_id'); + await testUtils.fields.many2one.clickHighlightedItem('product_id'); + await testUtils.dom.click($('.modal-footer button:first')); - testUtils.dom.click($('.o_form_button_save')); + await testUtils.dom.click($('.o_form_button_save')); // add a one2many record - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.dom.click($('.modal .o_data_row:first .o_list_record_selector input')); - testUtils.dom.click($('.modal .o_select_button')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click($('.modal .o_data_row:first .o_list_record_selector input')); + await testUtils.dom.click($('.modal .o_select_button')); // edit the second one2many record - testUtils.dom.click(form.$('.o_data_row:eq(1)')); - testUtils.fields.many2one.clickOpenDropdown('product_id'); - testUtils.fields.many2one.clickHighlightedItem('product_id'); - testUtils.dom.click($('.modal-footer button:first')); + await testUtils.dom.click(form.$('.o_data_row:eq(1)')); + await testUtils.fields.many2one.clickOpenDropdown('product_id'); + await testUtils.fields.many2one.clickHighlightedItem('product_id'); + await testUtils.dom.click($('.modal-footer button:first')); - testUtils.dom.click($('.o_form_button_save')); + await testUtils.dom.click($('.o_form_button_save')); form.destroy(); }); - QUnit.test('new record, the context is properly evaluated and sent', function (assert) { + QUnit.test('new record, the context is properly evaluated and sent', async function (assert) { assert.expect(2); this.data.partner.fields.int_field.default = 17; var n = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4413,15 +4407,15 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); form.destroy(); }); - QUnit.test('parent data is properly sent on an onchange rpc', function (assert) { + QUnit.test('parent data is properly sent on an onchange rpc', async function (assert) { assert.expect(1); this.data.partner.onchanges = { bar: function () { } }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4444,16 +4438,16 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); form.destroy(); }); - QUnit.test('parent data is properly sent on an onchange rpc, new record', function (assert) { + QUnit.test('parent data is properly sent on an onchange rpc, new record', async function (assert) { assert.expect(6); this.data.turtle.onchanges = { turtle_bar: function () { } }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4475,12 +4469,12 @@ QUnit.module('fields', {}, function () { return this._super.apply(this, arguments); }, }); - testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); assert.verifySteps(['default_get', 'onchange', 'default_get', 'onchange']); form.destroy(); }); - QUnit.test('id in one2many obtained in onchange is properly set', function (assert) { + QUnit.test('id in one2many obtained in onchange is properly set', async function (assert) { assert.expect(1); this.data.partner.onchanges.turtles = function (obj) { @@ -4489,7 +4483,7 @@ QUnit.module('fields', {}, function () { [1, 3, { turtle_foo: "kawa" }] ]; }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4508,10 +4502,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('id field in one2many in a new record', function (assert) { + QUnit.test('id field in one2many in a new record', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4533,19 +4527,19 @@ QUnit.module('fields', {}, function () { return this._super.apply(this, arguments); }, }); - testUtils.dom.click(form.$('td.o_field_x2many_list_row_add a')); - testUtils.fields.editInput(form.$('td input[name="turtle_foo"]'), 'cat'); - testUtils.form.clickSave(form); + await testUtils.dom.click(form.$('td.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('td input[name="turtle_foo"]'), 'cat'); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('sub form view with a required field', function (assert) { + QUnit.test('sub form view with a required field', async function (assert) { assert.expect(2); this.data.partner.fields.foo.required = true; this.data.partner.fields.foo.default = null; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4562,9 +4556,9 @@ QUnit.module('fields', {}, function () { res_id: 1, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); - testUtils.dom.click($('.modal-footer button.btn-primary').first()); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('tbody td.o_field_x2many_list_row_add a')); + await testUtils.dom.click($('.modal-footer button.btn-primary').first()); assert.strictEqual($('.modal').length, 1, "should still have an open modal"); assert.strictEqual($('.modal tbody label.o_field_invalid').length, 1, @@ -4572,12 +4566,12 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many list with action button', function (assert) { + QUnit.test('one2many list with action button', async function (assert) { assert.expect(4); this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4605,17 +4599,17 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(form.$('.o_list_button button')); + await testUtils.dom.click(form.$('.o_list_button button')); form.destroy(); }); - QUnit.test('one2many kanban with action button', function (assert) { + QUnit.test('one2many kanban with action button', async function (assert) { assert.expect(4); this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4649,12 +4643,12 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(form.$('.oe_kanban_action_button')); + await testUtils.dom.click(form.$('.oe_kanban_action_button')); form.destroy(); }); - QUnit.test('one2many kanban with edit type action and domain widget (widget using SpecialData)', function (assert) { + QUnit.test('one2many kanban with edit type action and domain widget (widget using SpecialData)', async function (assert) { assert.expect(1); this.data.turtle.fields.model_name = { string: "Domain Condition Model", type: "char" }; @@ -4664,7 +4658,7 @@ QUnit.module('fields', {}, function () { record.condition = '[]'; }); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4694,12 +4688,12 @@ QUnit.module('fields', {}, function () { res_id: 1, }); - testUtils.dom.click(form.$('.oe_kanban_action:eq(0)')); + await testUtils.dom.click(form.$('.oe_kanban_action:eq(0)')); assert.strictEqual($('.o_domain_selector').length, 1, "should add domain selector widget"); form.destroy(); }); - QUnit.test('one2many list with onchange and domain widget (widget using SpecialData)', function (assert) { + QUnit.test('one2many list with onchange and domain widget (widget using SpecialData)', async function (assert) { assert.expect(3); this.data.turtle.fields.model_name = { string: "Domain Condition Model", type: "char" }; @@ -4729,7 +4723,7 @@ QUnit.module('fields', {}, function () { }, }; var nbFetchSpecialDomain = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4762,28 +4756,28 @@ QUnit.module('fields', {}, function () { } }); - testUtils.dom.click(form.$('.o_field_one2many .o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_one2many .o_field_x2many_list_row_add a')); assert.strictEqual($('.modal').length, 1, "form view dialog should be opened"); - testUtils.fields.editInput($('.modal-body input[name="model_name"]'), 'partner'); - testUtils.dom.click($('.modal-footer button:first')); + await testUtils.fields.editInput($('.modal-body input[name="model_name"]'), 'partner'); + await testUtils.dom.click($('.modal-footer button:first')); assert.strictEqual(form.$('.o_field_one2many tbody tr:first').text(), "coucouhas changed[]", "the onchange should create one new record and remove the existing"); - testUtils.dom.click(form.$('.o_field_one2many .o_list_view tbody tr:eq(0) td:first')); + await testUtils.dom.click(form.$('.o_field_one2many .o_list_view tbody tr:eq(0) td:first')); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(nbFetchSpecialDomain, 1, "should only fetch special domain once"); form.destroy(); }); - QUnit.test('one2many without inline tree arch', function (assert) { + QUnit.test('one2many without inline tree arch', async function (assert) { assert.expect(2); this.data.partner.records[0].turtles = [2, 3]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4809,13 +4803,13 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('many2one and many2many in one2many', function (assert) { + QUnit.test('many2one and many2many in one2many', async function (assert) { assert.expect(11); this.data.turtle.records[1].product_id = 37; this.data.partner.records[0].turtles = [2, 3]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4862,46 +4856,46 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('.o_data_row:first td:nth(2) .badge:first span').text(), 'second record', "m2m values should have been correctly fetched"); - testUtils.dom.click(form.$('.o_data_row:first')); + await testUtils.dom.click(form.$('.o_data_row:first')); assert.strictEqual($('.modal .o_field_widget').text(), "xphone", 'should display the form view dialog with the many2one value'); - testUtils.dom.click($('.modal-footer button')); + await testUtils.dom.click($('.modal-footer button')); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // edit the m2m of first row - testUtils.dom.click(form.$('.o_list_view tbody td:first()')); + await testUtils.dom.click(form.$('.o_list_view tbody td:first()')); // remove a tag - testUtils.dom.click(form.$('.o_field_many2manytags .badge:contains(aaa) .o_delete')); + await testUtils.dom.click(form.$('.o_field_many2manytags .badge:contains(aaa) .o_delete')); assert.strictEqual(form.$('.o_selected_row .o_field_many2manytags .o_badge_text:contains(aaa)').length, 0, "tag should have been correctly removed"); // add a tag - testUtils.fields.many2one.clickOpenDropdown('partner_ids'); - testUtils.fields.many2one.clickHighlightedItem('partner_ids'); + await testUtils.fields.many2one.clickOpenDropdown('partner_ids'); + await testUtils.fields.many2one.clickHighlightedItem('partner_ids'); assert.strictEqual(form.$('.o_selected_row .o_field_many2manytags .o_badge_text:contains(first record)').length, 1, "tag should have been correctly added"); // edit the m2o of first row - testUtils.fields.many2one.clickOpenDropdown('product_id'); - testUtils.fields.many2one.clickItem('product_id', 'xpad'); + await testUtils.fields.many2one.clickOpenDropdown('product_id'); + await testUtils.fields.many2one.clickItem('product_id', 'xpad'); assert.strictEqual(form.$('.o_selected_row .o_field_many2one:first input').val(), 'xpad', "m2o value should have been updated"); // save (should correctly generate the commands) - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('many2manytag in one2many, onchange, some modifiers, and more than one page', function (assert) { + QUnit.test('many2manytag in one2many, onchange, some modifiers, and more than one page', async function (assert) { assert.expect(9); this.data.partner.records[0].turtles = [1, 2, 3]; this.data.partner.onchanges.turtles = function () { }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4922,8 +4916,8 @@ QUnit.module('fields', {}, function () { }); assert.containsN(form, '.o_data_row', 2, 'there should be only 2 rows displayed'); - testUtils.dom.clickFirst(form.$('.o_list_record_remove')); - testUtils.dom.clickFirst(form.$('.o_list_record_remove')); + await testUtils.dom.clickFirst(form.$('.o_list_record_remove')); + await testUtils.dom.clickFirst(form.$('.o_list_record_remove')); assert.containsOnce(form, '.o_data_row', 'there should be just one remaining row'); @@ -4940,7 +4934,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('onchange many2many in one2many list editable', function (assert) { + QUnit.test('onchange many2many in one2many list editable', async function (assert) { assert.expect(14); this.data.product.records.push({ @@ -4988,7 +4982,7 @@ QUnit.module('fields', {}, function () { turtles: partnerOnchange, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5007,11 +5001,11 @@ QUnit.module('fields', {}, function () { }); // add new line (first, xpad) - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput(form.$('input[name="display_name"]'), 'first'); - testUtils.dom.click(form.$('div[name="product_id"] input')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('input[name="display_name"]'), 'first'); + await testUtils.dom.click(form.$('div[name="product_id"] input')); // the onchange won't be generated - testUtils.dom.click($('li.ui-menu-item a:contains(xpad)').trigger('mouseenter')); + await testUtils.dom.click($('li.ui-menu-item a:contains(xpad)').trigger('mouseenter')); assert.containsOnce(form, '.o_field_many2manytags.o_input', 'should display the line in editable mode'); @@ -5020,7 +5014,7 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('.o_field_many2manytags.o_input .o_badge_text').text(), "first record", 'should display the tag from the onchange'); - testUtils.dom.click(form.$('input.o_field_integer[name="int_field"]')); + await testUtils.dom.click(form.$('input.o_field_integer[name="int_field"]')); assert.strictEqual(form.$('.o_data_cell.o_required_modifier').text(), "xpad", 'should display the product xpad'); @@ -5028,7 +5022,7 @@ QUnit.module('fields', {}, function () { 'should display the tag in readonly'); // enable the many2many onchange and generate it - testUtils.fields.editInput(form.$('input.o_field_integer[name="int_field"]'), '10'); + await testUtils.fields.editInput(form.$('input.o_field_integer[name="int_field"]'), '10'); assert.strictEqual(form.$('.o_data_cell.o_required_modifier').text(), "xenomorphexphone", 'should display the product xphone and xenomorphe'); @@ -5036,21 +5030,21 @@ QUnit.module('fields', {}, function () { 'should display the name, one2many and many2many value'); // disable the many2many onchange - testUtils.fields.editInput(form.$('input.o_field_integer[name="int_field"]'), '0'); + await testUtils.fields.editInput(form.$('input.o_field_integer[name="int_field"]'), '0'); // remove and start over - testUtils.dom.click(form.$('.o_list_record_remove:first button')); - testUtils.dom.click(form.$('.o_list_record_remove:first button')); + await testUtils.dom.click(form.$('.o_list_record_remove:first button')); + await testUtils.dom.click(form.$('.o_list_record_remove:first button')); // enable the many2many onchange - testUtils.fields.editInput(form.$('input.o_field_integer[name="int_field"]'), '10'); + await testUtils.fields.editInput(form.$('input.o_field_integer[name="int_field"]'), '10'); // add new line (first, xenomorphe) - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput(form.$('input[name="display_name"]'), 'first'); - testUtils.dom.click(form.$('div[name="product_id"] input')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('input[name="display_name"]'), 'first'); + await testUtils.dom.click(form.$('div[name="product_id"] input')); // generate the onchange - testUtils.dom.click($('li.ui-menu-item a:contains(xenomorphe)').trigger('mouseenter')); + await testUtils.dom.click($('li.ui-menu-item a:contains(xenomorphe)').trigger('mouseenter')); assert.containsOnce(form, '.o_field_many2manytags.o_input', 'should display the line in editable mode'); @@ -5060,19 +5054,19 @@ QUnit.module('fields', {}, function () { 'should display the tag from the onchange'); // put list in readonly mode - testUtils.dom.click(form.$('input.o_field_integer[name="int_field"]')); + await testUtils.dom.click(form.$('input.o_field_integer[name="int_field"]')); assert.strictEqual(form.$('.o_data_cell.o_required_modifier').text(), "xenomorphexphone", 'should display the product xphone and xenomorphe'); assert.strictEqual(form.$('.o_field_many2manytags:not(.o_input) .o_badge_text').text(), "second recordfirst record", 'should display the tag in readonly (first record and second record)'); - testUtils.fields.editInput(form.$('input.o_field_integer[name="int_field"]'), '10'); + await testUtils.fields.editInput(form.$('input.o_field_integer[name="int_field"]'), '10'); assert.strictEqual(form.$('.o_data_row').text().replace(/\s+/g, ' '), "firstxenomorphe second record new linexphone first record ", 'should display the name, one2many and many2many value'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_data_row').text().replace(/\s+/g, ' '), "firstxenomorphe second record new linexphone first record ", 'should display the name, one2many and many2many value after save'); @@ -5080,14 +5074,14 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('load view for x2many in one2many', function (assert) { + QUnit.test('load view for x2many in one2many', async function (assert) { assert.expect(2); this.data.turtle.records[1].product_id = 37; this.data.partner.records[0].turtles = [2, 3]; this.data.partner.records[2].turtles = [1, 3]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5116,7 +5110,7 @@ QUnit.module('fields', {}, function () { assert.containsN(form, '.o_data_row', 2, 'should display the 2 turtles'); - testUtils.dom.click(form.$('.o_data_row:first')); + await testUtils.dom.click(form.$('.o_data_row:first')); assert.strictEqual($('.modal .o_field_widget[name="partner_ids"] .o_list_view').length, 1, 'should display many2many list view in the modal'); @@ -5124,12 +5118,12 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many (who contains a one2many) with tree view and without form view', function (assert) { + QUnit.test('one2many (who contains a one2many) with tree view and without form view', async function (assert) { assert.expect(1); // avoid error in _postprocess - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5148,7 +5142,7 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(form.$('.o_data_row:first')); + await testUtils.dom.click(form.$('.o_data_row:first')); assert.strictEqual($('.modal .o_field_widget[name="turtle_foo"]').text(), 'blip', 'should open the modal and display the form field'); @@ -5156,7 +5150,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many with x2many in form view (but not in list view)', function (assert) { + QUnit.test('one2many with x2many in form view (but not in list view)', async function (assert) { assert.expect(1); // avoid error when saving the edited related record (because the @@ -5165,7 +5159,7 @@ QUnit.module('fields', {}, function () { this.data.turtle.fields.o2m = { string: "o2m", type: "one2many", relation: 'user' }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5197,26 +5191,26 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(form.$('.o_data_row:first')); // edit first record + await testUtils.dom.click(form.$('.o_data_row:first')); // edit first record - testUtils.fields.many2one.clickOpenDropdown('partner_ids'); - testUtils.fields.many2one.clickHighlightedItem('partner_ids'); + await testUtils.fields.many2one.clickOpenDropdown('partner_ids'); + await testUtils.fields.many2one.clickHighlightedItem('partner_ids'); // add a many2many tag and save - testUtils.dom.click($('.modal .o_field_many2manytags input')); - testUtils.fields.editInput($('.modal .o_field_many2manytags input'), 'test'); - testUtils.dom.click($('.modal .modal-footer .btn-primary')); // save + await testUtils.dom.click($('.modal .o_field_many2manytags input')); + await testUtils.fields.editInput($('.modal .o_field_many2manytags input'), 'test'); + await testUtils.dom.click($('.modal .modal-footer .btn-primary')); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('many2many list in a one2many opened by a many2one', function (assert) { + QUnit.test('many2many list in a one2many opened by a many2one', async function (assert) { assert.expect(1); this.data.turtle.records[1].turtle_trululu = 2; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5243,7 +5237,7 @@ QUnit.module('fields', {}, function () { }, mockRPC: function (route, args) { if (route === '/web/dataset/call_kw/partner/get_formview_id') { - return $.when(false); + return Promise.resolve(false); } if (args.method === 'write') { assert.deepEqual(args.args[1].timmy, [[6, false, [12]]], @@ -5254,27 +5248,27 @@ QUnit.module('fields', {}, function () { }); // edit the first partner in the one2many partner form view - testUtils.dom.click(form.$('.o_data_row:first td.o_data_cell')); + await testUtils.dom.click(form.$('.o_data_row:first td.o_data_cell')); // open form view for many2one - testUtils.dom.click(form.$('.o_external_button')); + await testUtils.dom.click(form.$('.o_external_button')); // click on add, to add a new partner in the m2m - testUtils.dom.click($('.modal .o_field_x2many_list_row_add a')); + await testUtils.dom.click($('.modal .o_field_x2many_list_row_add a')); // select the partner_type 'gold' (this closes the 2nd modal) - testUtils.dom.click($('.modal td:contains(gold)')); + await testUtils.dom.click($('.modal td:contains(gold)')); // confirm the changes in the modal - testUtils.dom.click($('.modal .modal-footer .btn-primary')); + await testUtils.dom.click($('.modal .modal-footer .btn-primary')); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('nested x2many default values', function (assert) { + QUnit.test('nested x2many default values', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5287,7 +5281,7 @@ QUnit.module('fields', {}, function () { '</form>', mockRPC: function (route, args) { if (args.model === 'partner' && args.method === 'default_get') { - return $.when({ + return Promise.resolve({ turtles: [ [0, 0, { partner_ids: [[6, 0, [4]]] }], [0, 0, { partner_ids: [[6, 0, [1]]] }], @@ -5308,12 +5302,12 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many (who contains display_name) with tree view and without form view', function (assert) { + QUnit.test('one2many (who contains display_name) with tree view and without form view', async function (assert) { assert.expect(1); // avoid error in _fetchX2Manys - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5332,7 +5326,7 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(form.$('.o_data_row:first')); + await testUtils.dom.click(form.$('.o_data_row:first')); assert.strictEqual($('.modal .o_field_widget[name="turtle_foo"]').text(), 'blip', 'should open the modal and display the form field'); @@ -5340,10 +5334,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many field with virtual ids', function (assert) { + QUnit.test('one2many field with virtual ids', async function (assert) { assert.expect(11); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5387,13 +5381,13 @@ QUnit.module('fields', {}, function () { "should not have kanban records yet"); // // switch to edit mode and create a new kanban record - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_widget .o-kanban-button-new')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_widget .o-kanban-button-new')); // save & close the modal assert.strictEqual($('.modal-content input.o_field_widget').val(), 'My little Foo Value', "should already have the default value for field foo"); - testUtils.dom.click($('.modal-content .btn-primary').first()); + await testUtils.dom.click($('.modal-content .btn-primary').first()); assert.containsOnce(form, '.o_field_widget .o_kanban_view', "should have one inner kanban view for the one2many field"); @@ -5405,7 +5399,7 @@ QUnit.module('fields', {}, function () { 'My little Foo Value', "should have a value for the foo field"); // save the view to force a create of the new record in the one2many - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.containsOnce(form, '.o_field_widget .o_kanban_view', "should have one inner kanban view for the one2many field"); assert.strictEqual(form.$('.o_field_widget .o_kanban_view .o_kanban_record:not(.o_kanban_ghost)').length, 1, @@ -5418,10 +5412,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('focusing fields in one2many list', function (assert) { + QUnit.test('focusing fields in one2many list', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5438,23 +5432,23 @@ QUnit.module('fields', {}, function () { '</form>', res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_data_row:first td:first')); + await testUtils.dom.click(form.$('.o_data_row:first td:first')); assert.strictEqual(form.$('input[name="turtle_foo"]')[0], document.activeElement, "turtle foo field should have focus"); - form.$('input[name="turtle_foo"]').trigger({ type: 'keydown', which: $.ui.keyCode.TAB }); + await testUtils.fields.triggerKeydown(form.$('input[name="turtle_foo"]'), 'tab'); assert.strictEqual(form.$('input[name="turtle_int"]')[0], document.activeElement, "turtle int field should have focus"); form.destroy(); }); - QUnit.test('one2many list editable = top', function (assert) { + QUnit.test('one2many list editable = top', async function (assert) { assert.expect(6); this.data.turtle.fields.turtle_foo.default = "default foo turtle"; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5479,12 +5473,12 @@ QUnit.module('fields', {}, function () { return this._super.apply(this, arguments); }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, '.o_data_row', "should start with one data row"); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.containsN(form, '.o_data_row', 2, "should have 2 data rows"); @@ -5493,15 +5487,15 @@ QUnit.module('fields', {}, function () { assert.hasClass(form.$('tr.o_data_row:first'),'o_selected_row', "first row should be selected"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('one2many list editable = bottom', function (assert) { + QUnit.test('one2many list editable = bottom', async function (assert) { assert.expect(6); this.data.turtle.fields.turtle_foo.default = "default foo turtle"; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5526,12 +5520,12 @@ QUnit.module('fields', {}, function () { return this._super.apply(this, arguments); }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, '.o_data_row', "should start with one data row"); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.containsN(form, '.o_data_row', 2, "should have 2 data rows"); @@ -5540,14 +5534,14 @@ QUnit.module('fields', {}, function () { assert.hasClass(form.$('tr.o_data_row:eq(1)'),'o_selected_row', "second row should be selected"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('x2many fields use their "mode" attribute', function (assert) { + QUnit.test('x2many fields use their "mode" attribute', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5578,8 +5572,8 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many list editable, no onchange when required field is not set', function (assert) { - assert.expect(7); + QUnit.test('one2many list editable, no onchange when required field is not set', async function (assert) { + assert.expect(8); this.data.turtle.fields.turtle_foo.required = true; this.data.partner.onchanges = { @@ -5590,7 +5584,7 @@ QUnit.module('fields', {}, function () { this.data.partner.records[0].int_field = 0; this.data.partner.records[0].turtles = []; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5609,28 +5603,29 @@ QUnit.module('fields', {}, function () { }, res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "0", "int_field should start with value 0"); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "0", "int_field should still be 0 (no onchange should have been done yet"); assert.verifySteps(['read', 'default_get'], "no onchange should have been applied"); - testUtils.fields.editInput(form.$('.o_field_widget[name="turtle_foo"]'), "some text"); + await testUtils.fields.editInput(form.$('.o_field_widget[name="turtle_foo"]'), "some text"); + assert.verifySteps(['onchange']); assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "1", "int_field should now be 1 (the onchange should have been done"); form.destroy(); }); - QUnit.test('one2many list editable: trigger onchange when row is valid', function (assert) { + QUnit.test('one2many list editable: trigger onchange when row is valid', async function (assert) { // should omit require fields that aren't in the view as they (obviously) // have no value, when checking the validity of required fields // shouldn't consider numerical fields with value 0 as unset - assert.expect(12); + assert.expect(13); this.data.turtle.fields.turtle_foo.required = true; this.data.turtle.fields.turtle_qux.required = true; // required field not in the view @@ -5647,7 +5642,7 @@ QUnit.module('fields', {}, function () { this.data.partner.records[0].int_field = 0; this.data.partner.records[0].turtles = []; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5670,33 +5665,34 @@ QUnit.module('fields', {}, function () { }, res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "0", "int_field should start with value 0"); // add a new row (which is invalid at first) - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "0", "int_field should still be 0 (no onchange should have been done yet)"); assert.verifySteps(['load_views', 'read', 'default_get'], "no onchange should have been applied"); // fill turtle_foo field - testUtils.fields.editInput(form.$('.o_field_widget[name="turtle_foo"]'), "some text"); + await testUtils.fields.editInput(form.$('.o_field_widget[name="turtle_foo"]'), "some text"); assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "0", "int_field should still be 0 (no onchange should have been done yet)"); - assert.verifySteps(['load_views', 'read', 'default_get'], "no onchange should have been applied"); + assert.verifySteps([], "no onchange should have been applied"); // fill partner_ids field with a tag (all required fields will then be set) - testUtils.fields.many2one.clickOpenDropdown('partner_ids'); - testUtils.fields.many2one.clickHighlightedItem('partner_ids'); + await testUtils.fields.many2one.clickOpenDropdown('partner_ids'); + await testUtils.fields.many2one.clickHighlightedItem('partner_ids'); assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "1", "int_field should now be 1 (the onchange should have been done"); + assert.verifySteps(['name_search', 'read', 'onchange']); form.destroy(); }); - QUnit.test('one2many list editable: \'required\' modifiers is properly working', function (assert) { + QUnit.test('one2many list editable: \'required\' modifiers is properly working', async function (assert) { assert.expect(3); this.data.partner.onchanges = { @@ -5707,7 +5703,7 @@ QUnit.module('fields', {}, function () { this.data.partner.records[0].turtles = []; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5721,18 +5717,18 @@ QUnit.module('fields', {}, function () { '</form>', res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "10", "int_field should start with value 10"); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "10", "int_field should still be 10 (no onchange, because line is not valid)"); // fill turtle_foo field - testUtils.fields.editInput(form.$('.o_field_widget[name="turtle_foo"]'), "some text"); + await testUtils.fields.editInput(form.$('.o_field_widget[name="turtle_foo"]'), "some text"); assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "1", "int_field should be 1 (onchange triggered, because line is now valid)"); @@ -5740,7 +5736,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many list editable: \'required\' modifiers is properly working, part 2', function (assert) { + QUnit.test('one2many list editable: \'required\' modifiers is properly working, part 2', async function (assert) { assert.expect(3); this.data.partner.onchanges = { @@ -5751,7 +5747,7 @@ QUnit.module('fields', {}, function () { this.data.partner.records[0].turtles = []; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5766,18 +5762,18 @@ QUnit.module('fields', {}, function () { '</form>', res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "10", "int_field should start with value 10"); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "10", "int_field should still be 10 (no onchange, because line is not valid)"); // fill turtle_int field - testUtils.fields.editInput(form.$('.o_field_widget[name="turtle_int"]'), "1"); + await testUtils.fields.editInput(form.$('.o_field_widget[name="turtle_int"]'), "1"); assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "1", "int_field should be 1 (onchange triggered, because line is now valid)"); @@ -5785,7 +5781,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many list editable: add new line before onchange returns', function (assert) { + QUnit.test('one2many list editable: add new line before onchange returns', async function (assert) { // If the user adds a new row (with a required field with onchange), selects // a value for that field, then adds another row before the onchange returns, // the editable list must wait for the onchange to return before trying to @@ -5796,8 +5792,8 @@ QUnit.module('fields', {}, function () { turtle_trululu: function () { }, }; - var def; - var form = createView({ + var prom; + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5811,23 +5807,23 @@ QUnit.module('fields', {}, function () { mockRPC: function (route, args) { var result = this._super.apply(this, arguments); if (args.method === 'onchange') { - return $.when(def).then(_.constant(result)); + return Promise.resolve(prom).then(_.constant(result)); } return result; }, }); // add a first line but hold the onchange back - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + prom = testUtils.makeTestPromise(); assert.containsOnce(form, '.o_data_row', "should have created the first row immediately"); - def = $.Deferred(); - testUtils.fields.many2one.clickOpenDropdown('turtle_trululu'); - testUtils.fields.many2one.clickHighlightedItem('turtle_trululu'); + await testUtils.fields.many2one.clickOpenDropdown('turtle_trululu'); + await testUtils.fields.many2one.clickHighlightedItem('turtle_trululu'); // try to add a second line and check that it is correctly waiting // for the onchange to return - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual($('.modal').length, 0, "no modal should be displayed"); assert.strictEqual($('.o_field_invalid').length, 0, "no field should be marked as invalid"); @@ -5836,8 +5832,9 @@ QUnit.module('fields', {}, function () { assert.hasClass(form.$('.o_data_row'),'o_selected_row', "first row should still be in edition"); - // resolve the onchange def - def.resolve(); + // resolve the onchange promise + prom.resolve(); + await testUtils.nextTick(); assert.containsN(form, '.o_data_row', 2, "second row should now have been created"); assert.doesNotHaveClass(form.$('.o_data_row:first'), 'o_selected_row', @@ -5846,15 +5843,15 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('editable list: multiple clicks on Add an item do not create invalid rows', function (assert) { + QUnit.test('editable list: multiple clicks on Add an item do not create invalid rows', async function (assert) { assert.expect(3); this.data.turtle.onchanges = { turtle_trululu: function () { }, }; - var def; - var form = createView({ + var prom; + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5868,21 +5865,21 @@ QUnit.module('fields', {}, function () { mockRPC: function (route, args) { var result = this._super.apply(this, arguments); if (args.method === 'onchange') { - return $.when(def).then(_.constant(result)); + return Promise.resolve(prom).then(_.constant(result)); } return result; }, }); - + prom = testUtils.makeTestPromise(); // click twice to add a new line - def = $.Deferred(); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.containsNone(form, '.o_data_row', "no row should have been created yet (waiting for the onchange)"); - // resolve the onchange def - def.resolve(); + // resolve the onchange promise + prom.resolve(); + await testUtils.nextTick(); assert.containsOnce(form, '.o_data_row', "only one row should have been created"); assert.hasClass(form.$('.o_data_row:first'),'o_selected_row', @@ -5891,7 +5888,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('editable list: value reset by an onchange', function (assert) { + QUnit.test('editable list: value reset by an onchange', async function (assert) { // this test reproduces a subtle behavior that may occur in a form view: // the user adds a record in a one2many field, and directly clicks on a // datetime field of the form view which has an onchange, which totally @@ -5907,8 +5904,8 @@ QUnit.module('fields', {}, function () { }, }; - var def; - var form = createView({ + var prom; + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5923,22 +5920,23 @@ QUnit.module('fields', {}, function () { mockRPC: function (route, args) { var result = this._super.apply(this, arguments); if (args.method === 'onchange') { - return $.when(def).then(_.constant(result)); + return Promise.resolve(prom).then(_.constant(result)); } return result; }, }); // trigger the two onchanges - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput(form.$('.o_data_row .o_field_widget'), 'a name'); - def = $.Deferred(); - testUtils.dom.click(form.$('.o_datepicker_input')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('.o_data_row .o_field_widget'), 'a name'); + prom = testUtils.makeTestPromise(); + await testUtils.dom.click(form.$('.o_datepicker_input')); var dateTimeVal = fieldUtils.format.datetime(moment(), { timezone: false }); - testUtils.fields.editSelect(form.$('.o_datepicker_input'), dateTimeVal); + await testUtils.fields.editSelect(form.$('.o_datepicker_input'), dateTimeVal); - // resolve the onchange def - def.resolve(); + // resolve the onchange promise + prom.resolve(); + await testUtils.nextTick(); assert.containsOnce(form, '.o_data_row', "should have one record in the o2m"); @@ -5948,14 +5946,14 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('editable list: onchange that returns a warning', function (assert) { + QUnit.test('editable list: onchange that returns a warning', async function (assert) { assert.expect(5); this.data.turtle.onchanges = { display_name: function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5970,7 +5968,7 @@ QUnit.module('fields', {}, function () { mockRPC: function (route, args) { if (args.method === 'onchange') { assert.step(args.method); - return $.when({ + return Promise.resolve({ value: {}, warning: { title: "Warning", @@ -5991,22 +5989,22 @@ QUnit.module('fields', {}, function () { }); // add a line (this should trigger an onchange and a warning) - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); // check if 'Add an item' still works (this should trigger an onchange // and a warning again) - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.verifySteps(['onchange', 'warning', 'onchange', 'warning']); form.destroy(); }); - QUnit.test('editable list: contexts are correctly sent', function (assert) { + QUnit.test('editable list: contexts are correctly sent', async function (assert) { assert.expect(5); this.data.partner.records[0].timmy = [12]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6051,14 +6049,14 @@ QUnit.module('fields', {}, function () { res_id: 1, }); - testUtils.dom.click(form.$('.o_data_cell:first')); - testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'abc'); - testUtils.form.clickSave(form); + await testUtils.dom.click(form.$('.o_data_cell:first')); + await testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'abc'); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('resetting invisible one2manys', function (assert) { + QUnit.test('resetting invisible one2manys', async function (assert) { assert.expect(3); this.data.partner.records[0].turtles = []; @@ -6066,7 +6064,7 @@ QUnit.module('fields', {}, function () { obj.turtles = [[5], [4, 1]]; }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6084,20 +6082,20 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.fields.editInput(form.$('input[name="foo"]'), 'abcd'); + await testUtils.fields.editInput(form.$('input[name="foo"]'), 'abcd'); assert.verifySteps(['read', 'onchange']); form.destroy(); }); - QUnit.test('one2many: onchange that returns unknow field in list, but not in form', function (assert) { + QUnit.test('one2many: onchange that returns unknow field in list, but not in form', async function (assert) { assert.expect(5); this.data.partner.onchanges = { name: function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6115,7 +6113,7 @@ QUnit.module('fields', {}, function () { '</form>', mockRPC: function (route, args) { if (args.method === 'onchange') { - return $.when({ + return Promise.resolve({ value: { p: [[5], [0, 0, { display_name: 'new', timmy: [[5], [4, 12]] }]], }, @@ -6130,7 +6128,7 @@ QUnit.module('fields', {}, function () { assert.containsNone(form, '.o_field_widget[name="timmy"]', "timmy should not be displayed in the list view"); - testUtils.dom.click(form.$('.o_data_row td:first')); + await testUtils.dom.click(form.$('.o_data_row td:first')); assert.strictEqual($('.modal .o_field_many2manytags[name="timmy"]').length, 1, "timmy should be displayed in the form view"); @@ -6142,16 +6140,16 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('onchange and required fields with override in arch', function (assert) { + QUnit.test('onchange and required fields with override in arch', async function (assert) { assert.expect(4); this.data.partner.onchanges = { - turtles: function (obj) { } + turtles: function () { } }; this.data.turtle.fields.turtle_foo.required = true; this.data.partner.records[0].turtles = []; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6169,16 +6167,16 @@ QUnit.module('fields', {}, function () { return this._super.apply(this, arguments); }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // triggers an onchange on partner, because the new record is valid - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.verifySteps(['read', 'default_get', 'onchange']); form.destroy(); }); - QUnit.test('onchange on a one2many containing a one2many', function (assert) { + QUnit.test('onchange on a one2many containing a one2many', async function (assert) { // the purpose of this test is to ensure that the onchange specs are // correctly and recursively computed assert.expect(1); @@ -6187,7 +6185,7 @@ QUnit.module('fields', {}, function () { p: function () { } }; var checkOnchange = false; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6211,16 +6209,16 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.dom.click($('.modal .o_field_x2many_list_row_add a')); - testUtils.fields.editInput($('.modal .o_data_cell input'), 'new record'); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click($('.modal .o_field_x2many_list_row_add a')); + await testUtils.fields.editInput($('.modal .o_data_cell input'), 'new record'); checkOnchange = true; - testUtils.dom.clickFirst($('.modal .modal-footer .btn-primary')); + await testUtils.dom.clickFirst($('.modal .modal-footer .btn-primary')); form.destroy(); }); - QUnit.test('editing tabbed one2many (editable=bottom)', function (assert) { + QUnit.test('editing tabbed one2many (editable=bottom)', async function (assert) { assert.expect(12); this.data.partner.records[0].turtles = []; @@ -6230,7 +6228,7 @@ QUnit.module('fields', {}, function () { this.data.partner.records[0].turtles.push(id); } - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6255,14 +6253,14 @@ QUnit.module('fields', {}, function () { }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.containsN(form, 'tr.o_data_row', 41); assert.hasClass(form.$('tr.o_data_row').last(), 'o_selected_row'); - testUtils.fields.editInput(form.$('.o_data_row input[name="turtle_foo"]'), 'rainbow dash'); - testUtils.form.clickSave(form); + await testUtils.fields.editInput(form.$('.o_data_row input[name="turtle_foo"]'), 'rainbow dash'); + await testUtils.form.clickSave(form); assert.containsN(form, 'tr.o_data_row', 40); @@ -6270,7 +6268,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('editing tabbed one2many (editable=bottom), again...', function (assert) { + QUnit.test('editing tabbed one2many (editable=bottom), again...', async function (assert) { assert.expect(1); this.data.partner.records[0].turtles = []; @@ -6280,7 +6278,7 @@ QUnit.module('fields', {}, function () { this.data.partner.records[0].turtles.push(id); } - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6295,18 +6293,18 @@ QUnit.module('fields', {}, function () { }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput(form.$('.o_data_row input[name="turtle_foo"]'), 'rainbow dash'); - testUtils.dom.click(form.$('.o_x2m_control_panel .o_pager_next')); - testUtils.dom.click(form.$('.o_x2m_control_panel .o_pager_next')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('.o_data_row input[name="turtle_foo"]'), 'rainbow dash'); + await testUtils.dom.click(form.$('.o_x2m_control_panel .o_pager_next')); + await testUtils.dom.click(form.$('.o_x2m_control_panel .o_pager_next')); assert.containsN(form, 'tr.o_data_row', 3, "should have 3 data rows on the current page"); form.destroy(); }); - QUnit.test('editing tabbed one2many (editable=top)', function (assert) { + QUnit.test('editing tabbed one2many (editable=top)', async function (assert) { assert.expect(15); this.data.partner.records[0].turtles = []; @@ -6317,7 +6315,7 @@ QUnit.module('fields', {}, function () { this.data.partner.records[0].turtles.push(id); } - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6342,12 +6340,12 @@ QUnit.module('fields', {}, function () { }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); assert.containsN(form, 'tr.o_data_row', 2); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.containsN(form, 'tr.o_data_row', 3); @@ -6356,8 +6354,8 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('tr.o_data_row input').val(), 'default foo', "selected input should have correct string"); - testUtils.fields.editInput(form.$('.o_data_row input[name="turtle_foo"]'), 'rainbow dash'); - testUtils.form.clickSave(form); + await testUtils.fields.editInput(form.$('.o_data_row input[name="turtle_foo"]'), 'rainbow dash'); + await testUtils.form.clickSave(form); assert.containsN(form, 'tr.o_data_row', 40); @@ -6365,8 +6363,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many field: change value before pending onchange returns', function (assert) { - var done = assert.async(); + QUnit.test('one2many field: change value before pending onchange returns', async function (assert) { assert.expect(2); var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; @@ -6375,8 +6372,8 @@ QUnit.module('fields', {}, function () { this.data.partner.onchanges = { int_field: function () { } }; - var def; - var form = createView({ + var prom; + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6392,42 +6389,41 @@ QUnit.module('fields', {}, function () { var result = this._super.apply(this, arguments); if (args.method === 'onchange') { // delay the onchange RPC - return $.when(def).then(_.constant(result)); + return Promise.resolve(prom).then(_.constant(result)); } return result; }, }); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - def = $.Deferred(); - testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), '44'); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + prom = testUtils.makeTestPromise(); + await testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), '44'); var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); // set trululu before onchange - form.$('.o_field_many2one input').val('first').trigger('keydown').trigger('keyup'); + await testUtils.fields.editAndTrigger(form.$('.o_field_many2one input'), + 'first', ['keydown', 'keyup']); // complete the onchange - def.resolve(); + prom.resolve(); assert.strictEqual(form.$('.o_field_many2one input').val(), 'first', 'should have kept the new value'); - concurrency.delay(0).then(function () { - // check name_search result - assert.strictEqual($dropdown.find('li:not(.o_m2o_dropdown_option)').length, 1, - 'autocomplete should contains 1 suggestion'); + await testUtils.nextTick(); + // check name_search result + assert.strictEqual($dropdown.find('li:not(.o_m2o_dropdown_option)').length, 1, + 'autocomplete should contains 1 suggestion'); - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - form.destroy(); - done(); - }); + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + form.destroy(); }); - QUnit.test('focus is correctly reset after an onchange in an x2many', function (assert) { + QUnit.test('focus is correctly reset after an onchange in an x2many', async function (assert) { assert.expect(2); this.data.partner.onchanges = { int_field: function () { } }; - var def; - var form = createView({ + var prom; + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6445,37 +6441,38 @@ QUnit.module('fields', {}, function () { var result = this._super.apply(this, arguments); if (args.method === 'onchange') { // delay the onchange RPC - return $.when(def).then(_.constant(result)); + return Promise.resolve(prom).then(_.constant(result)); } return result; }, }); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - def = $.Deferred(); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + prom = testUtils.makeTestPromise(); testUtils.fields.editAndTrigger(form.$('.o_field_widget[name=int_field]'), '44', ['input', { type: 'keydown', which: $.ui.keyCode.TAB }]); - def.resolve(); + prom.resolve(); + await testUtils.nextTick(); assert.strictEqual(document.activeElement, form.$('.o_field_widget[name=qux]')[0], "qux field should have the focus"); - testUtils.fields.many2one.clickOpenDropdown('trululu'); - testUtils.fields.many2one.clickHighlightedItem('trululu'); + await testUtils.fields.many2one.clickOpenDropdown('trululu'); + await testUtils.fields.many2one.clickHighlightedItem('trululu'); assert.strictEqual(form.$('.o_field_many2one input').val(), 'first record', "the one2many field should have the expected value"); form.destroy(); }); - QUnit.test('checkbox in an x2many that triggers an onchange', function (assert) { + QUnit.test('checkbox in an x2many that triggers an onchange', async function (assert) { assert.expect(1); this.data.partner.onchanges = { bar: function () { } }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6488,23 +6485,23 @@ QUnit.module('fields', {}, function () { '</form>', }); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.dom.click(form.$('.o_field_widget[name=bar] input')); + await testUtils.dom.click(form.$('.o_field_widget[name=bar] input')); assert.notOk(form.$('.o_field_widget[name=bar] input').prop('checked'), "the checkbox should be unticked"); form.destroy(); }); - QUnit.test('one2many with default value: edit line to make it invalid', function (assert) { + QUnit.test('one2many with default value: edit line to make it invalid', async function (assert) { assert.expect(3); this.data.partner.fields.p.default = [ [0, false, { foo: "coucou", int_field: 5 }], ]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6519,9 +6516,9 @@ QUnit.module('fields', {}, function () { }); // edit the line and enter an invalid value for int_field - testUtils.dom.click(form.$('.o_data_row .o_data_cell:nth(1)')); - testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 'e'); - testUtils.dom.click(form.$el); + await testUtils.dom.click(form.$('.o_data_row .o_data_cell:nth(1)')); + await testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 'e'); + await testUtils.dom.click(form.$el); assert.containsOnce(form, '.o_data_row.o_selected_row', "line should not have been removed and should still be in edition"); @@ -6533,7 +6530,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('default value for nested one2manys (coming from onchange)', function (assert) { + QUnit.test('default value for nested one2manys (coming from onchange)', async function (assert) { assert.expect(3); this.data.partner.onchanges.p = function (obj) { @@ -6543,7 +6540,7 @@ QUnit.module('fields', {}, function () { ]; }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6568,17 +6565,17 @@ QUnit.module('fields', {}, function () { assert.strictEqual(form.$('.o_data_cell').text(), '1 record', "should correctly display the value of the inner o2m"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('display correct value after validation error', function (assert) { + QUnit.test('display correct value after validation error', async function (assert) { assert.expect(4); this.data.partner.onchanges.turtles = function () { }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6597,8 +6594,8 @@ QUnit.module('fields', {}, function () { // we simulate a validation error. In the 'real' web client, // the server error will be used by the session to display // an error dialog. From the point of view of the basic - // model, the deferred is just rejected. - return $.Deferred().reject(); + // model, the promise is just rejected. + return Promise.reject(); } } if (args.method === 'write') { @@ -6615,31 +6612,31 @@ QUnit.module('fields', {}, function () { "initial text should be correct"); // click and edit value to 'foo', which will trigger onchange - testUtils.dom.click(form.$('.o_data_row .o_data_cell:nth(0)')); - testUtils.fields.editInput(form.$('.o_field_widget[name=turtle_foo]'), 'foo'); - testUtils.dom.click(form.$el); + await testUtils.dom.click(form.$('.o_data_row .o_data_cell:nth(0)')); + await testUtils.fields.editInput(form.$('.o_field_widget[name=turtle_foo]'), 'foo'); + await testUtils.dom.click(form.$el); assert.strictEqual(form.$('.o_data_row .o_data_cell:nth(0)').text(), 'foo', "field should have been changed to foo"); // click and edit value to 'pinky', which trigger a failed onchange - testUtils.dom.click(form.$('.o_data_row .o_data_cell:nth(0)')); - testUtils.fields.editInput(form.$('.o_field_widget[name=turtle_foo]'), 'pinky'); - testUtils.dom.click(form.$el); + await testUtils.dom.click(form.$('.o_data_row .o_data_cell:nth(0)')); + await testUtils.fields.editInput(form.$('.o_field_widget[name=turtle_foo]'), 'pinky'); + await testUtils.dom.click(form.$el); assert.strictEqual(form.$('.o_data_row .o_data_cell:nth(0)').text(), 'foo', "turtle_foo text should now be set back to foo"); // we make sure here that when we save, the values are the current // values displayed in the field. - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('propagate context to sub views', function (assert) { + QUnit.test('propagate context to sub views', async function (assert) { assert.expect(5); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6659,14 +6656,14 @@ QUnit.module('fields', {}, function () { }, viewOptions: { context: { flutter: 'shy' } }, }); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput(form.$('input[name="turtle_foo"]'), 'pinky pie'); - testUtils.form.clickSave(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('input[name="turtle_foo"]'), 'pinky pie'); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('onchange on nested one2manys', function (assert) { + QUnit.test('onchange on nested one2manys', async function (assert) { assert.expect(6); this.data.partner.onchanges.display_name = function (obj) { @@ -6681,7 +6678,7 @@ QUnit.module('fields', {}, function () { } }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6715,30 +6712,30 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'trigger onchange'); + await testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'trigger onchange'); assert.strictEqual(form.$('.o_data_cell').text(), 'test', "should have added the new row to the one2many"); // open the new subrecord to check the value of the nested o2m, and to // ensure that it will be saved - testUtils.dom.click(form.$('.o_data_cell:first')); + await testUtils.dom.click(form.$('.o_data_cell:first')); assert.strictEqual($('.modal .o_data_cell').text(), 'test nested', "should have added the new row to the nested one2many"); - testUtils.dom.clickFirst($('.modal .modal-footer .btn-primary')); + await testUtils.dom.clickFirst($('.modal .modal-footer .btn-primary')); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('one2many with multiple pages and sequence field', function (assert) { + QUnit.test('one2many with multiple pages and sequence field', async function (assert) { assert.expect(1); this.data.partner.records[0].turtles = [3, 2, 1]; this.data.partner.onchanges.turtles = function () { }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6754,7 +6751,7 @@ QUnit.module('fields', {}, function () { res_id: 1, mockRPC: function (route, args) { if (args.method === 'onchange') { - return $.when({ + return Promise.resolve({ value: { turtles: [ [5], @@ -6769,19 +6766,19 @@ QUnit.module('fields', {}, function () { mode: 'edit', }, }); - testUtils.dom.click(form.$('.o_list_record_remove:first button')); + await testUtils.dom.click(form.$('.o_list_record_remove:first button')); assert.strictEqual(form.$('.o_data_row').text(), 'from onchange', 'onchange has been properly applied'); form.destroy(); }); - QUnit.test('one2many with multiple pages and sequence field, part2', function (assert) { + QUnit.test('one2many with multiple pages and sequence field, part2', async function (assert) { assert.expect(1); this.data.partner.records[0].turtles = [3, 2, 1]; this.data.partner.onchanges.turtles = function () { }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6797,7 +6794,7 @@ QUnit.module('fields', {}, function () { res_id: 1, mockRPC: function (route, args) { if (args.method === 'onchange') { - return $.when({ + return Promise.resolve({ value: { turtles: [ [5], @@ -6813,20 +6810,20 @@ QUnit.module('fields', {}, function () { mode: 'edit', }, }); - testUtils.dom.click(form.$('.o_list_record_remove:first button')); + await testUtils.dom.click(form.$('.o_list_record_remove:first button')); assert.strictEqual(form.$('.o_data_row').text(), 'from onchange id2from onchange id3', 'onchange has been properly applied'); form.destroy(); }); - QUnit.test('one2many with sequence field, override default_get, bottom when inline', function (assert) { + QUnit.test('one2many with sequence field, override default_get, bottom when inline', async function (assert) { assert.expect(2); this.data.partner.records[0].turtles = [3, 2, 1]; this.data.turtle.fields.turtle_int.default = 10; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6852,22 +6849,22 @@ QUnit.module('fields', {}, function () { // check line is at the correct place var inputText = 'ninja'; - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput(form.$('.o_input[name="turtle_foo"]'), inputText); - testUtils.form.clickSave(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('.o_input[name="turtle_foo"]'), inputText); + await testUtils.form.clickSave(form); assert.strictEqual($('.o_data_cell').text(), "blipyopkawa" + inputText); form.destroy(); }); - QUnit.test('one2many with sequence field, override default_get, top when inline', function (assert) { + QUnit.test('one2many with sequence field, override default_get, top when inline', async function (assert) { assert.expect(2); this.data.partner.records[0].turtles = [3, 2, 1]; this.data.turtle.fields.turtle_int.default = 10; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6893,22 +6890,22 @@ QUnit.module('fields', {}, function () { // check line is at the correct place var inputText = 'ninja'; - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput(form.$('.o_input[name="turtle_foo"]'), inputText); - testUtils.form.clickSave(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('.o_input[name="turtle_foo"]'), inputText); + await testUtils.form.clickSave(form); assert.strictEqual($('.o_data_cell').text(), inputText + "blipyopkawa"); form.destroy(); }); - QUnit.test('one2many with sequence field, override default_get, bottom when popup', function (assert) { + QUnit.test('one2many with sequence field, override default_get, bottom when popup', async function (assert) { assert.expect(3); this.data.partner.records[0].turtles = [3, 2, 1]; this.data.turtle.fields.turtle_int.default = 10; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6939,25 +6936,25 @@ QUnit.module('fields', {}, function () { // check line is at the correct place var inputText = 'ninja'; - testUtils.dom.click($('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput($('.o_input[name="turtle_foo"]'), inputText); - testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); + await testUtils.dom.click($('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput($('.o_input[name="turtle_foo"]'), inputText); + await testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); assert.strictEqual($('.o_data_cell').text(), "blipyopkawa" + inputText); - testUtils.dom.click($('.o_form_button_save')); + await testUtils.dom.click($('.o_form_button_save')); assert.strictEqual($('.o_data_cell').text(), "blipyopkawa" + inputText); form.destroy(); }); - QUnit.test('one2many with sequence field, override default_get, not last page', function (assert) { + QUnit.test('one2many with sequence field, override default_get, not last page', async function (assert) { assert.expect(1); this.data.partner.records[0].turtles = [3, 2, 1]; this.data.turtle.fields.turtle_int.default = 10; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6979,19 +6976,19 @@ QUnit.module('fields', {}, function () { // click add a new line // check turtle_int for new is the current max of the page - testUtils.dom.click($('.o_field_x2many_list_row_add a')); + await testUtils.dom.click($('.o_field_x2many_list_row_add a')); assert.strictEqual($('.modal .o_input[name="turtle_int"]').val(), '10'); form.destroy(); }); - QUnit.test('one2many with sequence field, override default_get, last page', function (assert) { + QUnit.test('one2many with sequence field, override default_get, last page', async function (assert) { assert.expect(1); this.data.partner.records[0].turtles = [3, 2, 1]; this.data.turtle.fields.turtle_int.default = 10; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7013,12 +7010,12 @@ QUnit.module('fields', {}, function () { // click add a new line // check turtle_int for new is the current max of the page +1 - testUtils.dom.click($('.o_field_x2many_list_row_add a')); + await testUtils.dom.click($('.o_field_x2many_list_row_add a')); assert.strictEqual($('.modal .o_input[name="turtle_int"]').val(), '22'); form.destroy(); }); - QUnit.test('one2many with sequence field, fetch name_get from empty list, field text', function (assert) { + QUnit.test('one2many with sequence field, fetch name_get from empty list, field text', async function (assert) { // There was a bug where a RPC would fail because no route was set. // The scenario is: // - create a new parent model, which has a one2many @@ -7043,7 +7040,7 @@ QUnit.module('fields', {}, function () { relation: 'product' }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7067,11 +7064,11 @@ QUnit.module('fields', {}, function () { var inputText1 = 'relax'; var inputText2 = 'max'; - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput(form.$('.o_input[name="turtle_foo"]'), inputText1); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput(form.$('.o_input[name="turtle_foo"]'), inputText2); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('.o_input[name="turtle_foo"]'), inputText1); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('.o_input[name="turtle_foo"]'), inputText2); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual($('.o_data_cell:nth-child(2)').text(), inputText1 + inputText2); @@ -7079,7 +7076,7 @@ QUnit.module('fields', {}, function () { assert.equal($handles.length, 3, 'There should be 3 sequence handlers'); - testUtils.dom.dragAndDrop($handles.eq(1), + await testUtils.dom.dragAndDrop($handles.eq(1), form.$('tbody tr').first(), { position: 'top' } ); @@ -7089,7 +7086,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many with several pages, onchange and default order', function (assert) { + QUnit.test('one2many with several pages, onchange and default order', async function (assert) { // This test reproduces a specific scenario where a one2many is displayed // over several pages, and has a default order such that a record that // would normally be on page 1 is actually on another page. Moreover, @@ -7117,7 +7114,7 @@ QUnit.module('fields', {}, function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: data, @@ -7145,9 +7142,9 @@ QUnit.module('fields', {}, function () { "should display two records out of three, in the correct order"); // edit turtle_int field of first row - testUtils.dom.click(form.$('.o_data_cell:first')); - testUtils.fields.editInput(form.$('.o_field_widget[name=turtle_int]'), 3); - testUtils.dom.click(form.$el); + await testUtils.dom.click(form.$('.o_data_cell:first')); + await testUtils.fields.editInput(form.$('.o_field_widget[name=turtle_int]'), 3); + await testUtils.dom.click(form.$el); assert.strictEqual(form.$('.o_data_cell.foo').text(), 'blipkawa', "should still display the same two records"); @@ -7163,10 +7160,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('new record, with one2many with more default values than limit', function (assert) { + QUnit.test('new record, with one2many with more default values than limit', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7184,14 +7181,14 @@ QUnit.module('fields', {}, function () { }); assert.strictEqual(form.$('.o_data_row').text(), 'yopblip', 'data has been properly loaded'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_data_row').text(), 'yopblip', 'data has been properly saved'); form.destroy(); }); - QUnit.test('add a new line after limit is reached should behave nicely', function (assert) { + QUnit.test('add a new line after limit is reached should behave nicely', async function (assert) { assert.expect(2); this.data.partner.records[0].turtles = [1, 2, 3]; @@ -7208,7 +7205,7 @@ QUnit.module('fields', {}, function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7225,16 +7222,16 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.containsN(form, '.o_data_row', 4, 'should have 4 data rows'); - testUtils.fields.editInput(form.$('.o_input[name="turtle_foo"]'), 'a'); + await testUtils.fields.editInput(form.$('.o_input[name="turtle_foo"]'), 'a'); assert.containsN(form, '.o_data_row', 4, 'should still have 4 data rows (the limit is increased to 4)'); form.destroy(); }); - QUnit.test('onchange in a one2many with non inline view on an existing record', function (assert) { + QUnit.test('onchange in a one2many with non inline view on an existing record', async function (assert) { assert.expect(6); this.data.partner.fields.sequence = { string: 'Sequence', type: 'integer' }; @@ -7245,7 +7242,7 @@ QUnit.module('fields', {}, function () { this.data.partner_type.fields.partner_ids = { string: "Partner", type: "one2many", relation: 'partner' }; this.data.partner_type.records[0].partner_ids = [1, 2]; - var form = testUtils.createView({ + var form = await createView({ View: FormView, model: 'partner_type', data: this.data, @@ -7265,13 +7262,13 @@ QUnit.module('fields', {}, function () { }); // swap 2 lines in the one2many - testUtils.dom.dragAndDrop(form.$('.ui-sortable-handle:eq(1)'), form.$('tbody tr').first(), + await testUtils.dom.dragAndDrop(form.$('.ui-sortable-handle:eq(1)'), form.$('tbody tr').first(), { position: 'top' }); assert.verifySteps(['load_views', 'read', 'read', 'onchange', 'onchange']); form.destroy(); }); - QUnit.test('onchange in a one2many with non inline view on a new record', function (assert) { + QUnit.test('onchange in a one2many with non inline view on a new record', async function (assert) { assert.expect(8); this.data.turtle.onchanges = { @@ -7282,7 +7279,7 @@ QUnit.module('fields', {}, function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7300,8 +7297,8 @@ QUnit.module('fields', {}, function () { }); // add a row and trigger the onchange - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput(form.$('.o_data_row .o_field_widget[name=display_name]'), 'a name'); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('.o_data_row .o_field_widget[name=display_name]'), 'a name'); assert.strictEqual(form.$('.o_data_row .o_field_widget[name=turtle_int]').val(), "44", "should have triggered the onchange"); @@ -7318,10 +7315,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('add a line, edit it and "Save & New"', function (assert) { + QUnit.test('add a line, edit it and "Save & New"', async function (assert) { assert.expect(5); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7337,27 +7334,27 @@ QUnit.module('fields', {}, function () { "there should be no record in the relation"); // add a new record - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput($('.modal .o_field_widget'), 'new record'); - testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput($('.modal .o_field_widget'), 'new record'); + await testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); assert.strictEqual(form.$('.o_data_row .o_data_cell').text(), 'new record', "should display the new record"); // reopen freshly added record and edit it - testUtils.dom.click(form.$('.o_data_row .o_data_cell')); - testUtils.fields.editInput($('.modal .o_field_widget'), 'new record edited'); + await testUtils.dom.click(form.$('.o_data_row .o_data_cell')); + await testUtils.fields.editInput($('.modal .o_field_widget'), 'new record edited'); // save it, and choose to directly create another record - testUtils.dom.click($('.modal .modal-footer .btn-primary:nth(1)')); + await testUtils.dom.click($('.modal .modal-footer .btn-primary:nth(1)')); assert.strictEqual($('.modal').length, 1, "the model should still be open"); assert.strictEqual($('.modal .o_field_widget').text(), '', "should have cleared the input"); - testUtils.fields.editInput($('.modal .o_field_widget'), 'another new record'); - testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); + await testUtils.fields.editInput($('.modal .o_field_widget'), 'another new record'); + await testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); assert.strictEqual(form.$('.o_data_row .o_data_cell').text(), 'new record editedanother new record', "should display the two records"); @@ -7365,10 +7362,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('o2m add a line custom control create editable', function (assert) { + QUnit.test('o2m add a line custom control create editable', async function (assert) { assert.expect(5); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7402,25 +7399,25 @@ QUnit.module('fields', {}, function () { // click add food // check it's empty - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a:eq(0)')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a:eq(0)')); assert.strictEqual($('.o_data_cell').text(), ""); // click add pizza // save the modal // check it's pizza - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a:eq(1)')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a:eq(1)')); // click add pasta - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a:eq(2)')); - testUtils.form.clickSave(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a:eq(2)')); + await testUtils.form.clickSave(form); assert.strictEqual($('.o_data_cell').text(), "pizzapasta"); form.destroy(); }); - QUnit.test('o2m add a line custom control create non-editable', function (assert) { + QUnit.test('o2m add a line custom control create non-editable', async function (assert) { assert.expect(6); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7454,31 +7451,31 @@ QUnit.module('fields', {}, function () { // click add food // check it's empty - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a:eq(0)')); - testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a:eq(0)')); + await testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); assert.strictEqual($('.o_data_cell').text(), ""); // click add pizza // save the modal // check it's pizza - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a:eq(1)')); - testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a:eq(1)')); + await testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); assert.strictEqual($('.o_data_cell').text(), "pizza"); // click add pasta // save the whole record // check it's pizzapasta - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a:eq(2)')); - testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a:eq(2)')); + await testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); assert.strictEqual($('.o_data_cell').text(), "pizzapasta"); form.destroy(); }); - QUnit.test('o2m add a line custom control create align with handle', function (assert) { + QUnit.test('o2m add a line custom control create align with handle', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7501,7 +7498,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many form view with action button', function (assert) { + QUnit.test('one2many form view with action button', async function (assert) { // once the action button is clicked, the record is reloaded (via the // on_close handler, executed because the python method does not return // any action, or an ir.action.act_window_close) ; this test ensures that @@ -7512,7 +7509,7 @@ QUnit.module('fields', {}, function () { var data = this.data; data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: data, @@ -7547,21 +7544,21 @@ QUnit.module('fields', {}, function () { "initial display_name of o2m record should be correct"); // open one2many record in form view - testUtils.dom.click(form.$('.o_data_cell:first')); + await testUtils.dom.click(form.$('.o_data_cell:first')); assert.strictEqual($('.modal .o_form_view').length, 1, "should have opened the form view in a dialog"); assert.strictEqual($('.modal .o_form_view .o_data_row').length, 0, "there should be no record in the many2many"); // click on the action button - testUtils.dom.click($('.modal .o_form_view button')); + await testUtils.dom.click($('.modal .o_form_view button')); assert.strictEqual($('.modal .o_data_row').length, 1, "fields in the o2m form view should have been read"); assert.strictEqual($('.modal .o_data_cell').text(), 'gold', "many2many subrecord should have been fetched"); // save the dialog - testUtils.dom.click($('.modal .modal-footer .btn-primary')); + await testUtils.dom.click($('.modal .modal-footer .btn-primary')); assert.strictEqual(form.$('.o_data_cell').text(), 'new name', "fields in the o2m list view should have been read as well"); @@ -7569,7 +7566,7 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('onchange affecting inline unopened list view', function (assert) { + QUnit.test('onchange affecting inline unopened list view', async function (assert) { // when we got onchange result for fields of record that were not // already available because they were in a inline view not already // opened, in a given configuration the change were applied ignoring @@ -7605,7 +7602,7 @@ QUnit.module('fields', {}, function () { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'user', data: this.data, @@ -7627,42 +7624,42 @@ QUnit.module('fields', {}, function () { }); // add a turtle on second partner - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_data_row:eq(1)')); - testUtils.dom.click($('.modal .o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_data_row:eq(1)')); + await testUtils.dom.click($('.modal .o_field_x2many_list_row_add a')); $('.modal input[name="display_name"]').val('michelangelo').change(); - testUtils.dom.click($('.modal .btn-primary')); + await testUtils.dom.click($('.modal .btn-primary')); // open first partner so changes from previous action are applied - testUtils.dom.click(form.$('.o_data_row:eq(0)')); - testUtils.dom.click($('.modal .btn-primary')); - testUtils.form.clickSave(form); + await testUtils.dom.click(form.$('.o_data_row:eq(0)')); + await testUtils.dom.click($('.modal .btn-primary')); + await testUtils.form.clickSave(form); assert.strictEqual(numUserOnchange, 2, 'there should 2 and only 2 onchange from closing the partner modal'); - testUtils.dom.click(form.$('.o_data_row:eq(0)')); + await testUtils.dom.click(form.$('.o_data_row:eq(0)')); assert.strictEqual($('.modal .o_data_row').length, 1, 'only 1 turtle for first partner'); assert.strictEqual($('.modal .o_data_row').text(), 'donatello', 'first partner turtle is donatello'); - testUtils.dom.click($('.modal .o_form_button_cancel')); + await testUtils.dom.click($('.modal .o_form_button_cancel')); - testUtils.dom.click(form.$('.o_data_row:eq(1)')); + await testUtils.dom.click(form.$('.o_data_row:eq(1)')); assert.strictEqual($('.modal .o_data_row').length, 1, 'only 1 turtle for second partner'); assert.strictEqual($('.modal .o_data_row').text(), 'michelangelo', 'second partner turtle is michelangelo'); - testUtils.dom.click($('.modal .o_form_button_cancel')); + await testUtils.dom.click($('.modal .o_form_button_cancel')); form.destroy(); }); - QUnit.test('click on URL should not open the record', function (assert) { + QUnit.test('click on URL should not open the record', async function (assert) { assert.expect(2); this.data.partner.records[0].turtles = [1]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7678,20 +7675,20 @@ QUnit.module('fields', {}, function () { res_id: 1, }); - testUtils.dom.click(form.$('.o_email_cell a')); + await testUtils.dom.click(form.$('.o_email_cell a')); assert.strictEqual($('.modal .o_form_view').length, 0, 'click should not open the modal'); - testUtils.dom.click(form.$('.o_url_cell a')); + await testUtils.dom.click(form.$('.o_url_cell a')); assert.strictEqual($('.modal .o_form_view').length, 0, 'click should not open the modal'); form.destroy(); }); - QUnit.test('create and edit on m2o in o2m, and press ESCAPE', function (assert) { + QUnit.test('create and edit on m2o in o2m, and press ESCAPE', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7707,21 +7704,18 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.containsOnce(form, '.o_selected_row', "should have create a new row in edition"); - testUtils.fields.many2one.clickOpenDropdown('turtle_trululu'); - testUtils.fields.many2one.clickItem('turtle_trululu','Create and Edit...'); + await testUtils.fields.many2one.clickOpenDropdown('turtle_trululu'); + await testUtils.fields.many2one.clickItem('turtle_trululu','Create and Edit...'); assert.strictEqual($('.modal .o_form_view').length, 1, "should have opened a form view in a dialog"); - $('.modal .o_form_view .o_field_widget[name=display_name]').trigger($.Event('keydown', { - which: $.ui.keyCode.ESCAPE, - keyCode: $.ui.keyCode.ESCAPE, - })); + await testUtils.fields.triggerKeydown($('.modal .o_form_view .o_field_widget[name=display_name]'), 'escape'); assert.strictEqual($('.modal .o_form_view').length, 0, "should have closed the dialog"); @@ -7731,13 +7725,13 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many add a line should not crash if orderedResIDs is not set', function (assert) { + QUnit.test('one2many add a line should not crash if orderedResIDs is not set', async function (assert) { // There is no assertion, the code will just crash before the bugfix. assert.expect(0); this.data.partner.records[0].turtles = []; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7761,16 +7755,16 @@ QUnit.module('fields', {}, function () { }, }); - testUtils.dom.click($('button[name="action_invoice_open"]')); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click($('button[name="action_invoice_open"]')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); form.destroy(); }); - QUnit.test('one2many shortcut tab should not crash when there is no input widget', function (assert) { + QUnit.test('one2many shortcut tab should not crash when there is no input widget', async function (assert) { assert.expect(2); // create a one2many view which has no input (only 1 textarea in this case) - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7788,12 +7782,9 @@ QUnit.module('fields', {}, function () { }); // add a row, fill it, then trigger the tab shortcut - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editAndTrigger( - form.$('.o_input[name="turtle_foo"]'), - 'ninja', - ['input', $.Event('keydown', {which: $.ui.keyCode.TAB, keyCode: $.ui.keyCode.TAB,})] - ); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('.o_input[name="turtle_foo"]'), 'ninja'); + await testUtils.fields.triggerKeydown(form.$('.o_input[name="turtle_foo"]'), 'tab'); assert.strictEqual(form.$('.o_field_text').text(), 'blipninja', 'current line should be saved'); @@ -7803,15 +7794,15 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many with onchange, required field, shortcut enter', function (assert) { + QUnit.test('one2many with onchange, required field, shortcut enter', async function (assert) { assert.expect(5); this.data.turtle.onchanges = { turtle_foo: function () { }, }; - var def; - var form = createView({ + var prom; + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7825,7 +7816,7 @@ QUnit.module('fields', {}, function () { mockRPC: function (route, args) { var result = this._super.apply(this, arguments); if (args.method === 'onchange') { - return $.when(def).then(_.constant(result)); + return Promise.resolve(prom).then(_.constant(result)); } return result; }, @@ -7837,20 +7828,15 @@ QUnit.module('fields', {}, function () { var value = "hello"; // add a new line - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); // we want to add a delay to simulate an onchange - def = $.Deferred(); + prom = testUtils.makeTestPromise(); // write something in the field var $input = form.$('input[name="turtle_foo"]'); - testUtils.fields.editInput($input, value); - - // trigger keydown ENTER - $input.trigger($.Event('keydown', { - keyCode: $.ui.keyCode.ENTER, - which: $.ui.keyCode.ENTER, - })); + await testUtils.fields.editInput($input, value); + await testUtils.fields.triggerKeydown($input, 'enter'); // check that nothing changed before the onchange finished assert.strictEqual($input.val(), value, "input content shouldn't change"); @@ -7858,7 +7844,8 @@ QUnit.module('fields', {}, function () { "should still contain only one row"); // unlock onchange - def.resolve(); + prom.resolve(); + await testUtils.nextTick(); // check the current line is added with the correct content and a new line is editable assert.strictEqual(form.$('td.o_data_cell').text(), value); @@ -7869,13 +7856,13 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('no deadlock when leaving a one2many line with uncommitted changes', function (assert) { + QUnit.test('no deadlock when leaving a one2many line with uncommitted changes', async function (assert) { // Before unselecting a o2m line, field widgets are asked to commit their changes (new values // that they wouldn't have sent to the model yet). This test is added alongside a bug fix // ensuring that we don't end up in a deadlock when a widget actually has some changes to // commit at that moment. assert.expect(9); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7895,13 +7882,13 @@ QUnit.module('fields', {}, function () { fieldDebounce: 5000, }); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput(form.$('.o_field_widget[name=turtles] input'), 'some foo value'); + await testUtils.fields.editInput(form.$('.o_field_widget[name=turtles] input'), 'some foo value'); // click to add a second row to unselect the current one, then save - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.form.clickSave(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickSave(form); assert.containsOnce(form, '.o_form_readonly', "form view should be in readonly"); @@ -7919,10 +7906,10 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many with extra field from server not in form', function (assert) { + QUnit.test('one2many with extra field from server not in form', async function (assert) { assert.expect(6); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7948,20 +7935,20 @@ QUnit.module('fields', {}, function () { } }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); var x2mList = form.$('.o_field_x2many_list[name=p]'); // Add a record in the list - testUtils.dom.click(x2mList.find('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(x2mList.find('.o_field_x2many_list_row_add a')); var modal = $('.modal-lg'); var nameInput = modal.find('input.o_input[name=display_name]'); - testUtils.fields.editInput(nameInput, 'michelangelo'); + await testUtils.fields.editInput(nameInput, 'michelangelo'); // Save the record in the modal (though it is still virtual) - testUtils.dom.click(modal.find('.btn-primary').first()); + await testUtils.dom.click(modal.find('.btn-primary').first()); assert.equal(x2mList.find('.o_data_row').length, 1, 'There should be 1 records in the x2m list'); @@ -7974,7 +7961,7 @@ QUnit.module('fields', {}, function () { 'The display name field should have the right value'); // Save the whole thing - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); x2mList = form.$('.o_field_x2many_list[name=p]'); @@ -7992,11 +7979,11 @@ QUnit.module('fields', {}, function () { form.destroy(); }); - QUnit.test('one2many invisible depends on parent field', function (assert) { + QUnit.test('one2many invisible depends on parent field', async function (assert) { assert.expect(4); this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -8022,21 +8009,22 @@ QUnit.module('fields', {}, function () { }); assert.containsN(form, 'th', 2, "should be 2 columns in the one2many"); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_many2one[name="product_id"] input')); - testUtils.dom.click($('li.ui-menu-item a:contains(xpad)').trigger('mouseenter')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_many2one[name="product_id"] input')); + await testUtils.dom.click($('li.ui-menu-item a:contains(xpad)').trigger('mouseenter')); assert.containsOnce(form, 'th', "should be 1 column when the product_id is set"); - form.$('.o_field_many2one[name="product_id"] input').val('').trigger('keyup'); + await testUtils.fields.editAndTrigger(form.$('.o_field_many2one[name="product_id"] input'), + '', 'keyup'); assert.containsN(form, 'th', 2, "should be 2 columns in the one2many when product_id is not set"); - testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input')); + await testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input')); assert.containsOnce(form, 'th', "should be 1 column after the value change"); form.destroy(); }); - QUnit.test('one2many column visiblity depends on onchange of parent field', function (assert) { + QUnit.test('one2many column visiblity depends on onchange of parent field', async function (assert) { assert.expect(3); this.data.partner.records[0].p = [2]; @@ -8049,7 +8037,7 @@ QUnit.module('fields', {}, function () { } }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -8070,24 +8058,24 @@ QUnit.module('fields', {}, function () { "should be only 1 column ('foo') in the one2many"); assert.containsOnce(form, '.o_list_view .o_data_row', "should contain one row"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // add a new o2m record - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); form.$('.o_field_one2many input:first').focus(); - testUtils.fields.editInput(form.$('.o_field_one2many input:first'), 'New line'); - testUtils.dom.click(form.$el); + await testUtils.fields.editInput(form.$('.o_field_one2many input:first'), 'New line'); + await testUtils.dom.click(form.$el); assert.containsN(form, 'th', 2, "should be 2 columns('foo' + 'int_field')"); form.destroy(); }); - QUnit.test('one2many column_invisible on view not inline', function (assert) { + QUnit.test('one2many column_invisible on view not inline', async function (assert) { assert.expect(4); this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -8114,24 +8102,25 @@ QUnit.module('fields', {}, function () { }); assert.containsN(form, 'th', 2, "should be 2 columns in the one2many"); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_many2one[name="product_id"] input')); - testUtils.dom.click($('li.ui-menu-item a:contains(xpad)').trigger('mouseenter')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_many2one[name="product_id"] input')); + await testUtils.dom.click($('li.ui-menu-item a:contains(xpad)').trigger('mouseenter')); assert.containsOnce(form, 'th', "should be 1 column when the product_id is set"); - form.$('.o_field_many2one[name="product_id"] input').val('').trigger('keyup'); + await testUtils.fields.editAndTrigger(form.$('.o_field_many2one[name="product_id"] input'), + '', 'keyup'); assert.containsN(form, 'th', 2, "should be 2 columns in the one2many when product_id is not set"); - testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input')); + await testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input')); assert.containsOnce(form, 'th', "should be 1 column after the value change"); form.destroy(); }); - QUnit.test('field context is correctly passed to x2m subviews', function (assert) { + QUnit.test('field context is correctly passed to x2m subviews', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, diff --git a/addons/web/static/tests/fields/relational_fields_mobile_tests.js b/addons/web/static/tests/fields/relational_fields_mobile_tests.js index 1f2b2655e02..794a8a89545 100644 --- a/addons/web/static/tests/fields/relational_fields_mobile_tests.js +++ b/addons/web/static/tests/fields/relational_fields_mobile_tests.js @@ -52,11 +52,11 @@ QUnit.module('relational_fields', { QUnit.module('FieldOne2Many'); - QUnit.test('one2many kanban: deletion in mobile', function (assert) { + QUnit.test('one2many kanban: deletion in mobile', async function (assert) { assert.expect(9); this.data.partner.records[0].p = [1, 2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -94,7 +94,7 @@ QUnit.module('relational_fields', { assert.containsNone(form, '.o_field_one2many .o-kanban-button-new', '"Create" button should not be visible in readonly'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, '.o_field_one2many .o-kanban-button-new', '"Create" button should be visible in edit'); @@ -102,16 +102,16 @@ QUnit.module('relational_fields', { "should have 2 records"); // open and delete record - testUtils.dom.click(form.$('.oe_kanban_global_click').first()); + await testUtils.dom.click(form.$('.oe_kanban_global_click').first()); assert.strictEqual($('.modal .modal-footer .o_btn_remove').length, 1, 'there should be a Remove button in the modal footer'); - testUtils.dom.click($('.modal .modal-footer .o_btn_remove')); + await testUtils.dom.click($('.modal .modal-footer .o_btn_remove')); assert.strictEqual($('.o_modal').length, 0, "there shoul be no more modal"); assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 1, 'should contain 1 records'); // save and check that the correct command has been generated - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); }); diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index faf3df6f2e8..af64afbb9e4 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -1,7 +1,6 @@ odoo.define('web.relational_fields_tests', function (require) { "use strict"; -var concurrency = require('web.concurrency'); var FormView = require('web.FormView'); var ListView = require('web.ListView'); var relationalFields = require('web.relational_fields'); @@ -147,10 +146,10 @@ QUnit.module('relational_fields', { }] }, }; - } + }, }, function () { - QUnit.test('search more pager is reset when doing a new search', function (assert) { + QUnit.test('search more pager is reset when doing a new search', async function (assert) { assert.expect(6); for(var i = 10; i < 180; i++) { this.data.partner.records.push({ @@ -159,17 +158,19 @@ QUnit.module('relational_fields', { }); } - function stringToEvent ($element, string) { + async function stringToEvent ($element, string) { for (var i = 0; i < string.length; i++) { var keyAscii = string.charCodeAt(i); $element.val($element.val()+string[i]); $element.trigger($.Event('keyup', {which: keyAscii, keyCode:keyAscii})); + await testUtils.nextTick(); } $element.trigger($.Event('keyup', {which: $.ui.keyCode.ENTER, keyCode:$.ui.keyCode.ENTER})); + await testUtils.nextTick(); } this.data.partner.fields.datetime.searchable = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -187,17 +188,18 @@ QUnit.module('relational_fields', { res_id: 1, }); - form.$buttons.find('.o_form_button_edit').click(); - var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); - form.$('.o_field_many2one input').click(); - $dropdown.find('.o_m2o_dropdown_option:contains(Search)').mouseenter().click(); - $('.modal .o_pager_next').click(); + await testUtils.form.clickEdit(form); + + + await testUtils.fields.many2one.clickOpenDropdown('trululu'); + await testUtils.fields.many2one.clickItem('trululu','Search'); + await testUtils.dom.click($('.modal .o_pager_next')); assert.strictEqual($('.o_pager_limit').text(), "1173", "there should be 173 records"); assert.strictEqual($('.o_pager_value').text(), "181-160", "should display the second page"); assert.strictEqual($('tr.o_data_row').length, 80, "should display 80 record"); - stringToEvent($('.modal .o_searchview_input'), 'first'); + await stringToEvent($('.modal .o_searchview_input'), 'first'); assert.strictEqual($('.o_pager_limit').text(), "11", "there should be 1 record"); assert.strictEqual($('.o_pager_value').text(), "11-1", "should display the first page"); @@ -205,7 +207,7 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('do not call name_get if display_name already known', function (assert) { + QUnit.test('do not call name_get if display_name already known', async function (assert) { // default_get only returns the id for many2one fields // onchange returns an array with the id and the display_name // thus, when an onchange is performed, there is no need to call @@ -219,7 +221,7 @@ QUnit.module('relational_fields', { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -241,7 +243,7 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('x2many default_order multiple fields', function (assert) { + QUnit.test('x2many default_order multiple fields', async function (assert) { assert.expect(7); this.data.partner.records = [ @@ -256,7 +258,7 @@ QUnit.module('relational_fields', { this.data.partner.records[0].p = [1, 7, 4, 5, 2, 6, 3]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -284,7 +286,7 @@ QUnit.module('relational_fields', { }); - QUnit.test('one2many from a model that has been sorted', function (assert) { + QUnit.test('one2many from a model that has been sorted', async function (assert) { assert.expect(1); /* On a standard list view, sort your records by a field @@ -293,7 +295,7 @@ QUnit.module('relational_fields', { */ this.data.partner.records[0].turtles = [3, 2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -319,10 +321,10 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('widget many2many_checkboxes in a subview', function (assert) { + QUnit.test('widget many2many_checkboxes in a subview', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -347,19 +349,19 @@ QUnit.module('relational_fields', { res_id: 1, }); - form.$buttons.find('.o_form_button_edit').click(); - form.$('.o_data_cell').click(); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_data_cell')); // edit the partner_ids field by (un)checking boxes on the widget var $firstCheckbox = $('.modal .custom-control-input').first(); - $firstCheckbox.click(); + await testUtils.dom.click($firstCheckbox); assert.ok($firstCheckbox.prop('checked'), "the checkbox should be ticked"); var $secondCheckbox = $('.modal .custom-control-input').eq(1); - $secondCheckbox.click(); + await testUtils.dom.click($secondCheckbox); assert.notOk($secondCheckbox.prop('checked'), "the checkbox should be unticked"); form.destroy(); }); - QUnit.test('delete a record while adding another one in a multipage', function (assert) { + QUnit.test('delete a record while adding another one in a multipage', async function (assert) { // in a many2one with at least 2 pages, add a new line. Delete the line above it. // (the onchange makes it so that the virtualID is inserted in the middle of the currentResIDs.) // it should load the next line to display it on the page. @@ -370,7 +372,7 @@ QUnit.module('relational_fields', { obj.turtles = [[5]].concat(obj.turtles); }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -389,12 +391,12 @@ QUnit.module('relational_fields', { res_id: 1, }); - form.$buttons.find('.o_form_button_edit').click(); + await testUtils.form.clickEdit(form); // add a line (virtual record) - form.$('.o_field_x2many_list_row_add a').click(); - form.$('.o_input').val('pi').trigger('input'); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('.o_input'), 'pi'); // delete the line above it - form.$('.o_list_record_remove').first().click(); + await testUtils.dom.click(form.$('.o_list_record_remove').first()); // the next line should be displayed below the newly added one assert.strictEqual(form.$('.o_data_row').length, 2, "should have 2 records"); assert.strictEqual(form.$('.o_data_row .o_data_cell:first-child').text(), 'pikawa', @@ -403,7 +405,7 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('one2many, onchange, edition and multipage...', function (assert) { + QUnit.test('one2many, onchange, edition and multipage...', async function (assert) { assert.expect(7); this.data.partner.onchanges = { @@ -414,7 +416,7 @@ QUnit.module('relational_fields', { this.data.partner.records[0].turtles = [1,2,3]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -434,8 +436,8 @@ QUnit.module('relational_fields', { mode: 'edit', }, }); - form.$('.o_field_x2many_list_row_add a').click(); - form.$('.o_field_x2many_list_row_add a').click(); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.verifySteps([ 'read partner', @@ -448,7 +450,7 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('onchange on unloaded record clearing posterious change', function (assert) { + QUnit.test('onchange on unloaded record clearing posterious change', async function (assert) { // when we got onchange result for fields of record that were not // already available because they were in a inline view not already // opened, in a given configuration the change were applied ignoring @@ -480,7 +482,7 @@ QUnit.module('relational_fields', { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'user', data: this.data, @@ -503,47 +505,48 @@ QUnit.module('relational_fields', { }); // open first partner and change turtle name - form.$buttons.find('.o_form_button_edit').click(); - form.$('.o_data_row:eq(0)').click(); - $('.modal .o_data_cell:eq(0)').click(); - $('.modal input[name="display_name"]').val('Donatello').change(); - $('.modal .btn-primary').click(); - - // open second partner and add a turtle - form.$('.o_data_row:eq(1)').click(); - $('.modal .o_field_x2many_list_row_add a').click(); - $('.modal input[name="display_name"]').val('Michelangelo').change(); - $('.modal .btn-primary').click(); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_data_row:eq(0)')); + await testUtils.dom.click($('.modal .o_data_cell:eq(0)')); + await testUtils.fields.editAndTrigger($('.modal input[name="display_name"]'), + 'Donatello', 'change'); + await testUtils.dom.click($('.modal .btn-primary')); + + await testUtils.dom.click(form.$('.o_data_row:eq(1)')); + await testUtils.dom.click($('.modal .o_field_x2many_list_row_add a')); + await testUtils.fields.editAndTrigger($('.modal input[name="display_name"]'), + 'Michelangelo', 'change'); + await testUtils.dom.click($('.modal .btn-primary')); assert.strictEqual(numUserOnchange, 2, 'there should 2 and only 2 onchange from closing the partner modal'); // check first record still has change - form.$('.o_data_row:eq(0)').click(); + await testUtils.dom.click(form.$('.o_data_row:eq(0)')); assert.strictEqual($('.modal .o_data_row').length, 1, 'only 1 turtle for first partner'); assert.strictEqual($('.modal .o_data_row').text(), 'Donatello', 'first partner turtle is Donatello'); - $('.modal .o_form_button_cancel').click(); + await testUtils.dom.click($('.modal .o_form_button_cancel')); // check second record still has changes - form.$('.o_data_row:eq(1)').click(); + await testUtils.dom.click(form.$('.o_data_row:eq(1)')); assert.strictEqual($('.modal .o_data_row').length, 1, 'only 1 turtle for second partner'); assert.strictEqual($('.modal .o_data_row').text(), 'Michelangelo', 'second partner turtle is Michelangelo'); - $('.modal .o_form_button_cancel').click(); + await testUtils.dom.click($('.modal .o_form_button_cancel')); form.destroy(); }); - QUnit.test('quickly switch between pages in one2many list', function (assert) { + QUnit.test('quickly switch between pages in one2many list', async function (assert) { assert.expect(2); this.data.partner.records[0].turtles = [1, 2, 3]; - var readDefs = [$.when(), $.Deferred(), $.Deferred()]; - var form = createView({ + var readDefs = [Promise.resolve(), testUtils.makeTestPromise(), testUtils.makeTestPromise()]; + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -558,34 +561,35 @@ QUnit.module('relational_fields', { var result = this._super.apply(this, arguments); if (args.method === 'read') { var recordID = args.args[0][0]; - return $.when(readDefs[recordID - 1]).then(_.constant(result)); + return Promise.resolve(readDefs[recordID - 1]).then(_.constant(result)); } return result; }, res_id: 1, }); - form.$('.o_field_widget[name=turtles] .o_pager_next').click(); - form.$('.o_field_widget[name=turtles] .o_pager_next').click(); + await testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); + await testUtils.dom.click(form.$('.o_field_widget[name=turtles] .o_pager_next')); readDefs[1].resolve(); - + await testUtils.nextTick(); assert.strictEqual(form.$('.o_field_widget[name=turtles] .o_data_cell').text(), 'donatello'); readDefs[2].resolve(); + await testUtils.nextTick(); assert.strictEqual(form.$('.o_field_widget[name=turtles] .o_data_cell').text(), 'raphael'); form.destroy(); }); - QUnit.test('many2many read, field context is properly sent', function (assert) { + QUnit.test('many2many read, field context is properly sent', async function (assert) { assert.expect(4); this.data.partner.fields.timmy.context = {hello: 'world'}; this.data.partner.records[0].timmy = [12]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -603,18 +607,20 @@ QUnit.module('relational_fields', { assert.verifySteps(['world']); - form.$buttons.find('.o_form_button_edit').click(); + await testUtils.form.clickEdit(form); var $m2mInput = form.$('.o_field_many2manytags input'); $m2mInput.click(); + await testUtils.nextTick(); $m2mInput.autocomplete('widget').find('li:first()').click(); - assert.verifySteps(['world', 'world']); + await testUtils.nextTick(); + assert.verifySteps(['world']); form.destroy(); }); QUnit.module('FieldStatus'); - QUnit.test('static statusbar widget on many2one field', function (assert) { + QUnit.test('static statusbar widget on many2one field', async function (assert) { assert.expect(5); this.data.partner.fields.trululu.domain = "[('bar', '=', True)]"; @@ -622,7 +628,7 @@ QUnit.module('relational_fields', { var count = 0; var nb_fields_fetched; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -651,10 +657,10 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('static statusbar widget on many2one field with domain', function (assert) { + QUnit.test('static statusbar widget on many2one field with domain', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -675,10 +681,10 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('clickable statusbar widget on many2one field', function (assert) { + QUnit.test('clickable statusbar widget on many2one field', async function (assert) { assert.expect(5); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -696,7 +702,7 @@ QUnit.module('relational_fields', { assert.containsN(form, '.o_statusbar_status button.btn-secondary:not(.dropdown-toggle):not(:disabled)', 2); var $clickable = form.$('.o_statusbar_status button.btn-secondary:not(.dropdown-toggle):not(:disabled)'); - testUtils.dom.click($clickable.last()); // (last is visually the first here (css)) + await testUtils.dom.click($clickable.last()); // (last is visually the first here (css)) assert.hasClass(form.$('.o_statusbar_status button[data-value="1"]'), "btn-primary"); assert.hasClass(form.$('.o_statusbar_status button[data-value="1"]'), "disabled"); @@ -704,11 +710,11 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('statusbar with no status', function (assert) { + QUnit.test('statusbar with no status', async function (assert) { assert.expect(2); this.data.product.records = []; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -725,12 +731,12 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('statusbar with domain but no value (create mode)', function (assert) { + QUnit.test('statusbar with domain but no value (create mode)', async function (assert) { assert.expect(1); this.data.partner.fields.trululu.domain = "[('bar', '=', True)]"; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -745,12 +751,12 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('clickable statusbar should change m2o fetching domain in edit mode', function (assert) { + QUnit.test('clickable statusbar should change m2o fetching domain in edit mode', async function (assert) { assert.expect(2); this.data.partner.fields.trululu.domain = "[('bar', '=', True)]"; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -762,20 +768,20 @@ QUnit.module('relational_fields', { config: {device: {isMobile: false}}, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsN(form, '.o_statusbar_status button:not(.dropdown-toggle)', 3); - testUtils.dom.click(form.$('.o_statusbar_status button:not(.dropdown-toggle)').last()); + await testUtils.dom.click(form.$('.o_statusbar_status button:not(.dropdown-toggle)').last()); assert.containsN(form, '.o_statusbar_status button:not(.dropdown-toggle)', 2); form.destroy(); }); - QUnit.test('statusbar fold_field option and statusbar_visible attribute', function (assert) { + QUnit.test('statusbar fold_field option and statusbar_visible attribute', async function (assert) { assert.expect(2); this.data.partner.records[0].bar = false; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -788,7 +794,7 @@ QUnit.module('relational_fields', { config: {device: {isMobile: false}}, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, '.o_statusbar_status:first .dropdown-menu button.disabled'); assert.containsOnce(form, '.o_statusbar_status:last button.disabled'); @@ -796,14 +802,14 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('statusbar with dynamic domain', function (assert) { + QUnit.test('statusbar with dynamic domain', async function (assert) { assert.expect(5); this.data.partner.fields.trululu.domain = "[('int_field', '>', qux)]"; this.data.partner.records[2].int_field = 0; var rpcCount = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -823,14 +829,14 @@ QUnit.module('relational_fields', { config: {device: {isMobile: false}}, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsN(form, '.o_statusbar_status button.disabled', 3); assert.strictEqual(rpcCount, 1, "should have done 1 search_read rpc"); - testUtils.fields.editInput(form.$('input[name=qux]'), 9.5); + await testUtils.fields.editInput(form.$('input[name=qux]'), 9.5); assert.containsN(form, '.o_statusbar_status button.disabled', 2); assert.strictEqual(rpcCount, 2, "should have done 1 more search_read rpc"); - testUtils.fields.editInput(form.$('input[name=qux]'), "hey"); + await testUtils.fields.editInput(form.$('input[name=qux]'), "hey"); assert.strictEqual(rpcCount, 2, "should not have done 1 more search_read rpc"); form.destroy(); @@ -838,14 +844,14 @@ QUnit.module('relational_fields', { QUnit.module('FieldSelection'); - QUnit.test('widget selection in a list view', function (assert) { + QUnit.test('widget selection in a list view', async function (assert) { assert.expect(3); this.data.partner.records.forEach(function (r) { r.color = 'red'; }); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -856,7 +862,7 @@ QUnit.module('relational_fields', { assert.strictEqual(list.$('td:contains(Red)').length, 3, "should have 3 rows with correct value"); - testUtils.dom.click(list.$('td:contains(Red):first')); + await testUtils.dom.click(list.$('td:contains(Red):first')); var $td = list.$('tbody tr.o_selected_row td:not(.o_list_record_selector)'); @@ -865,15 +871,15 @@ QUnit.module('relational_fields', { list.destroy(); }); - QUnit.test('widget selection, edition and on many2one field', function (assert) { - assert.expect(18); + QUnit.test('widget selection, edition and on many2one field', async function (assert) { + assert.expect(19); this.data.partner.onchanges = {product_id: function () {}}; this.data.partner.records[0].product_id = 37; this.data.partner.records[0].trululu = false; var count = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -898,7 +904,7 @@ QUnit.module('relational_fields', { assert.strictEqual(form.$('.o_field_widget[name=color]').text(), 'Red', "should have rendered the selection field correctly"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsN(form.$('.o_form_view'), 'select', 3); assert.containsOnce(form, 'select[name="product_id"] option:contains(xphone)', @@ -909,7 +915,7 @@ QUnit.module('relational_fields', { "should have correct product_id value"); assert.strictEqual(form.$('select[name="trululu"]').val(), "false", "should not have any value in trululu field"); - testUtils.fields.editSelect(form.$('select[name="product_id"]'), 41); + await testUtils.fields.editSelect(form.$('select[name="product_id"]'), 41); assert.strictEqual(form.$('select[name="product_id"]').val(), "41", "should have a value of xphone"); @@ -919,12 +925,14 @@ QUnit.module('relational_fields', { assert.verifySteps(['read', 'name_search', 'name_search', 'onchange']); count = 0; - form.reload(); + await form.reload(); assert.strictEqual(count, 1, "should not reload product_id relation"); + assert.verifySteps(['read']); + form.destroy(); }); - QUnit.test('unset selection field with 0 as key', function (assert) { + QUnit.test('unset selection field with 0 as key', async function (assert) { // The server doesn't make a distinction between false value (the field // is unset), and selection 0, as in that case the value it returns is // false. So the client must convert false to value 0 if it exists. @@ -935,7 +943,7 @@ QUnit.module('relational_fields', { selection: [[0, "Value O"], [1, "Value 1"]], }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -953,7 +961,7 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('unset selection field with string keys', function (assert) { + QUnit.test('unset selection field with string keys', async function (assert) { // The server doesn't make a distinction between false value (the field // is unset), and selection 0, as in that case the value it returns is // false. So the client must convert false to value 0 if it exists. In @@ -965,7 +973,7 @@ QUnit.module('relational_fields', { selection: [['0', "Value O"], ['1', "Value 1"]], }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -983,10 +991,10 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('unset selection on a many2one field', function (assert) { + QUnit.test('unset selection on a many2one field', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1006,18 +1014,18 @@ QUnit.module('relational_fields', { }, }); - testUtils.fields.editSelect(form.$('.o_form_view select'), 'false'); - testUtils.form.clickSave(form); + await testUtils.fields.editSelect(form.$('.o_form_view select'), 'false'); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('field selection with many2ones and special characters', function (assert) { + QUnit.test('field selection with many2ones and special characters', async function (assert) { assert.expect(1); // edit the partner with id=4 this.data.partner.records[2].display_name = '<span>hey</span>'; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1032,7 +1040,7 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('widget selection on a many2one: domain updated by an onchange', function (assert) { + QUnit.test('widget selection on a many2one: domain updated by an onchange', async function (assert) { assert.expect(4); this.data.partner.onchanges = { @@ -1040,7 +1048,7 @@ QUnit.module('relational_fields', { }; var domain = []; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1052,7 +1060,7 @@ QUnit.module('relational_fields', { mockRPC: function (route, args) { if (args.method === 'onchange') { domain = [['id', 'in', [10]]]; - return $.when({ + return Promise.resolve({ domain: { trululu: domain, } @@ -1073,7 +1081,7 @@ QUnit.module('relational_fields', { "should be 4 options in the selection"); // trigger an onchange that will update the domain - testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 2); + await testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 2); assert.containsOnce(form, '.o_field_widget[name=trululu] option', "should be 1 option in the selection"); @@ -1081,7 +1089,7 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('required selection widget should not have blank option', function (assert) { + QUnit.test('required selection widget should not have blank option', async function (assert) { assert.expect(3); this.data.partner.fields.feedback_value = { @@ -1091,7 +1099,7 @@ QUnit.module('relational_fields', { default: 'good', string: 'Good' }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1102,14 +1110,14 @@ QUnit.module('relational_fields', { res_id: 1 }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_widget[name=color]')[0].options.length, 3, "non required selection field must have 3 options 1 blank option and 2 value options"); assert.strictEqual(form.$('.o_field_widget[name=feedback_value]')[0].options.length, 2, "should have only 2 options without blank option"); // change value to update widget modifier values - testUtils.fields.editSelect(form.$('.o_field_widget[name=feedback_value]'), '"bad"'); + await testUtils.fields.editSelect(form.$('.o_field_widget[name=feedback_value]'), '"bad"'); assert.strictEqual(form.$('.o_field_widget[name=color]')[0].options.length, 2, "should have only 2 options"); @@ -1118,11 +1126,11 @@ QUnit.module('relational_fields', { QUnit.module('FieldMany2ManyTags'); - QUnit.test('fieldmany2many tags with and without color', function (assert) { + QUnit.test('fieldmany2many tags with and without color', async function (assert) { assert.expect(5); this.data.partner.fields.partner_ids = {string: "Partner", type: "many2many", relation: 'partner'}; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1141,29 +1149,29 @@ QUnit.module('relational_fields', { }); // add a tag on field partner_ids - testUtils.fields.many2one.clickOpenDropdown('partner_ids'); - testUtils.fields.many2one.clickHighlightedItem('partner_ids'); + await testUtils.fields.many2one.clickOpenDropdown('partner_ids'); + await testUtils.fields.many2one.clickHighlightedItem('partner_ids'); // add a tag on field timmy - testUtils.fields.many2one.clickOpenDropdown('timmy'); + await testUtils.fields.many2one.clickOpenDropdown('timmy'); var $input = form.$('.o_field_many2manytags[name="timmy"] input'); assert.strictEqual($input.autocomplete('widget').find('li').length, 3, - "autocomplete dropdown should have 3 entries (2 values + 'Search and Edit...')"); - testUtils.fields.many2one.clickHighlightedItem('timmy'); + "autocomplete dropdown should have 3 entries (2 values + 'Search and Edit...')"); + await testUtils.fields.many2one.clickHighlightedItem('timmy'); assert.containsOnce(form, '.o_field_many2manytags[name="timmy"] .badge', "should contain 1 tag"); - assert.ok(form.$('.o_field_many2manytags[name="timmy"] .badge:contains("gold")').length, + assert.containsOnce(form, '.o_field_many2manytags[name="timmy"] .badge:contains("gold")', "should contain newly added tag 'gold'"); form.destroy(); }); - QUnit.test('fieldmany2many tags with color: rendering and edition', function (assert) { + QUnit.test('fieldmany2many tags with color: rendering and edition', async function (assert) { assert.expect(28); this.data.partner.records[0].timmy = [12, 14]; this.data.partner_type.records.push({id: 13, display_name: "red", color: 8}); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1194,7 +1202,7 @@ QUnit.module('relational_fields', { assert.strictEqual(form.$('.badge:first()').data('color'), 2, 'should have correctly fetched the color'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsN(form, '.o_field_many2manytags .badge .dropdown-toggle', 2, "should still contain 2 tags in edit mode"); @@ -1205,12 +1213,12 @@ QUnit.module('relational_fields', { // add an other existing tag var $input = form.$('.o_field_many2manytags input'); - testUtils.fields.many2one.clickOpenDropdown('timmy'); + await testUtils.fields.many2one.clickOpenDropdown('timmy'); assert.strictEqual($input.autocomplete('widget').find('li').length, 1, "autocomplete dropdown should have 1 entry"); assert.strictEqual($input.autocomplete('widget').find('li a:contains("red")').length, 1, "autocomplete dropdown should contain 'red'"); - testUtils.fields.many2one.clickHighlightedItem('timmy'); + await testUtils.fields.many2one.clickHighlightedItem('timmy'); assert.containsN(form, '.o_field_many2manytags .badge .dropdown-toggle', 3, "should contain 3 tags"); assert.ok(form.$('.o_field_many2manytags .badge .dropdown-toggle:contains("red")').length, @@ -1219,34 +1227,34 @@ QUnit.module('relational_fields', { "should have fetched the color of added tag"); // remove tag with id 14 - testUtils.dom.click(form.$('.o_field_many2manytags .badge[data-id=14] .o_delete')); + await testUtils.dom.click(form.$('.o_field_many2manytags .badge[data-id=14] .o_delete')); assert.containsN(form, '.o_field_many2manytags .badge .dropdown-toggle', 2, "should contain 2 tags"); assert.ok(!form.$('.o_field_many2manytags .badge .dropdown-toggle:contains("silver")').length, "should not contain tag 'silver' anymore"); // save the record (should do the write RPC with the correct commands) - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); // checkbox 'Hide in Kanban' $input = form.$('.o_field_many2manytags .badge[data-id=13] .dropdown-toggle'); // selects 'red' tag - testUtils.dom.click($input); + await testUtils.dom.click($input); var $checkBox = form.$('.o_field_many2manytags .badge[data-id=13] .custom-checkbox input'); assert.strictEqual($checkBox.length, 1, "should have a checkbox in the colorpicker dropdown menu"); assert.notOk($checkBox.is(':checked'), "should have unticked checkbox in colorpicker dropdown menu"); - testUtils.fields.editAndTrigger($checkBox, null,['mouseenter','mousedown']); + await testUtils.fields.editAndTrigger($checkBox, null,['mouseenter','mousedown']); $input = form.$('.o_field_many2manytags .badge[data-id=13] .dropdown-toggle'); // refresh - testUtils.dom.click($input); + await testUtils.dom.click($input); $checkBox = form.$('.o_field_many2manytags .badge[data-id=13] .custom-checkbox input'); // refresh assert.equal($input.parent().data('color'), "0", "should become transparent when toggling on checkbox"); assert.ok($checkBox.is(':checked'), "should have a ticked checkbox in colorpicker dropdown menu after mousedown"); - testUtils.fields.editAndTrigger($checkBox, null,['mouseenter','mousedown']); + await testUtils.fields.editAndTrigger($checkBox, null,['mouseenter','mousedown']); $input = form.$('.o_field_many2manytags .badge[data-id=13] .dropdown-toggle'); // refresh - testUtils.dom.click($input); + await testUtils.dom.click($input); $checkBox = form.$('.o_field_many2manytags .badge[data-id=13] .custom-checkbox input'); // refresh assert.equal($input.parent().data('color'), "8", "should revert to old color when toggling off checkbox"); assert.notOk($checkBox.is(':checked'), "should have an unticked checkbox in colorpicker dropdown menu after 2nd click"); @@ -1257,14 +1265,14 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('fieldmany2many tags view a domain', function (assert) { + QUnit.test('fieldmany2many tags view a domain', async function (assert) { assert.expect(7); this.data.partner.fields.timmy.domain = [['id', '<', 50]]; this.data.partner.records[0].timmy = [12]; this.data.partner_type.records.push({id: 99, display_name: "red", color: 8}); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1276,7 +1284,7 @@ QUnit.module('relational_fields', { if (args.method === 'name_search') { assert.deepEqual(args.kwargs.args, [['id', '<', 50], ['id', 'not in', [12]]], "domain sent to name_search should be correct"); - return $.when([[14, 'silver']]); + return Promise.resolve([[14, 'silver']]); } return this._super.apply(this, arguments); } @@ -1286,16 +1294,16 @@ QUnit.module('relational_fields', { assert.ok(form.$('.badge:contains(gold)').length, 'should have fetched and rendered gold partner tag'); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // add an other existing tag var $input = form.$('.o_field_many2manytags input'); - testUtils.fields.many2one.clickOpenDropdown('timmy'); + await testUtils.fields.many2one.clickOpenDropdown('timmy'); assert.strictEqual($input.autocomplete('widget').find('li').length, 1, "autocomplete dropdown should have 1 entry"); assert.strictEqual($input.autocomplete('widget').find('li a:contains("silver")').length, 1, "autocomplete dropdown should contain 'silver'"); - testUtils.fields.many2one.clickHighlightedItem('timmy'); + await testUtils.fields.many2one.clickHighlightedItem('timmy'); assert.containsN(form, '.o_field_many2manytags .badge', 2, "should contain 2 tags"); assert.ok(form.$('.o_field_many2manytags .badge:contains("silver")').length, @@ -1304,10 +1312,10 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('fieldmany2many tags in a new record', function (assert) { + QUnit.test('fieldmany2many tags in a new record', async function (assert) { assert.expect(7); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1326,10 +1334,10 @@ QUnit.module('relational_fields', { }); assert.hasClass(form.$('.o_form_view'),'o_form_editable', "form should be in edit mode"); - testUtils.fields.many2one.clickOpenDropdown('timmy'); + await testUtils.fields.many2one.clickOpenDropdown('timmy'); assert.strictEqual(form.$('.o_field_many2manytags input').autocomplete('widget').find('li').length, 3, "autocomplete dropdown should have 3 entries (2 values + 'Search and Edit...')"); - testUtils.fields.many2one.clickHighlightedItem('timmy'); + await testUtils.fields.many2one.clickHighlightedItem('timmy'); assert.containsOnce(form, '.o_field_many2manytags .badge', "should contain 1 tag"); @@ -1337,18 +1345,18 @@ QUnit.module('relational_fields', { "should contain newly added tag 'gold'"); // save the record (should do the write RPC with the correct commands) - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('fieldmany2many tags: update color', function (assert) { + QUnit.test('fieldmany2many tags: update color', async function (assert) { assert.expect(5); this.data.partner.records[0].timmy = [12, 14]; this.data.partner_type.records[0].color = 0; var color; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1371,28 +1379,30 @@ QUnit.module('relational_fields', { // Update the color in readonly color = 1; - testUtils.dom.click(form.$('.badge:first() .dropdown-toggle')); - $('.o_colorpicker a[data-color="' + color + '"]').trigger('mousedown'); // choose color 1 + await testUtils.dom.click(form.$('.badge:first() .dropdown-toggle')); + await testUtils.dom.triggerEvents($('.o_colorpicker a[data-color="' + color + '"]'), ['mousedown']); + await testUtils.nextTick(); assert.strictEqual(form.$('.badge:first()').data('color'), color, 'should have correctly updated the color (in readonly)'); // Update the color in edit color = 6; - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.badge:first() .dropdown-toggle')); - $('.o_colorpicker a[data-color="' + color + '"]').trigger('mousedown'); // choose color 6 + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.badge:first() .dropdown-toggle')); + await testUtils.dom.triggerEvents($('.o_colorpicker a[data-color="' + color + '"]'), ['mousedown']); // choose color 6 + await testUtils.nextTick(); assert.strictEqual(form.$('.badge:first()').data('color'), color, 'should have correctly updated the color (in edit)'); form.destroy(); }); - QUnit.test('fieldmany2many tags in editable list', function (assert) { + QUnit.test('fieldmany2many tags in editable list', async function (assert) { assert.expect(7); this.data.partner.records[0].timmy = [12]; - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -1414,20 +1424,20 @@ QUnit.module('relational_fields', { "m2m field should contain one tag"); // edit first row - testUtils.dom.click(list.$('.o_data_row:first td:nth(2)')); + await testUtils.dom.click(list.$('.o_data_row:first td:nth(2)')); var $m2o = list.$('.o_data_row:first .o_field_many2manytags .o_field_many2one'); assert.strictEqual($m2o.length, 1, "a many2one widget should have been instantiated"); // add a tag - testUtils.fields.many2one.clickOpenDropdown('timmy'); - testUtils.fields.many2one.clickHighlightedItem('timmy'); + await testUtils.fields.many2one.clickOpenDropdown('timmy'); + await testUtils.fields.many2one.clickHighlightedItem('timmy'); assert.containsN(list, '.o_data_row:first .o_field_many2manytags .badge', 2, "m2m field should contain 2 tags"); // leave edition - testUtils.dom.click(list.$('.o_data_row:nth(1) td:nth(2)')); + await testUtils.dom.click(list.$('.o_data_row:nth(1) td:nth(2)')); assert.containsN(list, '.o_data_row:first .o_field_many2manytags .badge', 2, "m2m field should contain 2 tags"); @@ -1457,9 +1467,8 @@ QUnit.module('relational_fields', { id: 10, display_name: "Partner 9", }); - this.data.partner.fields.datetime.searchable = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1482,23 +1491,23 @@ QUnit.module('relational_fields', { mode: 'edit', }, }); - testUtils.fields.many2one.clickOpenDropdown('trululu'); + await testUtils.fields.many2one.clickOpenDropdown('trululu'); await testUtils.fields.many2one.clickItem('trululu', 'Search'); + await testUtils.dom.click($('.modal .o_search_options .fa-bars')); + await testUtils.dom.click($('.modal .o_search_options .o_group_by_menu a:contains(Bar)')); - testUtils.dom.click($('.modal .o_search_options .fa-bars')); - testUtils.dom.click($('.modal .o_search_options .o_group_by_menu a:contains(Bar)')); + await testUtils.dom.click($('.modal .o_group_header:first')); - testUtils.dom.click($('.modal .o_group_header:first')); assert.strictEqual($('.modal tbody:nth(1) .o_data_row').length, 7, "should display 7 records in the first page"); - testUtils.dom.click($('.modal .o_group_header:first .o_pager_next')); + await testUtils.dom.click($('.modal .o_group_header:first .o_pager_next')); assert.strictEqual($('.modal tbody:nth(1) .o_data_row').length, 1, "should display 1 record in the second page"); form.destroy(); }); - QUnit.test('many2many_tags can load more than 40 records', function (assert) { + QUnit.test('many2many_tags can load more than 40 records', async function (assert) { assert.expect(1); this.data.partner.fields.partner_ids = {string: "Partner", type: "many2many", relation: 'partner'}; @@ -1507,7 +1516,7 @@ QUnit.module('relational_fields', { this.data.partner.records.push({id: i, display_name: 'walter' + i}); this.data.partner.records[0].partner_ids.push(i); } - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1521,7 +1530,7 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('field many2many_tags keeps focus when being edited', function (assert) { + QUnit.test('field many2many_tags keeps focus when being edited', async function (assert) { assert.expect(7); this.data.partner.records[0].timmy = [12]; @@ -1529,7 +1538,7 @@ QUnit.module('relational_fields', { obj.timmy = [[5]]; // DELETE command }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1540,22 +1549,22 @@ QUnit.module('relational_fields', { res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, '.o_field_many2manytags .badge', "should contain one tag"); // update foo, which will trigger an onchange and update timmy // -> m2mtags input should not have taken the focus form.$('input[name=foo]').focus(); - testUtils.fields.editInput(form.$('input[name=foo]'), 'trigger onchange'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'trigger onchange'); assert.containsNone(form, '.o_field_many2manytags .badge', "should contain no tags"); assert.strictEqual(form.$('input[name=foo]').get(0), document.activeElement, "foo input should have kept the focus"); // add a tag -> m2mtags input should still have the focus - testUtils.fields.many2one.clickOpenDropdown('timmy'); - testUtils.fields.many2one.clickHighlightedItem('timmy'); + await testUtils.fields.many2one.clickOpenDropdown('timmy'); + await testUtils.fields.many2one.clickHighlightedItem('timmy'); assert.containsOnce(form, '.o_field_many2manytags .badge', @@ -1564,7 +1573,7 @@ QUnit.module('relational_fields', { "m2m tags input should have kept the focus"); // remove a tag -> m2mtags input should still have the focus - testUtils.dom.click(form.$('.o_field_many2manytags .o_delete')); + await testUtils.dom.click(form.$('.o_field_many2manytags .o_delete')); assert.containsNone(form, '.o_field_many2manytags .badge', "should contain no tags"); assert.strictEqual(form.$('.o_field_many2manytags input').get(0), document.activeElement, @@ -1573,11 +1582,11 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('widget many2many_tags in one2many with display_name', function (assert) { + QUnit.test('widget many2many_tags in one2many with display_name', async function (assert) { assert.expect(4); this.data.turtle.records[0].partner_ids = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1605,26 +1614,26 @@ QUnit.module('relational_fields', { "secondrecordaaa", "the tags should be correctly rendered"); // open the x2m form view - form.$('.o_field_one2many[name="turtles"] .o_list_view td.o_data_cell:first').click(); + await testUtils.dom.click(form.$('.o_field_one2many[name="turtles"] .o_list_view td.o_data_cell:first')); assert.strictEqual($('.modal .o_form_view .o_field_many2many[name="partner_ids"] .o_list_view .o_data_cell').text(), "blipMy little Foo Value", "the list view should be correctly rendered with foo"); - $('.modal button.o_form_button_cancel').click(); + await testUtils.dom.click($('.modal button.o_form_button_cancel')); assert.strictEqual(form.$('.o_field_one2many[name="turtles"] .o_list_view .o_field_many2manytags[name="partner_ids"]').text().replace(/\s/g, ''), "secondrecordaaa", "the tags should still be correctly rendered"); - form.$buttons.find('.o_form_button_edit').click(); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_one2many[name="turtles"] .o_list_view .o_field_many2manytags[name="partner_ids"]').text().replace(/\s/g, ''), "secondrecordaaa", "the tags should still be correctly rendered"); form.destroy(); }); - QUnit.test('widget many2many_tags: tags title attribute', function (assert) { + QUnit.test('widget many2many_tags: tags title attribute', async function (assert) { assert.expect(1); this.data.turtle.records[0].partner_ids = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'turtle', data: this.data, @@ -1645,13 +1654,13 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('widget many2many_tags: toggle colorpicker multiple times', function (assert) { + QUnit.test('widget many2many_tags: toggle colorpicker multiple times', async function (assert) { assert.expect(11); this.data.partner.records[0].timmy = [12]; this.data.partner_type.records[0].color = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1672,13 +1681,13 @@ QUnit.module('relational_fields', { "colorpicker should be closed"); // click on the badge to open colorpicker - testUtils.dom.click(form.$('.o_field_many2manytags .badge .dropdown-toggle')); + await testUtils.dom.click(form.$('.o_field_many2manytags .badge .dropdown-toggle')); assert.strictEqual($('.o_colorpicker:visible').length, 1, "colorpicker should be open"); // click on the badge again to close colorpicker - testUtils.dom.click(form.$('.o_field_many2manytags .badge .dropdown-toggle')); + await testUtils.dom.click(form.$('.o_field_many2manytags .badge .dropdown-toggle')); assert.strictEqual($('.o_field_many2manytags .badge').data('color'), 0, "tag should still have color 0"); @@ -1686,13 +1695,13 @@ QUnit.module('relational_fields', { "colorpicker should be closed"); // click on the badge to open colorpicker - testUtils.dom.click(form.$('.o_field_many2manytags .badge .dropdown-toggle')); + await testUtils.dom.click(form.$('.o_field_many2manytags .badge .dropdown-toggle')); assert.strictEqual($('.o_colorpicker:visible').length, 1, "colorpicker should be open"); // click on the colorpicker, but not on a color - testUtils.dom.click(form.$('.o_colorpicker')); + await testUtils.dom.click(form.$('.o_colorpicker')); assert.strictEqual($('.o_field_many2manytags .badge').data('color'), 0, "tag should still have color 0"); @@ -1700,10 +1709,10 @@ QUnit.module('relational_fields', { "colorpicker should be closed"); // click on the badge to open colorpicker - testUtils.dom.click(form.$('.o_field_many2manytags .badge .dropdown-toggle')); + await testUtils.dom.click(form.$('.o_field_many2manytags .badge .dropdown-toggle')); // click on a color in the colorpicker - form.$('.o_colorpicker .o_tag_color_2').trigger('mousedown'); + await testUtils.dom.triggerEvents(form.$('.o_colorpicker .o_tag_color_2'),['mousedown']); assert.strictEqual($('.o_field_many2manytags .badge').data('color'), 2, "tag should have color 2"); @@ -1715,10 +1724,10 @@ QUnit.module('relational_fields', { QUnit.module('FieldRadio'); - QUnit.test('fieldradio widget on a many2one in a new record', function (assert) { + QUnit.test('fieldradio widget on a many2one in a new record', async function (assert) { assert.expect(6); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1732,18 +1741,18 @@ QUnit.module('relational_fields', { assert.ok(form.$('label.o_form_label:contains(xphone)').length, "one of them should be xphone"); assert.containsNone(form, 'input:checked', "none of the input should be checked"); - testUtils.dom.click(form.$("input.o_radio_input:first")); + await testUtils.dom.click(form.$("input.o_radio_input:first")); assert.containsOnce(form, 'input:checked', "one of the input should be checked"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); var newRecord = _.last(this.data.partner.records); assert.strictEqual(newRecord.product_id, 37, "should have saved record with correct value"); form.destroy(); }); - QUnit.test('fieldradio change value by onchange', function (assert) { + QUnit.test('fieldradio change value by onchange', async function (assert) { assert.expect(4); this.data.partner.onchanges = {bar: function (obj) { @@ -1751,7 +1760,7 @@ QUnit.module('relational_fields', { obj.color = obj.bar ? 'red' : 'black'; }}; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1762,20 +1771,20 @@ QUnit.module('relational_fields', { '</form>', }); - testUtils.dom.click(form.$("input[type='checkbox']")); + await testUtils.dom.click(form.$("input[type='checkbox']")); assert.containsOnce(form, 'input.o_radio_input[data-value="37"]:checked', "one of the input should be checked"); assert.containsOnce(form, 'input.o_radio_input[data-value="black"]:checked', "the other of the input should be checked"); - testUtils.dom.click(form.$("input[type='checkbox']")); + await testUtils.dom.click(form.$("input[type='checkbox']")); assert.containsOnce(form, 'input.o_radio_input[data-value="41"]:checked', "the other of the input should be checked"); assert.containsOnce(form, 'input.o_radio_input[data-value="red"]:checked', "one of the input should be checked"); form.destroy(); }); - QUnit.test('fieldradio widget on a selection in a new record', function (assert) { + QUnit.test('fieldradio widget on a selection in a new record', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1790,21 +1799,21 @@ QUnit.module('relational_fields', { assert.ok(form.$('label.o_form_label:contains(Red)').length, "one of them should be Red"); // click on 2nd option - testUtils.dom.click(form.$("input.o_radio_input").eq(1)); + await testUtils.dom.click(form.$("input.o_radio_input").eq(1)); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); var newRecord = _.last(this.data.partner.records); assert.strictEqual(newRecord.color, 'black', "should have saved record with correct value"); form.destroy(); }); - QUnit.test('fieldradio widget has o_horizontal or o_vertical class', function (assert) { + QUnit.test('fieldradio widget has o_horizontal or o_vertical class', async function (assert) { assert.expect(2); this.data.partner.fields.color2 = this.data.partner.fields.color; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1824,7 +1833,7 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('fieldradio widget with numerical keys encoded as strings', function (assert) { + QUnit.test('fieldradio widget with numerical keys encoded as strings', async function (assert) { assert.expect(5); this.data.partner.fields.selection = { @@ -1832,7 +1841,7 @@ QUnit.module('relational_fields', { selection: [['0', "Red"], ['1', "Black"]], }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1853,19 +1862,19 @@ QUnit.module('relational_fields', { assert.strictEqual(form.$('.o_field_widget').text(), '', "field should be unset"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsNone(form, '.o_radio_input:checked', "no value should be checked"); - testUtils.dom.click(form.$("input.o_radio_input:nth(1)")); + await testUtils.dom.click(form.$("input.o_radio_input:nth(1)")); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_widget').text(), 'Black', "value should be 'Black'"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, '.o_radio_input[data-index=1]:checked', "'Black' should be checked"); @@ -1873,7 +1882,7 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('widget radio on a many2one: domain updated by an onchange', function (assert) { + QUnit.test('widget radio on a many2one: domain updated by an onchange', async function (assert) { assert.expect(4); this.data.partner.onchanges = { @@ -1881,7 +1890,7 @@ QUnit.module('relational_fields', { }; var domain = []; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1893,7 +1902,7 @@ QUnit.module('relational_fields', { mockRPC: function (route, args) { if (args.method === 'onchange') { domain = [['id', 'in', [10]]]; - return $.when({ + return Promise.resolve({ value: { trululu: false, }, @@ -1917,8 +1926,7 @@ QUnit.module('relational_fields', { "should be 3 radio buttons"); // trigger an onchange that will update the domain - testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 2); - + await testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 2); assert.containsNone(form, '.o_field_widget[name=trululu] .o_radio_item', "should be no more radio button"); @@ -1928,10 +1936,10 @@ QUnit.module('relational_fields', { QUnit.module('FieldSelectionBadge'); - QUnit.test('FieldSelectionBadge widget on a many2one in a new record', function (assert) { + QUnit.test('FieldSelectionBadge widget on a many2one in a new record', async function (assert) { assert.expect(6); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1945,21 +1953,21 @@ QUnit.module('relational_fields', { assert.ok(form.$('span.o_selection_badge:contains(xphone)').length, "one of them should be xphone"); assert.containsNone(form, 'span.active', "none of the input should be checked"); - testUtils.dom.click($("span.o_selection_badge:first")); + await testUtils.dom.click($("span.o_selection_badge:first")); assert.containsOnce(form, 'span.active', "one of the input should be checked"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); var newRecord = _.last(this.data.partner.records); assert.strictEqual(newRecord.product_id, 37, "should have saved record with correct value"); form.destroy(); }); - QUnit.test('FieldSelectionBadge widget on a selection in a new record', function (assert) { + QUnit.test('FieldSelectionBadge widget on a selection in a new record', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1973,19 +1981,19 @@ QUnit.module('relational_fields', { assert.ok(form.$('span.o_selection_badge:contains(Red)').length, "one of them should be Red"); // click on 2nd option - testUtils.dom.click(form.$("span.o_selection_badge").eq(1)); + await testUtils.dom.click(form.$("span.o_selection_badge").eq(1)); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); var newRecord = _.last(this.data.partner.records); assert.strictEqual(newRecord.color, 'black', "should have saved record with correct value"); form.destroy(); }); - QUnit.test('FieldSelectionBadge widget on a selection in a readonly mode', function (assert) { + QUnit.test('FieldSelectionBadge widget on a selection in a readonly mode', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2000,11 +2008,11 @@ QUnit.module('relational_fields', { QUnit.module('FieldMany2ManyCheckBoxes'); - QUnit.test('widget many2many_checkboxes', function (assert) { + QUnit.test('widget many2many_checkboxes', async function (assert) { assert.expect(10); this.data.partner.records[0].timmy = [12]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2025,23 +2033,23 @@ QUnit.module('relational_fields', { assert.ok(form.$('div.o_field_widget div.custom-checkbox input').prop('disabled'), "the checkboxes should be disabled"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.notOk(form.$('div.o_field_widget div.custom-checkbox input').prop('disabled'), "the checkboxes should not be disabled"); // add a m2m value by clicking on input - testUtils.dom.click(form.$('div.o_field_widget div.custom-checkbox input').eq(1)); - testUtils.form.clickSave(form); + await testUtils.dom.click(form.$('div.o_field_widget div.custom-checkbox input').eq(1)); + await testUtils.form.clickSave(form); assert.deepEqual(this.data.partner.records[0].timmy, [12, 14], "should have added the second element to the many2many"); assert.containsN(form, 'input:checked', 2, "both checkboxes should be checked"); // remove a m2m value by clinking on label - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('div.o_field_widget div.custom-checkbox > label').eq(0)); - testUtils.form.clickSave(form); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('div.o_field_widget div.custom-checkbox > label').eq(0)); + await testUtils.form.clickSave(form); assert.deepEqual(this.data.partner.records[0].timmy, [14], "should have removed the first element to the many2many"); assert.notOk(form.$('div.o_field_widget div.custom-checkbox input').eq(0).prop('checked'), @@ -2052,11 +2060,11 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('widget many2many_checkboxes: start non empty, then remove twice', function (assert) { + QUnit.test('widget many2many_checkboxes: start non empty, then remove twice', async function (assert) { assert.expect(2); this.data.partner.records[0].timmy = [12,14]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2067,9 +2075,9 @@ QUnit.module('relational_fields', { viewOptions: {mode: 'edit'}, }); - testUtils.dom.click(form.$('div.o_field_widget div.custom-checkbox input').eq(0)); - testUtils.dom.click(form.$('div.o_field_widget div.custom-checkbox input').eq(1)); - testUtils.form.clickSave(form); + await testUtils.dom.click(form.$('div.o_field_widget div.custom-checkbox input').eq(0)); + await testUtils.dom.click(form.$('div.o_field_widget div.custom-checkbox input').eq(1)); + await testUtils.form.clickSave(form); assert.notOk(form.$('div.o_field_widget div.custom-checkbox input').eq(0).prop('checked'), "first checkbox should not be checked"); assert.notOk(form.$('div.o_field_widget div.custom-checkbox input').eq(1).prop('checked'), @@ -2080,8 +2088,8 @@ QUnit.module('relational_fields', { QUnit.module('FieldMany2ManyBinaryMultiFiles'); - QUnit.test('widget many2many_binary', function (assert) { - assert.expect(14); + QUnit.test('widget many2many_binary', async function (assert) { + assert.expect(15); this.data['ir.attachment'] = { fields: { name: {string:"Name", type: "char"}, @@ -2100,7 +2108,7 @@ QUnit.module('relational_fields', { }; this.data.turtle.records[0].picture_ids = [17]; - var form = createView({ + var form = await createView({ View: FormView, model: 'turtle', data: this.data, @@ -2130,7 +2138,7 @@ QUnit.module('relational_fields', { "there should not be a Delete button (readonly)"); // to edit mode - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, 'div.o_field_widget.oe_fileupload .o_attach', "there should be an Add button"); assert.strictEqual(form.$('div.o_field_widget.oe_fileupload .o_attach').text().trim(), "Pictures", @@ -2142,7 +2150,7 @@ QUnit.module('relational_fields', { // no idea how to test this // delete the attachment - testUtils.dom.click(form.$('div.o_field_widget.oe_fileupload .oe_attachment .oe_delete')); + await testUtils.dom.click(form.$('div.o_field_widget.oe_fileupload .oe_attachment .oe_delete')); assert.verifySteps([ @@ -2150,17 +2158,23 @@ QUnit.module('relational_fields', { '/web/dataset/call_kw/ir.attachment/read', ]); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('div.o_field_widget.oe_fileupload .oe_attachments').children().length, 0, "there should be no attachment"); + assert.verifySteps([ + '/web/dataset/call_kw/turtle/write', + '/web/dataset/call_kw/turtle/read', + ]); + form.destroy(); }); QUnit.test('name_create in form dialog', async function (assert) { assert.expect(2); - var form = createView({ + + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2184,7 +2198,7 @@ QUnit.module('relational_fields', { }, }); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); await testUtils.fields.many2one.searchAndClickItem('product_id', {selector: '.modal', search: 'new record'}); @@ -2195,14 +2209,14 @@ QUnit.module('relational_fields', { QUnit.module('FieldReference'); - QUnit.test('Reference field in modal readonly mode', function (assert) { + QUnit.test('Reference field in modal readonly mode', async function (assert) { assert.expect(4); this.data.partner.records[0].p = [2]; this.data.partner.records[1].trululu = 1; this.data.partner.records[1].reference = 'product,41'; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2225,7 +2239,7 @@ QUnit.module('relational_fields', { assert.equal($cell_o2m.text(), 'second record', 'the list should have one record'); - testUtils.dom.click($cell_o2m); + await testUtils.dom.click($cell_o2m); // In modal var $modal = $('.modal-lg'); @@ -2235,19 +2249,19 @@ QUnit.module('relational_fields', { assert.equal($modal.find('.o_form_uri.o_field_widget[name=reference]').text(), 'xpad', 'The field reference in the modal should have the right value'); - testUtils.dom.click($modal.find('.o_form_button_cancel')); + await testUtils.dom.click($modal.find('.o_form_button_cancel')); form.destroy(); }); - QUnit.test('Reference field in modal write mode', function (assert) { + QUnit.test('Reference field in modal write mode', async function (assert) { assert.expect(5); this.data.partner.records[0].p = [2]; this.data.partner.records[1].trululu = 1; this.data.partner.records[1].reference = 'product,41'; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2263,7 +2277,7 @@ QUnit.module('relational_fields', { }); // current form - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); var $fieldRef = form.$('.o_field_widget.o_field_many2one[name=reference]'); assert.equal($fieldRef.find('option:selected').text(), 'Product', @@ -2271,7 +2285,7 @@ QUnit.module('relational_fields', { assert.equal($fieldRef.find('.o_input.ui-autocomplete-input').val(), 'xphone', 'The reference field\'s record should be xphone'); - testUtils.dom.click(form.$('.o_data_cell')); + await testUtils.dom.click(form.$('.o_data_cell')); // In modal var $modal = $('.modal-lg'); @@ -2288,10 +2302,10 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('reference in form view', function (assert) { + QUnit.test('reference in form view', async function (assert) { assert.expect(15); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2309,7 +2323,7 @@ QUnit.module('relational_fields', { mockRPC: function (route, args) { if (args.method === 'get_formview_action') { assert.deepEqual(args.args[0], [37], "should call get_formview_action with correct id"); - return $.when({ + return Promise.resolve({ res_id: 17, type: 'ir.actions.act_window', target: 'current', @@ -2318,7 +2332,7 @@ QUnit.module('relational_fields', { } if (args.method === 'get_formview_id') { assert.deepEqual(args.args[0], [37], "should call get_formview_id with correct id"); - return $.when(false); + return Promise.resolve(false); } if (args.method === 'name_search') { assert.strictEqual(args.model, 'partner_type', @@ -2341,9 +2355,9 @@ QUnit.module('relational_fields', { assert.strictEqual(form.$('a.o_form_uri:contains(xphone)').length, 1, "should contain a link"); - testUtils.dom.click(form.$('a.o_form_uri')); + await testUtils.dom.click(form.$('a.o_form_uri')); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsN(form, '.o_field_widget', 2, "should contain two field widgets (selection and many2one)"); @@ -2360,28 +2374,28 @@ QUnit.module('relational_fields', { assert.deepEqual(options, ['', 'product', 'partner_type', 'partner'], "the options should be correctly set"); - testUtils.dom.click(form.$('.o_external_button')); + await testUtils.dom.click(form.$('.o_external_button')); assert.strictEqual($('.modal .modal-title').text().trim(), 'Open: custom label', "dialog title should display the custom string label"); - testUtils.dom.click($('.modal .o_form_button_cancel')); + await testUtils.dom.click($('.modal .o_form_button_cancel')); - testUtils.fields.editSelect(form.$('.o_field_widget select'), 'partner_type'); + await testUtils.fields.editSelect(form.$('.o_field_widget select'), 'partner_type'); assert.strictEqual(form.$('.o_field_widget input').val(), "", "many2one value should be reset after model change"); - testUtils.fields.many2one.clickOpenDropdown('reference'); - testUtils.fields.many2one.clickHighlightedItem('reference'); + await testUtils.fields.many2one.clickOpenDropdown('reference'); + await testUtils.fields.many2one.clickHighlightedItem('reference'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('a.o_form_uri:contains(gold)').length, 1, "should contain a link with the new value"); form.destroy(); }); - QUnit.test('default_get and onchange with a reference field', function (assert) { + QUnit.test('default_get and onchange with a reference field', async function (assert) { assert.expect(8); this.data.partner.fields.reference.default = 'product,37'; @@ -2393,7 +2407,7 @@ QUnit.module('relational_fields', { }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2423,9 +2437,9 @@ QUnit.module('relational_fields', { "reference field value should be correctly set"); // trigger onchange - testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 12); + await testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 12); - assert.verifySteps(['product', 'partner_type'], "the second name_get should have been done"); + assert.verifySteps(['partner_type'], "the second name_get should have been done"); assert.strictEqual(form.$('.o_field_widget[name="reference"] select').val(), "partner_type", "reference field model should be correctly set"); assert.strictEqual(form.$('.o_field_widget[name="reference"] input').val(), "gold", @@ -2433,14 +2447,14 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('default_get a reference field in a x2m', function (assert) { + QUnit.test('default_get a reference field in a x2m', async function (assert) { assert.expect(1); this.data.partner.fields.turtles.default = [ [0, false, {turtle_ref: 'product,37'}] ]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2465,7 +2479,7 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('widget reference on char field, reset by onchange', function (assert) { + QUnit.test('widget reference on char field, reset by onchange', async function (assert) { assert.expect(4); this.data.partner.records[0].foo = 'product,37'; @@ -2476,7 +2490,7 @@ QUnit.module('relational_fields', { }; var nbNameGet = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2506,7 +2520,7 @@ QUnit.module('relational_fields', { "foo field should be correctly set"); // trigger onchange - testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 41); + await testUtils.fields.editInput(form.$('.o_field_widget[name=int_field]'), 41); assert.strictEqual(nbNameGet, 2, "the second name_get should have been done"); @@ -2515,10 +2529,10 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('reference and list navigation', function (assert) { + QUnit.test('reference and list navigation', async function (assert) { assert.expect(2); - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -2526,25 +2540,25 @@ QUnit.module('relational_fields', { }); // edit first row - testUtils.dom.click(list.$('.o_data_row .o_data_cell').first()); + await testUtils.dom.click(list.$('.o_data_row .o_data_cell').first()); assert.strictEqual(list.$('.o_data_row:eq(0) .o_field_widget[name="reference"] input')[0], document.activeElement, 'input of first data row should be selected'); // press TAB to go to next line - list.$('.o_data_row:eq(0) input').trigger($.Event('keydown', { + await testUtils.dom.triggerEvents(list.$('.o_data_row:eq(0) input:eq(1)'),[$.Event('keydown', { which: $.ui.keyCode.TAB, keyCode: $.ui.keyCode.TAB, - })); + })]); assert.strictEqual(list.$('.o_data_row:eq(1) .o_field_widget[name="reference"] select')[0], document.activeElement, 'select of second data row should be selected'); list.destroy(); }); - QUnit.test('one2many with extra field from server not in form', function (assert) { + QUnit.test('one2many with extra field from server not in form', async function (assert) { assert.expect(6); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2569,20 +2583,20 @@ QUnit.module('relational_fields', { } }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); var x2mList = form.$('.o_field_x2many_list[name=p]'); // Add a record in the list - testUtils.dom.click(x2mList.find('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(x2mList.find('.o_field_x2many_list_row_add a')); var modal = $('.modal-lg'); var nameInput = modal.find('input.o_input[name=display_name]'); - testUtils.fields.editInput(nameInput, 'michelangelo'); + await testUtils.fields.editInput(nameInput, 'michelangelo'); // Save the record in the modal (though it is still virtual) - testUtils.dom.click(modal.find('.btn-primary').first()); + await testUtils.dom.click(modal.find('.btn-primary').first()); assert.equal(x2mList.find('.o_data_row').length, 1, 'There should be 1 records in the x2m list'); @@ -2595,7 +2609,7 @@ QUnit.module('relational_fields', { 'The display name field should have the right value'); // Save the whole thing - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); x2mList = form.$('.o_field_x2many_list[name=p]'); @@ -2613,11 +2627,11 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('one2many invisible depends on parent field', function (assert) { + QUnit.test('one2many invisible depends on parent field', async function (assert) { assert.expect(4); this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2643,21 +2657,22 @@ QUnit.module('relational_fields', { }); assert.containsN(form, 'th', 2, "should be 2 columns in the one2many"); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_many2one[name="product_id"] input')); - testUtils.dom.click($('li.ui-menu-item a:contains(xpad)').trigger('mouseenter')); + await testUtils.form.clickEdit(form); + await testUtils.fields.many2one.clickOpenDropdown("product_id"); + await testUtils.fields.many2one.clickHighlightedItem("product_id"); assert.containsOnce(form, 'th', "should be 1 column when the product_id is set"); - form.$('.o_field_many2one[name="product_id"] input').val('').trigger('keyup'); + await testUtils.fields.editAndTrigger(form.$('.o_field_many2one[name="product_id"] input'), + '', 'keyup'); assert.containsN(form, 'th', 2, "should be 2 columns in the one2many when product_id is not set"); - testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input')); + await testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input')); assert.containsOnce(form, 'th', "should be 1 column after the value change"); form.destroy(); }); - QUnit.test('one2many column visiblity depends on onchange of parent field', function (assert) { + QUnit.test('one2many column visiblity depends on onchange of parent field', async function (assert) { assert.expect(3); this.data.partner.records[0].p = [2]; @@ -2670,7 +2685,7 @@ QUnit.module('relational_fields', { } }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2691,24 +2706,24 @@ QUnit.module('relational_fields', { "should be only 1 column ('foo') in the one2many"); assert.containsOnce(form, '.o_list_view .o_data_row', "should contain one row"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // add a new o2m record - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); form.$('.o_field_one2many input:first').focus(); - testUtils.fields.editInput(form.$('.o_field_one2many input:first'), 'New line'); - testUtils.dom.click(form.$el); + await testUtils.fields.editInput(form.$('.o_field_one2many input:first'), 'New line'); + await testUtils.dom.click(form.$el); assert.containsN(form, 'th', 2, "should be 2 columns('foo' + 'int_field')"); form.destroy(); }); - QUnit.test('one2many column_invisible on view not inline', function (assert) { + QUnit.test('one2many column_invisible on view not inline', async function (assert) { assert.expect(4); this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2735,25 +2750,26 @@ QUnit.module('relational_fields', { }); assert.containsN(form, 'th', 2, "should be 2 columns in the one2many"); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_many2one[name="product_id"] input')); - testUtils.dom.click($('li.ui-menu-item a:contains(xpad)').trigger('mouseenter')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_many2one[name="product_id"] input')); + await testUtils.fields.many2one.clickHighlightedItem("product_id"); assert.containsOnce(form, 'th', "should be 1 column when the product_id is set"); - form.$('.o_field_many2one[name="product_id"] input').val('').trigger('keyup'); + await testUtils.fields.editAndTrigger(form.$('.o_field_many2one[name="product_id"] input'), + '', 'keyup'); assert.containsN(form, 'th', 2, "should be 2 columns in the one2many when product_id is not set"); - testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input')); + await testUtils.dom.click(form.$('.o_field_boolean[name="bar"] input')); assert.containsOnce(form, 'th', "should be 1 column after the value change"); form.destroy(); }); QUnit.module('TabNavigation'); - QUnit.test('when Navigating to a many2one with tabs, it receives the focus and adds a new line', function (assert) { + QUnit.test('when Navigating to a many2one with tabs, it receives the focus and adds a new line', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', viewOptions: { @@ -2782,10 +2798,7 @@ QUnit.module('relational_fields', { assert.strictEqual(form.$el.find('input[name="qux"]')[0], document.activeElement, "initially, the focus should be on the 'qux' field because it is the first input"); - form.$el.find('input[name="qux"]').trigger($.Event('keydown', { - which: $.ui.keyCode.TAB, - keyCode: $.ui.keyCode.TAB, - })); + await testUtils.fields.triggerKeydown(form.$el.find('input[name="qux"]'), 'tab'); assert.strictEqual(assert.strictEqual(form.$el.find('input[name="turtle_foo"]')[0], document.activeElement, "after tab, the focus should be on the many2one on the first input of the newly added line")); @@ -2793,10 +2806,10 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('when Navigating to a many to one with tabs, it places the focus on the first visible field', function (assert) { + QUnit.test('when Navigating to a many to one with tabs, it places the focus on the first visible field', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', viewOptions: { @@ -2830,7 +2843,7 @@ QUnit.module('relational_fields', { which: $.ui.keyCode.TAB, keyCode: $.ui.keyCode.TAB, })); - testUtils.dom.click(document.activeElement); + await testUtils.dom.click(document.activeElement); assert.strictEqual(assert.strictEqual(form.$el.find('input[name="turtle_foo"]')[0], document.activeElement, "after tab, the focus should be on the many2one")); @@ -2839,12 +2852,12 @@ QUnit.module('relational_fields', { }); QUnit.test('when Navigating to a many2one with tabs, not filling any field and hitting tab,' + - ' we should not add a first line but navigate to the next control', function (assert) { + ' we should not add a first line but navigate to the next control', async function (assert) { assert.expect(3); this.data.partner.records[0].turtles = []; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', viewOptions: { @@ -2875,32 +2888,27 @@ QUnit.module('relational_fields', { }); assert.strictEqual(form.$el.find('input[name="qux"]')[0], - document.activeElement, - "initially, the focus should be on the 'qux' field because it is the first input"); - form.$el.find('input[name="qux"]').trigger($.Event('keydown', { - which: $.ui.keyCode.TAB, - keyCode: $.ui.keyCode.TAB, - })); + document.activeElement, + "initially, the focus should be on the 'qux' field because it is the first input"); + await testUtils.fields.triggerKeydown(form.$el.find('input[name="qux"]'), 'tab'); // skips the first field of the one2many - $(document.activeElement).trigger($.Event('keydown', {which: $.ui.keyCode.TAB})); - + await testUtils.fields.triggerKeydown($(document.activeElement), 'tab'); // skips the second (and last) field of the one2many - $(document.activeElement).trigger($.Event('keydown', {which: $.ui.keyCode.TAB})); - + await testUtils.fields.triggerKeydown($(document.activeElement), 'tab'); assert.strictEqual(assert.strictEqual(form.$el.find('input[name="foo"]')[0], - document.activeElement, - "after tab, the focus should be on the many2one")); + document.activeElement, + "after tab, the focus should be on the many2one")); form.destroy(); }); - QUnit.test('when Navigating to a many to one with tabs, editing in a popup, the popup should receive the focus then give it back', function (assert) { + QUnit.test('when Navigating to a many to one with tabs, editing in a popup, the popup should receive the focus then give it back', async function (assert) { assert.expect(3); this.data.partner.records[0].turtles = []; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', viewOptions: { @@ -2934,22 +2942,14 @@ QUnit.module('relational_fields', { }); assert.strictEqual(form.$el.find('input[name="qux"]')[0], - document.activeElement, - "initially, the focus should be on the 'qux' field because it is the first input"); - form.$el.find('input[name="qux"]').trigger($.Event('keydown', { - which: $.ui.keyCode.TAB, - keyCode: $.ui.keyCode.TAB, - })); - + document.activeElement, + "initially, the focus should be on the 'qux' field because it is the first input"); + await testUtils.fields.triggerKeydown(form.$el.find('input[name="qux"]'), 'tab'); assert.strictEqual($.find('input[name="turtle_foo"]')[0], document.activeElement, "when the one2many received the focus, the popup should open because it automatically adds a new line"); - $('input[name="turtle_foo"]').trigger($.Event('keydown', { - which: $.ui.keyCode.ESCAPE, - keyCode: $.ui.keyCode.ESCAPE, - })); - + await testUtils.fields.triggerKeydown($('input[name="turtle_foo"]'), 'escape'); assert.strictEqual(form.$el.find('.o_field_x2many_list_row_add a')[0], document.activeElement, "after escape, the focus should be back on the add new line link"); @@ -2957,13 +2957,12 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('when creating a new many2one on a x2many then discarding it immediately with ESCAPE, it should not crash', function (assert) { - var done = assert.async(); + QUnit.test('when creating a new many2one on a x2many then discarding it immediately with ESCAPE, it should not crash', async function (assert) { assert.expect(1); this.data.partner.records[0].turtles = []; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', viewOptions: { @@ -2987,33 +2986,30 @@ QUnit.module('relational_fields', { }); // add a new line - testUtils.dom.click(form.$el.find('.o_field_x2many_list_row_add>a')); + await testUtils.dom.click(form.$el.find('.o_field_x2many_list_row_add>a')); // open the field turtle_trululu (one2many) var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; - testUtils.dom.click(form.$el.find('.o_input_dropdown>input')); + await testUtils.dom.click(form.$el.find('.o_input_dropdown>input')); - concurrency.delay(0).then(function () { - // click create and edit - testUtils.dom.click($('.ui-autocomplete .ui-menu-item a:contains(Create and)').trigger('mouseenter')); + // click create and edit + await testUtils.dom.click($('.ui-autocomplete .ui-menu-item a:contains(Create and)').trigger('mouseenter')); - // hit escape immediately - var escapeKey = $.ui.keyCode.ESCAPE; - $(document.activeElement).trigger( - $.Event('keydown', {which: escapeKey, keyCode: escapeKey})); + // hit escape immediately + var escapeKey = $.ui.keyCode.ESCAPE; + $(document.activeElement).trigger( + $.Event('keydown', {which: escapeKey, keyCode: escapeKey})); - assert.ok('did not crash'); - relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; - form.destroy(); - done(); - }); + assert.ok('did not crash'); + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + form.destroy(); }); - QUnit.test('navigating through an editable list with custom controls [REQUIRE FOCUS]', function (assert) { + QUnit.test('navigating through an editable list with custom controls [REQUIRE FOCUS]', async function (assert) { assert.expect(5); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3040,44 +3036,29 @@ QUnit.module('relational_fields', { "first input should be focused by default"); // press tab to navigate to the list - form.$('.o_field_widget[name="display_name"]').trigger($.Event('keydown', { - which: $.ui.keyCode.TAB, - keyCode: $.ui.keyCode.TAB, - })); - + await testUtils.fields.triggerKeydown( + form.$('.o_field_widget[name="display_name"]'), 'tab'); // press ESC to cancel 1st control click (create) - form.$('.o_data_cell input').trigger($.Event('keydown', { - which: $.ui.keyCode.ESCAPE, - keyCode: $.ui.keyCode.ESCAPE, - })); - + await testUtils.fields.triggerKeydown( + form.$('.o_data_cell input'), 'escape'); assert.strictEqual(document.activeElement, form.$('.o_field_x2many_list_row_add a:first')[0], "first editable list control should now have the focus"); // press right to focus the second control - form.$('.o_field_x2many_list_row_add a:first').trigger($.Event('keydown', { - which: $.ui.keyCode.RIGHT, - keyCode: $.ui.keyCode.RIGHT, - })); - + await testUtils.fields.triggerKeydown( + form.$('.o_field_x2many_list_row_add a:first'), 'right'); assert.strictEqual(document.activeElement, form.$('.o_field_x2many_list_row_add a:nth(1)')[0], "second editable list control should now have the focus"); // press left to come back to first control - form.$('.o_field_x2many_list_row_add a:nth(1)').trigger($.Event('keydown', { - which: $.ui.keyCode.LEFT, - keyCode: $.ui.keyCode.LEFT, - })); - + await testUtils.fields.triggerKeydown( + form.$('.o_field_x2many_list_row_add a:nth(1)'), 'left'); assert.strictEqual(document.activeElement, form.$('.o_field_x2many_list_row_add a:first')[0], "first editable list control should now have the focus"); // press tab to leave the list - form.$('.o_field_x2many_list_row_add a:first').trigger($.Event('keydown', { - which: $.ui.keyCode.TAB, - keyCode: $.ui.keyCode.TAB, - })); - + await testUtils.fields.triggerKeydown( + form.$('.o_field_x2many_list_row_add a:first'), 'tab'); assert.strictEqual(document.activeElement, form.$('.o_field_widget[name="int_field"]')[0], "last input should now be focused"); diff --git a/addons/web/static/tests/fields/special_fields.js b/addons/web/static/tests/fields/special_fields.js index 6acc2be521b..2a674be19ba 100644 --- a/addons/web/static/tests/fields/special_fields.js +++ b/addons/web/static/tests/fields/special_fields.js @@ -141,7 +141,7 @@ QUnit.module('special_fields', { QUnit.module('FieldTimezoneMismatch'); - QUnit.test('widget timezone_mismatch in a list view', function (assert) { + QUnit.test('widget timezone_mismatch in a list view', async function (assert) { assert.expect(5); this.data.partner.fields.tz_offset = { @@ -158,7 +158,7 @@ QUnit.module('special_fields', { } }; - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -170,21 +170,21 @@ QUnit.module('special_fields', { assert.strictEqual(list.$('td:contains(Red)').length, 3, "should have 3 rows with correct value"); - list.$('td:contains(Red):first').click(); + await testUtils.dom.click(list.$('td:contains(Red):first')); var $td = list.$('tbody tr.o_selected_row td:not(.o_list_record_selector)'); assert.strictEqual($td.find('select').length, 1, "td should have a child 'select'"); assert.strictEqual($td.contents().length, 1, "select tag should be only child of td"); - $td.find('select').val('"black"').trigger('change'); + await testUtils.fields.editSelect($td.find('select'), '"black"'); assert.strictEqual($td.find('.o_tz_warning').length, 1, "Should display icon alert"); assert.ok($td.find('select option:selected').text().match(/Black\s+\([0-9]+\/[0-9]+\/[0-9]+ [0-9]+:[0-9]+:[0-9]+\)/), "Should display the datetime in the selected timezone"); list.destroy(); }); - QUnit.test('widget timezone_mismatch in a form view', function (assert) { + QUnit.test('widget timezone_mismatch in a form view', async function (assert) { assert.expect(1); this.data.partner.fields.tz_offset = { @@ -198,7 +198,7 @@ QUnit.module('special_fields', { this.data.partner.records[0].tz = false; this.data.partner.records[0].tz_offset = '+4800'; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', res_id: 1, @@ -208,12 +208,12 @@ QUnit.module('special_fields', { '<field name="tz" widget="timezone_mismatch"/>' + '</form>', }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, 'select[name=tz]'); form.destroy(); }); - QUnit.test('widget timezone_mismatch in a form view edit mode with mismatch', function (assert) { + QUnit.test('widget timezone_mismatch in a form view edit mode with mismatch', async function (assert) { assert.expect(3); this.data.partner.fields.tz_offset = { @@ -227,7 +227,7 @@ QUnit.module('special_fields', { this.data.partner.records[0].tz = 'America/Los_Angeles'; this.data.partner.records[0].tz_offset = '+4800'; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', res_id: 1, @@ -256,7 +256,7 @@ QUnit.module('special_fields', { QUnit.module('FieldReportLayout'); - QUnit.test('report_layout widget in form view', function (assert) { + QUnit.test('report_layout widget in form view', async function (assert) { assert.expect(3); this.data['report.layout'] = { @@ -275,11 +275,11 @@ QUnit.module('special_fields', { view_id: 41, image: "/web/static/tata.png", pdf: "/web/static/tata.pdf", - }] + }], }; this.data.partner.records[1].product_id = false; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -287,6 +287,9 @@ QUnit.module('special_fields', { '<field name="product_id" widget="report_layout"/> '+ '</form>', res_id: 2, + viewOptions: { + mode: 'edit', + }, }); assert.strictEqual(form.$('.img.img-fluid').length, 2, @@ -295,7 +298,7 @@ QUnit.module('special_fields', { "No image should be selected"); // select first image - form.$(".img.img-fluid:first").click(); + await testUtils.dom.click(form.$(".img.img-fluid:first")); assert.ok(form.$(".img.img-fluid:first").hasClass('btn-info'), "First image should be selected"); diff --git a/addons/web/static/tests/fields/upgrade_fields_tests.js b/addons/web/static/tests/fields/upgrade_fields_tests.js index 65de67a7951..d908fd48b61 100644 --- a/addons/web/static/tests/fields/upgrade_fields_tests.js +++ b/addons/web/static/tests/fields/upgrade_fields_tests.js @@ -22,27 +22,27 @@ QUnit.module('upgrade_fields', { QUnit.module('UpgradeBoolean'); - QUnit.test('widget upgrade_boolean in a form view', function (assert) { + QUnit.test('widget upgrade_boolean in a form view', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, arch: '<form><field name="bar" widget="upgrade_boolean"/></form>', }); - testUtils.dom.click(form.$('input:checkbox')); + await testUtils.dom.click(form.$('input:checkbox')); assert.strictEqual($('.modal').length, 1, "the 'Upgrade to Enterprise' dialog should be opened"); form.destroy(); }); - QUnit.test('widget upgrade_boolean in a form view', function (assert) { + QUnit.test('widget upgrade_boolean in a form view', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js index ff75722b333..44cc276e1c8 100644 --- a/addons/web/static/tests/helpers/mock_server.js +++ b/addons/web/static/tests/helpers/mock_server.js @@ -103,9 +103,9 @@ var MockServer = Class.extend({ * * @param {string} route * @param {Object} args - * @returns {Deferred<any>} + * @returns {Promise<any>} * Resolved with the result of the RPC, stringified then parsed. - * If the RPC should fail, the deferred will be rejected with the + * If the RPC should fail, the promise will be rejected with the * error object, stringified then parsed. */ performRpc: function (route, args) { @@ -132,17 +132,18 @@ var MockServer = Class.extend({ console.log('%c[rpc] response' + route, 'color: blue; font-weight: bold;', JSON.parse(resultString)); } return JSON.parse(resultString); - }, function (result, event) { - var errorString = JSON.stringify(result || false); + }, function (result) { + var message = result && result.message; + var event = result && result.event; + var errorString = JSON.stringify(message || false); if (debug) { console.log('%c[rpc] response (error) ' + route, 'color: orange; font-weight: bold;', JSON.parse(errorString)); } - return $.Deferred().reject(errorString, event || $.Event()); + return Promise.reject({message: errorString, event: event || $.Event()}); }); - var promise = def.promise(); - promise.abort = abort; - return promise; + def.abort = abort; + return def; }, //-------------------------------------------------------------------------- @@ -861,7 +862,7 @@ var MockServer = Class.extend({ } } if (type === 'many2one') { - var ids = _.pluck(records, aggregatedFields[i]); + var ids = _.pluck(records, aggregatedFields[i]); group[aggregatedFields[i]] = _.uniq(ids).length || null; } } @@ -1064,7 +1065,7 @@ var MockServer = Class.extend({ var result = this._mockSearchReadController({ model: model, domain: kwargs.domain || args[0], - fields: kwargs.fields || args[1], + fields: kwargs.fields || args[1], offset: kwargs.offset || args[2], limit: kwargs.limit || args[3], order: kwargs.order || args[4], @@ -1181,83 +1182,83 @@ var MockServer = Class.extend({ * @private * @param {string} route * @param {Object} args - * @returns {Deferred<any>} + * @returns {Promise<any>} * Resolved with the result of the RPC. If the RPC should fail, the - * deferred should either be rejected or the call should throw an + * promise should either be rejected or the call should throw an * exception (@see performRpc for error handling). */ _performRpc: function (route, args) { switch (route) { case '/web/action/load': - return $.when(this._mockLoadAction(args.kwargs)); + return Promise.resolve(this._mockLoadAction(args.kwargs)); case '/web/dataset/search_read': - return $.when(this._mockSearchReadController(args)); + return Promise.resolve(this._mockSearchReadController(args)); case '/web/dataset/resequence': - return $.when(this._mockResequence(args)); + return Promise.resolve(this._mockResequence(args)); } if (route.indexOf('/web/image') >= 0 || _.contains(['.png', '.jpg'], route.substr(route.length - 4))) { - return $.when(); + return Promise.resolve(); } switch (args.method) { case 'copy': - return $.when(this._mockCopy(args.model, args.args[0])); + return Promise.resolve(this._mockCopy(args.model, args.args[0])); case 'create': - return $.when(this._mockCreate(args.model, args.args[0])); + return Promise.resolve(this._mockCreate(args.model, args.args[0])); case 'default_get': - return $.when(this._mockDefaultGet(args.model, args.args, args.kwargs)); + return Promise.resolve(this._mockDefaultGet(args.model, args.args, args.kwargs)); case 'fields_get': - return $.when(this._mockFieldsGet(args.model, args.args)); + return Promise.resolve(this._mockFieldsGet(args.model, args.args)); case 'search_panel_select_range': - return $.when(this._mockSearchPanelSelectRange(args.model, args.args, args.kwargs)); + return Promise.resolve(this._mockSearchPanelSelectRange(args.model, args.args, args.kwargs)); case 'search_panel_select_multi_range': - return $.when(this._mockSearchPanelSelectMultiRange(args.model, args.args, args.kwargs)); + return Promise.resolve(this._mockSearchPanelSelectMultiRange(args.model, args.args, args.kwargs)); case 'load_views': - return $.when(this._mockLoadViews(args.model, args.kwargs)); + return Promise.resolve(this._mockLoadViews(args.model, args.kwargs)); case 'name_get': - return $.when(this._mockNameGet(args.model, args.args)); + return Promise.resolve(this._mockNameGet(args.model, args.args)); case 'name_create': - return $.when(this._mockNameCreate(args.model, args.args)); + return Promise.resolve(this._mockNameCreate(args.model, args.args)); case 'name_search': - return $.when(this._mockNameSearch(args.model, args.args, args.kwargs)); + return Promise.resolve(this._mockNameSearch(args.model, args.args, args.kwargs)); case 'onchange': - return $.when(this._mockOnchange(args.model, args.args)); + return Promise.resolve(this._mockOnchange(args.model, args.args)); case 'read': - return $.when(this._mockRead(args.model, args.args, args.kwargs)); + return Promise.resolve(this._mockRead(args.model, args.args, args.kwargs)); case 'read_group': - return $.when(this._mockReadGroup(args.model, args.kwargs)); + return Promise.resolve(this._mockReadGroup(args.model, args.kwargs)); case 'read_progress_bar': - return $.when(this._mockReadProgressBar(args.model, args.kwargs)); + return Promise.resolve(this._mockReadProgressBar(args.model, args.kwargs)); case 'search_count': - return $.when(this._mockSearchCount(args.model, args.args)); + return Promise.resolve(this._mockSearchCount(args.model, args.args)); case 'search_read': - return $.when(this._mockSearchRead(args.model, args.args, args.kwargs)); + return Promise.resolve(this._mockSearchRead(args.model, args.args, args.kwargs)); case 'unlink': - return $.when(this._mockUnlink(args.model, args.args)); + return Promise.resolve(this._mockUnlink(args.model, args.args)); case 'write': - return $.when(this._mockWrite(args.model, args.args)); + return Promise.resolve(this._mockWrite(args.model, args.args)); } var model = this.data[args.model]; if (model && typeof model[args.method] === 'function') { - return $.when(this.data[args.model][args.method](args.args, args.kwargs)); + return Promise.resolve(this.data[args.model][args.method](args.args, args.kwargs)); } throw new Error("Unimplemented route: " + route); diff --git a/addons/web/static/tests/helpers/qunit_asserts.js b/addons/web/static/tests/helpers/qunit_asserts.js index bce6ef2a777..e8f9948d347 100644 --- a/addons/web/static/tests/helpers/qunit_asserts.js +++ b/addons/web/static/tests/helpers/qunit_asserts.js @@ -37,7 +37,7 @@ function _checkClass(w, className, shouldHaveClass, msg) { var descr = `${shouldHaveClass ? 'hasClass' : 'doesNotHaveClass'} ${className}`; QUnit.assert.ok(false, `Assertion '${descr}' targets ${$el.length} elements instead of 1`); } else { - msg = msg || `target ${$el.selector || ''} should ${shouldHaveClass ? '' : 'not'} have class ${className}`; + msg = msg || `target should ${shouldHaveClass ? '' : 'not'} have class ${className}`; var hasClass = $el.hasClass(className); var condition = shouldHaveClass ? hasClass : !hasClass; QUnit.assert.ok(condition, msg); @@ -63,8 +63,15 @@ function _checkVisible(w, shouldBeVisible, msg) { var descr = `${shouldBeVisible ? 'isVisible' : 'isNotVisible'}`; QUnit.assert.ok(false, `Assertion '${descr}' targets ${$el.length} elements instead of 1`); } else { - msg = msg || `target ${$el.selector} should ${shouldBeVisible ? '' : 'not'} be visible`; + msg = msg || `target should ${shouldBeVisible ? '' : 'not'} be visible`; var isVisible = $el.is(':visible'); + if (isVisible) { + // Additional test to see if $el is really visible, since jQuery + // considers an element visible if it has a DOWRect, even if its + // width and height are equal to zero. + var boundingClientRect = $el[0].getBoundingClientRect(); + isVisible = boundingClientRect.width + boundingClientRect.height; + } var condition = shouldBeVisible ? isVisible : !isVisible; QUnit.assert.ok(condition, msg); } @@ -96,9 +103,7 @@ function containsN(w, selector, n, msg) { var $matches = $el.find(selector); if (!msg) { msg = `Selector '${selector}' should have exactly ${n} matches`; - if ($el.selector) { - msg += ` (inside '${$el.selector}')`; - } + msg += ` (inside the target)`; } QUnit.assert.strictEqual($matches.length, n, msg); } @@ -181,7 +186,7 @@ function hasAttrValue(w, attr, value, msg) { var descr = `hasAttrValue (${attr}: ${value})`; QUnit.assert.ok(false, `Assertion '${descr}' targets ${$el.length} elements instead of 1`); } else { - msg = msg || `attribute '${attr}' of target ${$el.selector || ''} should be '${value}'`; + msg = msg || `attribute '${attr}' of target should be '${value}'`; QUnit.assert.strictEqual($el.attr(attr), value, msg); } } diff --git a/addons/web/static/tests/helpers/qunit_config.js b/addons/web/static/tests/helpers/qunit_config.js index 511b9cd2fce..8c6496bb8f9 100644 --- a/addons/web/static/tests/helpers/qunit_config.js +++ b/addons/web/static/tests/helpers/qunit_config.js @@ -92,6 +92,64 @@ QUnit.moduleDone(function(result) { }); /** + * After each test, we check that there is no leftover in the DOM. + * + * Note: this event is not QUnit standard, we added it for this specific use case. + */ +QUnit.on('OdooAfterTestHook', function () { + // check for leftover elements in the body + var $bodyChilds = $('body > *'); + var validElements = [ + // always in the body: + {tagName: 'DIV', attrToCompare: 'id', value: 'qunit'}, + {tagName: 'DIV', attrToCompare: 'id', value: 'qunit-fixture'}, + {tagName: 'SCRIPT', attrToCompare: 'id', value: ''}, + // shouldn't be in the body after a test but are tolerated: + {tagName: 'DIV', attrToCompare: 'className', value: 'o_notification_manager'}, + {tagName: 'DIV', attrToCompare: 'className', value: 'tooltip fade bs-tooltip-auto'}, + {tagName: 'DIV', attrToCompare: 'className', value: 'tooltip fade bs-tooltip-auto show'}, + {tagName: 'I', attrToCompare: 'title', value: 'Raphaël Colour Picker'}, + {tagName: 'SPAN', attrToCompare: 'className', value: 'select2-hidden-accessible'}, + // Due to a Document Kanban bug (already present in 12.0) + {tagName: 'DIV', attrToCompare: 'className', value: 'ui-helper-hidden-accessible'}, + {tagName: 'UL', attrToCompare: 'className', value: 'ui-menu ui-widget ui-widget-content ui-autocomplete ui-front'}, + ]; + if ($bodyChilds.length > 3) { + for (var i = 0; i < $bodyChilds.length; i++) { + var bodyChild = $bodyChilds[i]; + var isValid = false; + + for (var j = 0; j < validElements.length; j++) { + var toleratedElement = validElements[j]; + if (toleratedElement.tagName === bodyChild.tagName) { + var attr = toleratedElement.attrToCompare; + if (toleratedElement.value === bodyChild[attr]) { + isValid = true; + break; + } + } + } + + if (!isValid) { + if (!document.body.classList.contains('debug')) { + $(bodyChild).remove(); + } + QUnit.pushFailure(`Body still contains undesirable elements`); + } + } + } + + // check for leftovers in #qunit-fixture + var qunitFixture = document.getElementById('qunit-fixture'); + if (qunitFixture.children.length) { + QUnit.pushFailure(`#qunit-fixture still contains elements`); + if (!document.body.classList.contains('debug')) { + $(qunitFixture.children).remove(); + } + } +}); + +/** * Add a sort button on top of the QUnit result page, so we can see which tests * take the most time. */ diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js index 2897b02214c..ccf5d8daf6b 100644 --- a/addons/web/static/tests/helpers/test_utils.js +++ b/addons/web/static/tests/helpers/test_utils.js @@ -11,7 +11,9 @@ odoo.define('web.test_utils', function (require) { */ var ajax = require('web.ajax'); +var concurrency = require('web.concurrency'); var core = require('web.core'); +var relationalFields = require('web.relational_fields'); var session = require('web.session'); var testUtilsCreate = require('web.test_utils_create'); var testUtilsDom = require('web.test_utils_dom'); @@ -25,22 +27,89 @@ var testUtilsModal = require('web.test_utils_modal'); var testUtilsPivot = require('web.test_utils_pivot'); var tools = require('web.tools'); + function deprecated(fn, type) { var msg = `Helper 'testUtils.${fn.name}' is deprecated. ` + `Please use 'testUtils.${type}.${fn.name}' instead.`; return tools.deprecated(fn, msg); } +/** + * Helper function, make a promise with a public resolve function. Note that + * this is not standard and should not be used outside of tests... + * + * @returns {Promise + resolve and reject function} + */ +function makeTestPromise() { + var resolve; + var reject; + var promise = new Promise(function (_resolve, _reject) { + resolve = _resolve; + reject = _reject; + }); + promise.resolve = function () { + resolve.apply(null, arguments); + return promise; + }; + promise.reject = function () { + reject.apply(null, arguments); + return promise; + }; + return promise; +} + +/** + * Make a promise with public resolve and reject functions (see + * @makeTestPromise). Perform an assert.step when the promise is + * resolved/rejected. + * + * @param {Object} assert instance object with the assertion methods + * @param {function} assert.step + * @param {string} str message to pass to assert.step + * @returns {Promise + resolve and reject function} + */ +function makeTestPromiseWithAssert(assert, str) { + var prom = makeTestPromise(); + prom.then(() => assert.step('ok ' + str)).catch(function () {}); + prom.catch(() => assert.step('ko ' + str)); + return prom; +} + +/** + * Create a new promise that can be waited by the caller in order to execute + * code after the next microtask tick and before the next jobqueue tick. + * + * @return {Promise} an already fulfilled promise + */ +async function nextMicrotaskTick() { + return Promise.resolve(); +} + +/** + * Returns a promise that is resolved in the next jobqueue tick so that the + * caller can wait on it in order to execute code in the next jobqueue tick. + * + * @return {Promise} a promise that will be fulfilled in the next jobqueue tick + */ +async function nextTick() { + return concurrency.delay(0); +} + // Loading static files cannot be properly simulated when their real content is // really needed. This is the case for static XML files so we load them here, // before starting the qunit test suite. // (session.js is in charge of loading the static xml bundle and we also have // to load xml files that are normally lazy loaded by specific widgets). -return $.when( +return Promise.all([ session.is_bound, ajax.loadXML('/web/static/src/xml/dialog.xml', core.qweb) -).then(function () { +]).then(function () { setTimeout(function () { + // jquery autocomplete refines the search in a setTimeout() parameterized + // with a delay, so we force this delay to 0 s.t. the dropdown is filtered + // directly on the next tick + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + // this is done with the hope that tests are // only started all together... QUnit.start(); @@ -53,6 +122,7 @@ return $.when( patchDate: testUtilsMock.patchDate, unpatch: testUtilsMock.unpatch, fieldsViewGet: testUtilsMock.fieldsViewGet, + patchSetTimeout: testUtilsMock.patchSetTimeout, }, dom: { triggerKeypressEvent: testUtilsDom.triggerKeypressEvent, @@ -63,6 +133,7 @@ return $.when( click: testUtilsDom.click, clickFirst: testUtilsDom.clickFirst, clickLast: testUtilsDom.clickLast, + triggerEvents: testUtilsDom.triggerEvents, }, form: { clickEdit: testUtilsForm.clickEdit, @@ -99,6 +170,9 @@ return $.when( editInput: testUtilsFields.editInput, editSelect: testUtilsFields.editSelect, editAndTrigger: testUtilsFields.editAndTrigger, + triggerKey: testUtilsFields.triggerKey, + triggerKeydown: testUtilsFields.triggerKeydown, + triggerKeyup: testUtilsFields.triggerKeyup, }, file: { createFile: testUtilsFile.createFile, @@ -108,11 +182,16 @@ return $.when( createActionManager: testUtilsCreate.createActionManager, createDebugManager: testUtilsCreate.createDebugManager, - createAsyncView: testUtilsCreate.createAsyncView, + createAsyncView: testUtilsCreate.createView, + createCalendarView: testUtilsCreate.createCalendarView, createControlPanel: testUtilsCreate.createControlPanel, createView: testUtilsCreate.createView, createModel: testUtilsCreate.createModel, createParent: testUtilsCreate.createParent, + makeTestPromise: makeTestPromise, + makeTestPromiseWithAssert: makeTestPromiseWithAssert, + nextMicrotaskTick: nextMicrotaskTick, + nextTick: nextTick, // backward-compatibility addMockEnvironment: deprecated(testUtilsMock.addMockEnvironment, 'mock'), diff --git a/addons/web/static/tests/helpers/test_utils_create.js b/addons/web/static/tests/helpers/test_utils_create.js index 5a64512260e..7e4de1060bc 100644 --- a/addons/web/static/tests/helpers/test_utils_create.js +++ b/addons/web/static/tests/helpers/test_utils_create.js @@ -13,6 +13,7 @@ odoo.define('web.test_utils_create', function (require) { var ActionManager = require('web.ActionManager'); var config = require('web.config'); var ControlPanelView = require('web.ControlPanelView'); +var concurrency = require('web.concurrency'); var DebugManager = require('web.DebugManager'); var dom = require('web.dom'); var testUtilsMock = require('web.test_utils_mock'); @@ -30,7 +31,7 @@ var Widget = require('web.Widget'); * @param {function} [params.mockRPC] * @returns {ActionManager} */ -function createActionManager (params) { +var createActionManager = async function (params) { params = params || {}; var $target = $('#qunit-fixture'); if (params.debug) { @@ -47,7 +48,7 @@ function createActionManager (params) { _.extend(params, { mockRPC: function (route, args) { if (args.model === 'ir.attachment') { - return $.when([]); + return Promise.resolve([]); } if (mockRPC) { return mockRPC.apply(this, arguments); @@ -56,7 +57,7 @@ function createActionManager (params) { }, }); testUtilsMock.addMockEnvironment(widget, _.defaults(params, { debounce: false })); - widget.prependTo($target); + await widget.prependTo($target); widget.$el.addClass('o_web_client'); if (config.device.isMobile) { widget.$el.addClass('o_touch_device'); @@ -71,14 +72,14 @@ function createActionManager (params) { widget.destroy(); }; var fragment = document.createDocumentFragment(); - actionManager.appendTo(fragment).then(function () { + return actionManager.appendTo(fragment).then(function () { dom.append(widget.$el, fragment, { callbacks: [{ widget: actionManager }], in_DOM: true, }); + return actionManager; }); - return actionManager; -} +}; /** * create a view from various parameters. Here, a view means a javascript @@ -89,10 +90,6 @@ function createActionManager (params) { * through a mock method using the data object as source, and already loaded/ * started. * - * Most views can be tested synchronously (@see createView), but some view have - * external dependencies (like lazy loaded libraries). In that case, it is - * necessary to use this method. - * * @param {Object} params * @param {string} params.arch the xml (arch) of the view to be instantiated * @param {any[]} [params.domain] the initial domain for the view @@ -109,9 +106,9 @@ function createActionManager (params) { * Note that this is particularly useful if you want to intercept events going * up in the init process of the view, because there are no other way to do it * after this method returns - * @returns {Deferred<AbstractView>} resolves with the instance of the view + * @returns {Promise<AbstractController>} resolves with the instance of the view */ -function createAsyncView(params) { +async function createView(params) { var $target = $('#qunit-fixture'); var widget = new Widget(); if (params.debug) { @@ -175,6 +172,7 @@ function createAsyncView(params) { // when it will be called the second time (by its parent) delete view.destroy; widget.destroy(); + $webClient.remove(); testUtilsMock.unpatch(View); }; @@ -187,14 +185,45 @@ function createAsyncView(params) { callbacks: [{ widget: view }], in_DOM: true, }); + view.$el.on('click', 'a', function (ev) { ev.preventDefault(); }); - return view; }); }); } + +/** + * Similar as createView, but specific for calendar views. Some calendar + * tests need to trigger positional clicks on the DOM produced by fullcalendar. + * Those tests must use this helper with option positionalClicks set to true. + * This will move the rendered calendar to the body (required to do positional + * clicks), and wait for a setTimeout(0) before returning, because fullcalendar + * makes the calendar scroll to 6:00 in a setTimeout(0), which might have an + * impact according to where we want to trigger positional clicks. + * + * @param {Object} params see @createView + * @param {Object} [options] + * @param {boolean} [options.positionalClicks=false] + * @returns {Promise<CalendarController>} + */ +async function createCalendarView(params, options) { + var calendar = await createView(params); + if (!options || !options.positionalClicks) { + return calendar; + } + var $view = $('#qunit-fixture').contents(); + $view.prependTo('body'); + var destroy = calendar.destroy; + calendar.destroy = function () { + $view.remove(); + destroy(); + }; + await concurrency.delay(0); + return calendar; +} + /** * create a controlPanel from various parameters. * @@ -227,7 +256,7 @@ function createAsyncView(params) { * @param {boolean} [params.withSearchBar=true] if set to false, no default * search bar will be rendered * - * @returns {Deferred<ControlPanel>} resolves with an instance of the ControlPanelController + * @returns {Promise<ControlPanel>} resolves with an instance of the ControlPanelController */ function createControlPanel(params) { params = params || {}; @@ -270,6 +299,7 @@ function createControlPanel(params) { // children when it will be called the second time (by its parent) delete controlPanel.destroy; widget.destroy(); + $webClient.remove(); }; // render the controlPanel in a fragment as it must be able to render @@ -290,16 +320,16 @@ function createControlPanel(params) { * * @param {Object} [params={}] */ -function createDebugManager (params) { +var createDebugManager = function (params) { params = params || {}; var mockRPC = params.mockRPC; _.extend(params, { mockRPC: function (route, args) { if (args.method === 'check_access_rights') { - return $.when(true); + return Promise.resolve(true); } if (args.method === 'xmlid_to_res_id') { - return $.when(true); + return Promise.resolve(true); } if (mockRPC) { return mockRPC.apply(this, arguments); @@ -309,7 +339,7 @@ function createDebugManager (params) { session: { user_has_group: function (group) { if (group === 'base.group_no_one') { - return $.when(true); + return Promise.resolve(true); } return this._super.apply(this, arguments); }, @@ -318,7 +348,7 @@ function createDebugManager (params) { var debugManager = new DebugManager(); testUtilsMock.addMockEnvironment(debugManager, params); return debugManager; -} +}; /** * create a model from given parameters. @@ -360,32 +390,10 @@ function createParent(params) { return widget; } -/** - * create a view synchronously. This method uses the createAsyncView method. - * Most views are synchronous, so the deferred can be resolved immediately and - * this method will work. - * - * Be careful, if for some reason a view is async, this method will crash. - * @see createAsyncView - * - * @param {Object} params will be given to createAsyncView - * @returns {AbstractView} - */ -function createView(params) { - var view; - createAsyncView(params).then(function (result) { - view = result; - }); - if (!view) { - throw "The view that you are trying to create is async. Please use createAsyncView instead"; - } - return view; -} - - return { createActionManager: createActionManager, - createAsyncView: createAsyncView, + createView: createView, + createCalendarView: createCalendarView, createControlPanel: createControlPanel, createDebugManager: createDebugManager, createModel: createModel, diff --git a/addons/web/static/tests/helpers/test_utils_dom.js b/addons/web/static/tests/helpers/test_utils_dom.js index dcf9eab6e15..315a00cbfe7 100644 --- a/addons/web/static/tests/helpers/test_utils_dom.js +++ b/addons/web/static/tests/helpers/test_utils_dom.js @@ -1,6 +1,8 @@ -odoo.define('web.test_utils_dom', function () { +odoo.define('web.test_utils_dom', function (require) { "use strict"; +var concurrency = require('web.concurrency'); + /** * DOM Test Utils * @@ -101,6 +103,7 @@ function dragAndDrop($el, $to, options) { $el.trigger($.Event("mouseup")); }); } + return concurrency.delay(0); } /** @@ -114,7 +117,8 @@ function dragAndDrop($el, $to, options) { function triggerMouseEvent($el, type) { var pos = $el.offset(); var e = new $.Event(type); - e.pageX = e.layerX = e.screenX = pos.left; + // little fix since it seems on chrome, it triggers 1px too on the left + e.pageX = e.layerX = e.screenX = pos.left + 1; e.pageY = e.layerY = e.screenY = pos.top; e.which = 1; $el.trigger(e); @@ -154,6 +158,8 @@ function triggerKeypressEvent(char) { var keycode; if (char === "Enter") { keycode = $.ui.keyCode.ENTER; + } else if (char === "Tab") { + keycode = $.ui.keyCode.TAB; } else { keycode = char.charCodeAt(0); } @@ -177,37 +183,47 @@ function openDatepicker($datepickerEl) { * @param {Object} [options] * @param {boolean} [options.first=false] if true, clicks on the first element * @param {boolean} [options.last=false] if true, clicks on the last element + * @param {boolean} [options.allowInvisible=false] if true, clicks on the + * element event if it is invisible + * @param {boolean} [options.shouldTriggerClick=false] if true, trigger the + * click event without calling the function click of jquery */ function click(el, options) { options = options || {}; var matches = typeof el === 'string' ? $(el) : el; - var selectorMsg = el.selector ? `(selector: ${el.selector})` : ''; + var selectorMsg = typeof el === 'string' ? `(selector: ${el})` : ''; if (!matches.filter) { // it might be an array of dom elements matches = $(matches); } + var validMatches = options.allowInvisible ? matches : matches.filter(':visible'); - var visibleMatches = matches.filter(':visible'); if (options.first) { - if (visibleMatches.length === 1) { + if (validMatches.length === 1) { throw new Error(`There should be more than one visible target ${selectorMsg}. If` + ' you are sure that there is exactly one target, please use the ' + 'click function instead of the clickFirst function'); } - visibleMatches = visibleMatches.first(); + validMatches = validMatches.first(); } else if (options.last) { - if (visibleMatches.length === 1) { + if (validMatches.length === 1) { throw new Error(`There should be more than one visible target ${selectorMsg}. If` + ' you are sure that there is exactly one target, please use the ' + 'click function instead of the clickLast function'); } - visibleMatches = visibleMatches.last(); + validMatches = validMatches.last(); } - if (visibleMatches.length === 0 && matches.length > 0) { + if (validMatches.length === 0 && matches.length > 0) { throw new Error(`Element to click on is not visible ${selectorMsg}`); - } else if (visibleMatches.length !== 1) { - throw new Error(`Found ${visibleMatches.length} elements to click on, instead of 1 ${selectorMsg}`); + } else if (validMatches.length !== 1) { + throw new Error(`Found ${validMatches.length} elements to click on, instead of 1 ${selectorMsg}`); + } + if (options.shouldTriggerClick) { + validMatches.trigger('click'); + } else { + validMatches.click(); } - visibleMatches.click(); + + return concurrency.delay(0); } /** @@ -216,9 +232,13 @@ function click(el, options) { * use the click helper instead. * * @param {string|NodeList|jQuery} el (if string: it is a (jquery) selector) + * @param {boolean} [options.allowInvisible=false] if true, clicks on the + * element event if it is invisible + * @param {boolean} [options.shouldTriggerClick=false] if true, trigger the + * click event without calling the function click of jquery */ -function clickFirst(el) { - click(el, {first: true}); +function clickFirst(el, options) { + return click(el, _.extend({}, options, {first: true})); } /** @@ -227,11 +247,36 @@ function clickFirst(el) { * use the click helper instead. * * @param {string|NodeList|jQuery} el + * @param {boolean} [options.allowInvisible=false] if true, clicks on the + * element event if it is invisible + * @param {boolean} [options.shouldTriggerClick=false] if true, trigger the + * click event without calling the function click of jquery + */ +function clickLast(el, options) { + return click(el, _.extend({}, options, {last: true})); +} + + +/** + * trigger events on the specified target + * @param {jQuery} $el should target a single dom node + * @param {string[]} events the events you want to trigger + * @returns Promise */ -function clickLast(el) { - click(el, {last: true}); +function triggerEvents($el, events) { + if ($el.length !== 1) { + throw new Error(`target has length ${$el.length} instead of 1`); + } + if (typeof events === 'string') { + events = [events]; + } + events.forEach(function (event) { + $el.trigger(event); + }); + return concurrency.delay(0); } + return { triggerKeypressEvent: triggerKeypressEvent, triggerMouseEvent: triggerMouseEvent, @@ -241,6 +286,7 @@ return { click: click, clickFirst: clickFirst, clickLast: clickLast, + triggerEvents: triggerEvents, }; }); diff --git a/addons/web/static/tests/helpers/test_utils_fields.js b/addons/web/static/tests/helpers/test_utils_fields.js index f0f29839a9d..359f5f56b08 100644 --- a/addons/web/static/tests/helpers/test_utils_fields.js +++ b/addons/web/static/tests/helpers/test_utils_fields.js @@ -11,6 +11,7 @@ odoo.define('web.test_utils_fields', function (require) { */ var concurrency = require('web.concurrency'); +var domUtils = require('web.test_utils_dom'); //------------------------------------------------------------------------------ // Public functions @@ -32,9 +33,7 @@ function editAndTrigger($el, value, events) { throw new Error(`target ${$el.selector} has length ${$el.length} instead of 1`); } $el.val(value); - events.forEach(function (event) { - $el.trigger(event); - }); + return domUtils.triggerEvents($el, events); } /** @@ -49,7 +48,7 @@ function editAndTrigger($el, value, events) { * @param {string|number} value */ function editInput($el, value) { - editAndTrigger($el, value, ['input']); + return editAndTrigger($el, value, ['input']); } /** @@ -65,6 +64,7 @@ function editInput($el, value) { */ function editSelect($el, value) { editAndTrigger($el, value, ['change']); + return concurrency.delay(0); } /** @@ -75,14 +75,16 @@ function editSelect($el, value) { * input * @returns {HTMLInputElement} the main many2one input */ -function clickOpenM2ODropdown(fieldName, selector) { +async function clickOpenM2ODropdown(fieldName, selector) { var m2oSelector = `${selector || ''} .o_field_many2one[name=${fieldName}] input`; var matches = document.querySelectorAll(m2oSelector); if (matches.length !== 1) { throw new Error(`cannot open m2o: selector ${selector} has been found ${matches.length} instead of 1`); } matches[0].click(); - return matches[0]; + return await concurrency.delay(0).then(function () { + return matches[0]; + }); } /** @@ -97,6 +99,7 @@ function clickM2OHighlightedItem(fieldName, selector) { var $dropdown = $(m2oSelector).autocomplete('widget'); // clicking on an li (no matter which one), will select the focussed one $dropdown.find('li:first()').click(); + return concurrency.delay(0); } /** @@ -117,7 +120,10 @@ function clickM2OItem(fieldName, searchText) { if ($target.length !== 1 || !$target.is(':visible')) { throw new Error('Menu item should be unique and visible'); } - $target.mouseenter().click(); + $target.mouseenter(); + $target.click(); + + return concurrency.delay(0); } /** @@ -140,30 +146,69 @@ function clickM2OItem(fieldName, searchText) { function searchAndClickM2OItem(fieldName, options) { options = options || {}; - var input = clickOpenM2ODropdown(fieldName, options.selector); - - var def; - if (options.search) { - // jquery autocomplete refines the search in a setTimeout() parameterized - // with a delay, so we force this delay to 0 s.t. the dropdown is filtered - // directly on the next tick - var $input = $(input); - var delay = $input.autocomplete('option', 'delay'); - $input.autocomplete('option', 'delay', 0); - input.value = options.search; - input.dispatchEvent(new Event('input')); - $input.autocomplete('option', 'delay', delay); - def = concurrency.delay(0); - } + return clickOpenM2ODropdown(fieldName, options.selector).then(function (input) { + var def; + if (options.search) { + input.value = options.search; + input.dispatchEvent(new Event('input')); + def = concurrency.delay(0); + } + return Promise.resolve(def).then(function () { + if (options.item) { + return clickM2OItem(fieldName, options.item); + } else { + return clickM2OHighlightedItem(fieldName, options.selector); + } + }); + }); +} - return $.when(def).then(function () { - if (options.item) { - clickM2OItem(fieldName, options.item); +/** + * Helper to trigger a key event on an element. + * + * @param {string} type type of key event ('press', 'up' or 'down') + * @param {jQuery} $el + * @param {number|string} keyCode used as number, but if string, it'll check if + * the string corresponds to a key -otherwise it will keep only the first + * char to get a letter key- and convert it into a keyCode. + * @returns {Promise} + */ +function triggerKey(type, $el, keyCode) { + type = 'key' + type; + if (typeof keyCode === 'string') { + if (keyCode.length > 1) { + keyCode = keyCode.toUpperCase(); + keyCode = $.ui.keyCode[keyCode]; } else { - clickM2OHighlightedItem(fieldName, options.selector); + keyCode = keyCode.charCodeAt(0); } - }); + } + $el.trigger({type: type, which: keyCode, keyCode: keyCode}); + return concurrency.delay(0); } + +/** + * Helper to trigger a keydown event on an element. + * + * @param {jQuery} $el + * @param {number|string} keyCode @see triggerKey + * @returns {Promise} + */ +function triggerKeydown($el, keyCode) { + return triggerKey('down', $el, keyCode); +} + +/** + * Helper to trigger a keyup event on an element. + * + * @param {jQuery} $el + * @param {number|string} keyCode @see triggerKey + * @returns {Promise} + */ +function triggerKeyup($el, keyCode) { + return triggerKey('up', $el, keyCode); +} + return { clickOpenM2ODropdown: clickOpenM2ODropdown, clickM2OHighlightedItem: clickM2OHighlightedItem, @@ -172,6 +217,9 @@ return { editInput: editInput, editSelect: editSelect, searchAndClickM2OItem: searchAndClickM2OItem, + triggerKey: triggerKey, + triggerKeydown: triggerKeydown, + triggerKeyup: triggerKeyup, }; }); diff --git a/addons/web/static/tests/helpers/test_utils_file.js b/addons/web/static/tests/helpers/test_utils_file.js index 64e044e3180..e5b68a28e0b 100644 --- a/addons/web/static/tests/helpers/test_utils_file.js +++ b/addons/web/static/tests/helpers/test_utils_file.js @@ -47,29 +47,30 @@ function _createFakeDataTransfer(file) { * @param {string} data.name * @param {string} data.content * @param {string} data.contentType - * @returns {$.Promise<Object>} resolved with file created + * @returns {Promise<Object>} resolved with file created */ function createFile(data) { - var def = $.Deferred(); - var requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; - if (!requestFileSystem) { - throw new Error('FileSystem API is not supported'); - } - requestFileSystem(window.TEMPORARY, 1024 * 1024, function (fileSystem) { - fileSystem.root.getFile(data.name, { create: true }, function (fileEntry) { - fileEntry.createWriter(function (fileWriter) { - fileWriter.onwriteend = function (e) { - fileSystem.root.getFile(data.name, {}, function (fileEntry) { - fileEntry.file(function (file) { - def.resolve(file); + // Note: this is only supported by Chrome, and does not work in Incognito mode + return new Promise(function (resolve, reject) { + var requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; + if (!requestFileSystem) { + throw new Error('FileSystem API is not supported'); + } + requestFileSystem(window.TEMPORARY, 1024 * 1024, function (fileSystem) { + fileSystem.root.getFile(data.name, { create: true }, function (fileEntry) { + fileEntry.createWriter(function (fileWriter) { + fileWriter.onwriteend = function (e) { + fileSystem.root.getFile(data.name, {}, function (fileEntry) { + fileEntry.file(function (file) { + resolve(file); + }); }); - }); - }; - fileWriter.write(new Blob([ data.content ], { type: data.contentType })); + }; + fileWriter.write(new Blob([ data.content ], { type: data.contentType })); + }); }); }); }); - return def; } /** diff --git a/addons/web/static/tests/helpers/test_utils_form.js b/addons/web/static/tests/helpers/test_utils_form.js index 379350e1ca6..b4bff154ce7 100644 --- a/addons/web/static/tests/helpers/test_utils_form.js +++ b/addons/web/static/tests/helpers/test_utils_form.js @@ -20,7 +20,7 @@ var testUtilsDom = require('web.test_utils_dom'); * @param {FormController} form */ function clickEdit(form) { - testUtilsDom.click(form.$buttons.find('.o_form_button_edit')); + return testUtilsDom.click(form.$buttons.find('.o_form_button_edit')); } /** @@ -30,7 +30,7 @@ function clickEdit(form) { * @param {FormController} form */ function clickSave(form) { - testUtilsDom.click(form.$buttons.find('.o_form_button_save')); + return testUtilsDom.click(form.$buttons.find('.o_form_button_save')); } /** @@ -40,7 +40,7 @@ function clickSave(form) { * @param {FormController} form */ function clickCreate(form) { - testUtilsDom.click(form.$buttons.find('.o_form_button_create')); + return testUtilsDom.click(form.$buttons.find('.o_form_button_create')); } /** @@ -50,7 +50,7 @@ function clickCreate(form) { * @param {FormController} form */ function clickDiscard(form) { - testUtilsDom.click(form.$buttons.find('.o_form_button_cancel')); + return testUtilsDom.click(form.$buttons.find('.o_form_button_cancel')); } /** @@ -60,7 +60,7 @@ function clickDiscard(form) { * @param {[Object]} params given to the controller reload method */ function reload(form, params) { - form.reload(params); + return form.reload(params); } return { diff --git a/addons/web/static/tests/helpers/test_utils_graph.js b/addons/web/static/tests/helpers/test_utils_graph.js index 8867f82c349..f6b50a1b571 100644 --- a/addons/web/static/tests/helpers/test_utils_graph.js +++ b/addons/web/static/tests/helpers/test_utils_graph.js @@ -18,7 +18,7 @@ odoo.define('web.test_utils_graph', function () { * @param {[Object]} params given to the controller reload method */ function reload(graph, params) { - graph.reload(params); + return graph.reload(params); } return { diff --git a/addons/web/static/tests/helpers/test_utils_kanban.js b/addons/web/static/tests/helpers/test_utils_kanban.js index 166ae2a2572..76af632b417 100644 --- a/addons/web/static/tests/helpers/test_utils_kanban.js +++ b/addons/web/static/tests/helpers/test_utils_kanban.js @@ -18,22 +18,24 @@ var testUtilsFields = require('web.test_utils_fields'); * the Create button is visible. * * @param {KanbanController} kanban + * @returns {Promise} */ function clickCreate(kanban) { - testUtilsDom.click(kanban.$buttons.find('.o-kanban-button-new')); + return testUtilsDom.click(kanban.$buttons.find('.o-kanban-button-new')); } /** * Open the settings menu for a column (in a grouped kanban view) * * @param {jQuery} $column + * @returns {Promise} */ function toggleGroupSettings($column) { var $dropdownToggler = $column.find('.o_kanban_config > a.dropdown-toggle'); if (!$dropdownToggler.is(':visible')) { $dropdownToggler.css('display', 'block'); } - testUtilsDom.click($dropdownToggler); + return testUtilsDom.click($dropdownToggler); } /** @@ -43,6 +45,7 @@ function toggleGroupSettings($column) { * @param {kanbanController} kanban * @param {string|number} value * @param {[string]} fieldName + * @returns {Promise} */ function quickCreate(kanban, value, fieldName) { var additionalSelector = fieldName ? ('[name=' + fieldName + ']'): ''; @@ -53,7 +56,7 @@ function quickCreate(kanban, value, fieldName) { keyCode: $.ui.keyCode.ENTER, } ); - testUtilsFields.editAndTrigger( + return testUtilsFields.editAndTrigger( kanban.$('.o_kanban_quick_create input' + additionalSelector), value, ['input', enterEvent] @@ -65,9 +68,10 @@ function quickCreate(kanban, value, fieldName) { * * @param {KanbanController} kanban * @param {[Object]} params given to the controller reload method + * @returns {Promise} */ function reload(kanban, params) { - kanban.reload(params); + return kanban.reload(params); } /** @@ -76,13 +80,14 @@ function reload(kanban, params) { * does not comply with the usual dom structure. * * @param {jQuery} $record + * @returns {Promise} */ function toggleRecordDropdown($record) { var $dropdownToggler = $record.find('.o_dropdown_kanban > a.dropdown-toggle'); if (!$dropdownToggler.is(':visible')) { $dropdownToggler.css('display', 'block'); } - testUtilsDom.click($dropdownToggler); + return testUtilsDom.click($dropdownToggler); } diff --git a/addons/web/static/tests/helpers/test_utils_mock.js b/addons/web/static/tests/helpers/test_utils_mock.js index 2ecc655db7e..ccb4d20959e 100644 --- a/addons/web/static/tests/helpers/test_utils_mock.js +++ b/addons/web/static/tests/helpers/test_utils_mock.js @@ -124,7 +124,7 @@ function removeSrcAttribute(el, widget) { * is completely removed by default. * * @returns {MockServer} the instance of the mock server, created by this - * function. It is necessary for createAsyncView so that method can call some + * function. It is necessary for createView so that method can call some * other methods on it. */ function addMockEnvironment(widget, params) { @@ -199,10 +199,11 @@ function addMockEnvironment(widget, params) { // widget is destroyed, at the end of each test to avoid collisions core.bus.trigger('clear_cache'); - _(services).chain() - .compact() // services can be defined but null (e.g. ajax) - .reject(function (s) { return s.isDestroyed(); }) - .invoke('destroy'); + Object.keys(services).forEach(function(s) { + var service = services[s]; + if (service && !service.isDestroyed()) + service.destroy(); + }); DebouncedField.prototype.DEBOUNCE = initialDebounceValue; dom.DEBOUNCE = initialDOMDebounceValue; @@ -543,6 +544,25 @@ function unpatch(target) { delete target.__patchID; } +window.originalSetTimeout = window.setTimeout; +function patchSetTimeout() { + var original = window.setTimeout; + var self = this; + window.setTimeout = function (handler, delay) { + console.log("calling setTimeout on " + (handler.name || "some function") + "with delay of " + delay); + console.trace(); + var handlerArguments = Array.prototype.slice.call(arguments, 1); + return original(function () { + handler.bind(self, handlerArguments)(); + console.log('after doing the action of the setTimeout'); + }, delay); + }; + + return function () { + window.setTimeout = original; + }; +} + return { addMockEnvironment: addMockEnvironment, @@ -551,6 +571,7 @@ return { patchDate: patchDate, patch: patch, unpatch: unpatch, + patchSetTimeout: patchSetTimeout, }; }); diff --git a/addons/web/static/tests/helpers/test_utils_modal.js b/addons/web/static/tests/helpers/test_utils_modal.js index d2162aebfa0..5587eecfc01 100644 --- a/addons/web/static/tests/helpers/test_utils_modal.js +++ b/addons/web/static/tests/helpers/test_utils_modal.js @@ -11,6 +11,7 @@ odoo.define('web.test_utils_modal', function (require) { */ var core = require('web.core'); +var concurrency = require('web.concurrency'); /** * Click on a button in the footer of a modal (which contains a given string). @@ -25,6 +26,7 @@ function clickButton(text) { throw new Error(`Found ${$button.length} button(s) containing '${text}'`); } $button.click(); + return concurrency.delay(0); } return { diff --git a/addons/web/static/tests/helpers/test_utils_pivot.js b/addons/web/static/tests/helpers/test_utils_pivot.js index 18a5a7307f3..fe059b533ed 100644 --- a/addons/web/static/tests/helpers/test_utils_pivot.js +++ b/addons/web/static/tests/helpers/test_utils_pivot.js @@ -24,7 +24,7 @@ var testUtilsDom = require('web.test_utils_dom'); * @param {string} measure */ function clickMeasure(pivot, measure) { - testUtilsDom.click(pivot.$buttons.find(`.dropdown-item[data-field=${measure}]`)); + return testUtilsDom.click(pivot.$buttons.find(`.dropdown-item[data-field=${measure}]`)); } /** @@ -35,7 +35,7 @@ function clickMeasure(pivot, measure) { * @param {PivotController} pivot */ function toggleMeasuresDropdown(pivot) { - testUtilsDom.click(pivot.$buttons.filter('.btn-group:first').find('> button')); + return testUtilsDom.click(pivot.$buttons.filter('.btn-group:first').find('> button')); } /** @@ -45,7 +45,7 @@ function toggleMeasuresDropdown(pivot) { * @param {[Object]} params given to the controller reload method */ function reload(pivot, params) { - pivot.reload(params); + return pivot.reload(params); } return { diff --git a/addons/web/static/tests/qweb_tests.js b/addons/web/static/tests/qweb_tests.js index fbb710d8cfc..fda72e2392b 100644 --- a/addons/web/static/tests/qweb_tests.js +++ b/addons/web/static/tests/qweb_tests.js @@ -15,20 +15,20 @@ function loadTest(assert, template, context) { var done = assert.async(); assert.expect(1); - var def = $.Deferred(); var qweb = new window.QWeb2.Engine(); - - qweb.add_template(qwebPath + template, function (error, doc) { - if (error) { - return $.Deferred().reject(error); - } - def.resolve({ - qweb: qweb, - doc: doc + var prom = new Promise(function (resolve, reject) { + qweb.add_template(qwebPath + template, function (error, doc) { + if (error) { + return reject(error); + } + resolve({ + qweb: qweb, + doc: doc + }); }); }); - def.then(function (r) { + prom.then(function (r) { var qweb = r.qweb; var doc = r.doc; assert.expect(doc.querySelectorAll('result').length); @@ -60,7 +60,7 @@ function loadTest(assert, template, context) { } done(); }); - return def; + return prom; } QUnit.module('QWeb', {}, function () { @@ -87,7 +87,9 @@ QUnit.module('QWeb', {}, function () { var WORD_REPLACEMENT = window.QWeb2.WORD_REPLACEMENT; window.QWeb2.WORD_REPLACEMENT = _.extend({not: '!', None: 'undefined'}, WORD_REPLACEMENT); loadTest(assert, 'qweb-test-global.xml', {bool: function (v) { return !!v ? 'True' : 'False';}}) - .always(function () { + .then(function () { + window.QWeb2.WORD_REPLACEMENT = WORD_REPLACEMENT; + }, function () { window.QWeb2.WORD_REPLACEMENT = WORD_REPLACEMENT; }); }); diff --git a/addons/web/static/tests/services/notification_service_tests.js b/addons/web/static/tests/services/notification_service_tests.js index 8a10b9f2df9..aed8d2fa847 100644 --- a/addons/web/static/tests/services/notification_service_tests.js +++ b/addons/web/static/tests/services/notification_service_tests.js @@ -41,76 +41,68 @@ QUnit.module('Services', { }, function () { QUnit.module('Notification'); - QUnit.test('Display a simple notification', function (assert) { - var done = assert.async(); + QUnit.test('Display a simple notification', async function (assert) { assert.expect(4); - var view = createView(this.viewParams); + var view = await createView(this.viewParams); view.call('notification', 'notify', { title: 'a', message: 'b', }); + await testUtils.nextMicrotaskTick(); var $notification = $('body .o_notification_manager .o_notification'); assert.strictEqual(_.str.trim($notification.html().replace(/\s+/g, ' ')), "<div class=\"o_notification_title\"> <span role=\"img\" aria-label=\"Notification undefined\" class=\"o_icon fa fa-3x fa-lightbulb-o\" title=\"Notification undefined\"></span> a </div> <div class=\"o_notification_content\">b</div>", "should display notification"); - assert.strictEqual($notification.find('.o_close').length, 0, "should not display the close button in "); - setTimeout(function () { - assert.strictEqual($notification.is(':hidden'), true, "should hide the notification"); - assert.strictEqual($('body .o_notification_manager .o_notification').length, 0, "should destroy the notification"); - view.destroy(); - done(); - }); + assert.containsNone($notification, '.o_close', "should not display the close button in "); + await testUtils.nextTick(); + assert.strictEqual($notification.is(':hidden'), true, "should hide the notification"); + assert.strictEqual($('body .o_notification_manager .o_notification').length, 0, "should destroy the notification"); + view.destroy(); }); - QUnit.test('Display a warning', function (assert) { - var done = assert.async(); + QUnit.test('Display a warning', async function (assert) { assert.expect(1); - var view = createView(this.viewParams); + var view = await createView(this.viewParams); view.call('notification', 'notify', { title: 'a', message: 'b', type: 'warning' }); + await testUtils.nextMicrotaskTick(); var $notification = $('body .o_notification_manager .o_notification'); assert.strictEqual(_.str.trim($notification.html().replace(/\s+/g, ' ')), "<div class=\"o_notification_title\"> <span role=\"img\" aria-label=\"Notification undefined\" class=\"o_icon fa fa-3x fa-exclamation\" title=\"Notification undefined\"></span> a </div> <div class=\"o_notification_content\">b</div>", "should display notification"); view.destroy(); - setTimeout(done); }); - QUnit.test('Display a sticky notification', function (assert) { - var done = assert.async(); + QUnit.test('Display a sticky notification', async function (assert) { assert.expect(3); - var view = createView(this.viewParams); + var view = await createView(this.viewParams); view.call('notification', 'notify', { title: 'a', message: 'b', sticky: true, }); + await testUtils.nextTick(); var $notification = $('body .o_notification_manager .o_notification'); - assert.strictEqual($notification.find('.o_close').length, 1, "should display the close button in notification"); + assert.containsOnce($notification, '.o_close', "should display the close button in notification"); - setTimeout(function () { - assert.strictEqual($notification.is(':hidden'), false, "should not hide the notification automatically"); - testUtils.dom.click($notification.find('.o_close')); - setTimeout(function () { - assert.strictEqual($('body .o_notification_manager .o_notification').length, 0, "should destroy the notification"); - view.destroy(); - done(); - }); - }); + assert.strictEqual($notification.is(':hidden'), false, "should not hide the notification automatically"); + await testUtils.dom.click($notification.find('.o_close')); + assert.strictEqual($('body .o_notification_manager .o_notification').length, + 0, "should destroy the notification"); + view.destroy(); }); - QUnit.test('Display a simple notification with onClose callback when automatically close', function (assert) { - var done = assert.async(); + QUnit.test('Display a simple notification with onClose callback when automatically close', async function (assert) { assert.expect(2); var close = 0; - var view = createView(this.viewParams); + var view = await createView(this.viewParams); view.call('notification', 'notify', { title: 'a', message: 'b', @@ -118,16 +110,14 @@ QUnit.module('Services', { close++; } }); + await testUtils.nextMicrotaskTick(); view.destroy(); assert.strictEqual(close, 0, "should wait to call onClose method once"); - setTimeout(function () { - assert.strictEqual(close, 1, "should call onClose method once"); - done(); - }); + await testUtils.nextTick(); + assert.strictEqual(close, 1, "should call onClose method once"); }); - QUnit.test('Display a sticky notification with onClose callback', function (assert) { - var done = assert.async(); + QUnit.test('Display a sticky notification with onClose callback', async function (assert) { assert.expect(2); testUtils.mock.unpatch(Notification); @@ -135,7 +125,7 @@ QUnit.module('Services', { _autoCloseDelay: 2500, _animationDelay: 0, }); - var view = createView(this.viewParams); + var view = await createView(this.viewParams); var close = 0; view.call('notification', 'notify', { @@ -146,20 +136,17 @@ QUnit.module('Services', { close++; } }); + await testUtils.nextMicrotaskTick(); assert.strictEqual(close, 0, "should wait to call onClose method once"); testUtils.dom.click($('body .o_notification_manager .o_notification .o_close')); - setTimeout(function () { - assert.strictEqual(close, 1, "should call onClose method once"); - view.destroy(); - done(); - }); + assert.strictEqual(close, 1, "should call onClose method once"); + view.destroy(); }); - QUnit.test('Display a question', function (assert) { - var done = assert.async(); + QUnit.test('Display a question', async function (assert) { assert.expect(8); - var view = createView(this.viewParams); + var view = await createView(this.viewParams); function notification (inc) { return { title: 'a' + inc, @@ -187,9 +174,11 @@ QUnit.module('Services', { view.call('notification', 'notify', notification(0)); view.call('notification', 'notify', notification(1)); view.call('notification', 'notify', notification(2)); + await testUtils.nextTick(); var $notification = $('body .o_notification_manager .o_notification'); - assert.strictEqual($notification.eq(0).find('.o_close').length, 1, "should display the close button in notification"); + assert.containsOnce($notification.eq(0), '.o_close', + "should display the close button in notification"); assert.strictEqual(_.str.trim($notification.eq(0).html().replace(/\s+/g, ' ')), "<a aria-label=\"Close\" class=\"fa fa-times o_close\" href=\"#\" title=\"Close\"></a> <div class=\"o_notification_title\"> <span role=\"img\" aria-label=\"Notification undefined\" class=\"o_icon fa fa-3x fa-question-circle-o\" title=\"Notification undefined\"></span> a0 </div> <div class=\"o_notification_content\">b0</div> <div class=\"o_buttons\"> <button class=\"btn btn-primary\" type=\"button\"> <span>accept0</span> </button><button class=\"btn btn-secondary\" type=\"button\"> <span>refuse0</span> </button> </div>", "should display notification"); @@ -198,17 +187,14 @@ QUnit.module('Services', { testUtils.dom.click($notification.find('.o_buttons button:contains(refuse1)')); testUtils.dom.click($notification.eq(2).find('.o_close')); - setTimeout(function () { - assert.strictEqual($notification.is(':hidden'), true, "should hide the notification"); - assert.strictEqual($('body .o_notification_manager .o_notification').length, 0, "should destroy the notification"); - assert.verifySteps(['accept0', 'refuse1', 'close2']); - view.destroy(); - done(); - }); + assert.strictEqual($notification.is(':hidden'), true, "should hide the notification"); + assert.strictEqual($('body .o_notification_manager .o_notification').length, + 0, "should destroy the notification"); + assert.verifySteps(['accept0', 'refuse1', 'close2']); + view.destroy(); }); - QUnit.test('call close notification service', function (assert) { - var done = assert.async(); + QUnit.test('call close notification service', async function (assert) { assert.expect(2); testUtils.mock.unpatch(Notification); @@ -216,7 +202,7 @@ QUnit.module('Services', { _autoCloseDelay: 2500, _animationDelay: 0, }); - var view = createView(this.viewParams); + var view = await createView(this.viewParams); var close = 0; var notificationId0 = view.call('notification', 'notify', { @@ -234,20 +220,18 @@ QUnit.module('Services', { close++; } }); + await testUtils.nextTick(); view.call('notification', 'close', notificationId0); view.call('notification', 'close', notificationId1); + await testUtils.nextTick(); - setTimeout(function () { - assert.strictEqual($('body .o_notification_manager .o_notification').length, 0, "should destroy the notifications"); - assert.strictEqual(close, 2, "should call onClose method twice"); - view.destroy(); - done(); - }); + assert.strictEqual($('body .o_notification_manager .o_notification').length, 0, "should destroy the notifications"); + assert.strictEqual(close, 2, "should call onClose method twice"); + view.destroy(); }); - QUnit.test('Display a custom notification', function (assert) { - var done = assert.async(); + QUnit.test('Display a custom notification', async function (assert) { assert.expect(3); var Custom = Notification.extend({ @@ -263,21 +247,17 @@ QUnit.module('Services', { }, }); - var view = createView(this.viewParams); + var view = await createView(this.viewParams); view.call('notification', 'notify', { Notification: Custom, customParams: true, }); - assert.strictEqual( - $('body .o_notification_manager .o_notification:contains(Custom)').length, 1, + await testUtils.nextMicrotaskTick(); + assert.containsOnce($('body'), '.o_notification_manager .o_notification:contains(Custom)', "should display the notification"); view.destroy(); - setTimeout(function () { - assert.strictEqual( - $('body .o_notification_manager .o_notification').length, 0, - "should destroy the notification"); - done(); - }); + assert.containsNone($('body'), '.o_notification_manager .o_notification', + "should destroy the notification"); }); });}); diff --git a/addons/web/static/tests/tools/debug_manager_tests.js b/addons/web/static/tests/tools/debug_manager_tests.js index fb454aae0d1..0de3d5857e0 100644 --- a/addons/web/static/tests/tools/debug_manager_tests.js +++ b/addons/web/static/tests/tools/debug_manager_tests.js @@ -8,7 +8,7 @@ var createDebugManager = testUtils.createDebugManager; QUnit.module('DebugManager', {}, function () { - QUnit.test("list: edit view menu item", function (assert) { + QUnit.test("list: edit view menu item", async function (assert) { assert.expect(3); var debugManager = createDebugManager(); @@ -28,7 +28,8 @@ QUnit.module('DebugManager', {}, function () { var view = { viewType: "list", }; - debugManager.update('action', action, view); + await testUtils.nextTick(); + await debugManager.update('action', action, view); var $editView = debugManager.$('a[data-action=edit][data-model="ir.ui.view"]'); assert.strictEqual($editView.length, 1, "should have edit view menu item"); @@ -39,7 +40,7 @@ QUnit.module('DebugManager', {}, function () { debugManager.destroy(); }); - QUnit.test("form: Manage Attachments option", function (assert) { + QUnit.test("form: Manage Attachments option", async function (assert) { assert.expect(3); var debugManager = createDebugManager({ @@ -59,8 +60,7 @@ QUnit.module('DebugManager', {}, function () { }, }, }); - - debugManager.appendTo($('#qunit-fixture')); + await debugManager.appendTo($('#qunit-fixture')); // Simulate update debug manager from web client var action = { @@ -79,19 +79,19 @@ QUnit.module('DebugManager', {}, function () { return [5]; }, }; - debugManager.update('action', action, view); + await debugManager.update('action', action, view); var $attachmentMenu = debugManager.$('a[data-action=get_attachments]'); assert.strictEqual($attachmentMenu.length, 1, "should have Manage Attachments menu item"); assert.strictEqual($attachmentMenu.text().trim(), "Manage Attachments", "should have correct menu item text"); - testUtils.dom.click(debugManager.$('> a')); // open dropdown - testUtils.dom.click($attachmentMenu); + await testUtils.dom.click(debugManager.$('> a')); // open dropdown + await testUtils.dom.click($attachmentMenu); debugManager.destroy(); }); - QUnit.test("Debug: Set defaults with right model", function (assert) { + QUnit.test("Debug: Set defaults with right model", async function (assert) { assert.expect(2); /* Click on debug > set default, @@ -115,7 +115,7 @@ QUnit.module('DebugManager', {}, function () { }, }; - var form = testUtils.createView({ + var form = await testUtils.createView({ View: FormView, model: 'partner', data: data, @@ -132,13 +132,13 @@ QUnit.module('DebugManager', {}, function () { if (route == "/web/dataset/call_kw/ir.default/set") { assert.deepEqual(args.args, ["partner", "foo", "yop", true, true, false], 'Model, field, value and booleans for current user/company should have been passed'); - return $.when(); + return Promise.resolve(); } return this._super.apply(this, arguments); } }); - debugManager.appendTo($('#qunit-fixture')); + await debugManager.appendTo($('#qunit-fixture')); // Simulate update debug manager from web client var action = { @@ -155,18 +155,18 @@ QUnit.module('DebugManager', {}, function () { }; // We are all set - debugManager.update('action', action, form); + await debugManager.update('action', action, form); // click on set_defaults dropdown - testUtils.dom.click(debugManager.$('> a')); // open dropdown - testUtils.dom.click(debugManager.$('a[data-action="set_defaults"]')); + await testUtils.dom.click(debugManager.$('> a')); // open dropdown + await testUtils.dom.click(debugManager.$('a[data-action="set_defaults"]')); var $modal = $('.modal-content'); assert.strictEqual($modal.length, 1, 'One modal present'); $modal.find('select[id=formview_default_fields] option[value=foo]').prop('selected', true); // Save - testUtils.dom.click($modal.find('.modal-footer button').eq(1)); + await testUtils.dom.click($modal.find('.modal-footer button').eq(1)); form.destroy(); debugManager.destroy(); diff --git a/addons/web/static/tests/views/abstract_controller_tests.js b/addons/web/static/tests/views/abstract_controller_tests.js index f9045645bc6..e0fa433b13c 100644 --- a/addons/web/static/tests/views/abstract_controller_tests.js +++ b/addons/web/static/tests/views/abstract_controller_tests.js @@ -1,89 +1,82 @@ -odoo.define("base.abstract_controller_tests", function(require) { - "use strict"; +odoo.define("base.abstract_controller_tests", function (require) { +"use strict"; - var testUtils = require("web.test_utils"); - var createView = testUtils.createView; - var BasicView = require("web.BasicView"); - var BasicRenderer = require("web.BasicRenderer"); +var testUtils = require("web.test_utils"); +var createView = testUtils.createView; +var BasicView = require("web.BasicView"); +var BasicRenderer = require("web.BasicRenderer"); - function getHtmlRenderer(html) { - return BasicRenderer.extend({ - start: function() { - this.$el.html(html); - return this._super.apply(this, arguments); - } - }); - } - function getHtmlView(html, viewType) { - viewType = viewType || "test"; - return BasicView.extend({ - viewType: viewType, - config: _.extend({}, BasicView.prototype.config, { - Renderer: getHtmlRenderer(html) - }) - }); - } +function getHtmlRenderer(html) { + return BasicRenderer.extend({ + start: function () { + this.$el.html(html); + return this._super.apply(this, arguments); + } + }); +} +function getHtmlView(html, viewType) { + viewType = viewType || "test"; + return BasicView.extend({ + viewType: viewType, + config: _.extend({}, BasicView.prototype.config, { + Renderer: getHtmlRenderer(html) + }) + }); +} - QUnit.module( - "Views", - { - beforeEach: function() { - this.data = { - test_model: { - fields: {}, - records: [] - } - }; +QUnit.module("Views", { + beforeEach: function () { + this.data = { + test_model: { + fields: {}, + records: [] } - }, - function() { - - QUnit.module('AbstractController'); - - QUnit.test( - 'click on a a[type="action"] child triggers the correct action', - function(assert) { - assert.expect(7); + }; + } +}, function () { + QUnit.module('AbstractController'); - var html = - "<div>" + - '<a name="a1" type="action" class="simple">simple</a>' + - '<a name="a2" type="action" class="with-child">' + - "<span>child</input>" + - "</a>" + - '<a type="action" data-model="foo" data-method="bar" class="method">method</a>' + - '<a type="action" data-model="foo" data-res-id="42" class="descr">descr</a>' + - '<a type="action" data-model="foo" class="descr2">descr2</a>' + - "</div>"; + QUnit.test('click on a a[type="action"] child triggers the correct action', async function (assert) { + assert.expect(7); - var view = createView({ - View: getHtmlView(html, "test"), - data: this.data, - model: "test_model", - arch: "<test/>", - intercepts: { - do_action: function (event) { - assert.step(event.data.action.name || event.data.action); - } - }, - mockRPC: function (route, args) { - if (args.model === 'foo' && args.method === 'bar') { - assert.step("method"); - return $.when({name: 'method'}); - } - return this._super.apply(this, arguments); - } - }); - testUtils.dom.click(view.$(".simple")); - testUtils.dom.click(view.$(".with-child span")); - testUtils.dom.click(view.$(".method")); - testUtils.dom.click(view.$(".descr")); - testUtils.dom.click(view.$(".descr2")); - assert.verifySteps(["a1", "a2", "method", "method", "descr", "descr2"]); + var html = + "<div>" + + '<a name="a1" type="action" class="simple">simple</a>' + + '<a name="a2" type="action" class="with-child">' + + "<span>child</input>" + + "</a>" + + '<a type="action" data-model="foo" data-method="bar" class="method">method</a>' + + '<a type="action" data-model="foo" data-res-id="42" class="descr">descr</a>' + + '<a type="action" data-model="foo" class="descr2">descr2</a>' + + "</div>"; - view.destroy(); + var view = await createView({ + View: getHtmlView(html, "test"), + data: this.data, + model: "test_model", + arch: "<test/>", + intercepts: { + do_action: function (event) { + assert.step(event.data.action.name || event.data.action); } - ); - } + }, + mockRPC: function (route, args) { + if (args.model === 'foo' && args.method === 'bar') { + assert.step("method"); + return Promise.resolve({name: 'method'}); + } + return this._super.apply(this, arguments); + } + }); + await testUtils.dom.click(view.$(".simple")); + await testUtils.dom.click(view.$(".with-child span")); + await testUtils.dom.click(view.$(".method")); + await testUtils.dom.click(view.$(".descr")); + await testUtils.dom.click(view.$(".descr2")); + assert.verifySteps(["a1", "a2", "method", "method", "descr", "descr2"]); + + view.destroy(); + } ); }); +}); diff --git a/addons/web/static/tests/views/abstract_view_banner_tests.js b/addons/web/static/tests/views/abstract_view_banner_tests.js index 21532ed60d9..ab668dd8c0d 100644 --- a/addons/web/static/tests/views/abstract_view_banner_tests.js +++ b/addons/web/static/tests/views/abstract_view_banner_tests.js @@ -5,7 +5,7 @@ var AbstractRenderer = require('web.AbstractRenderer'); var AbstractView = require('web.AbstractView'); var testUtils = require('web.test_utils'); -var createAsyncView = testUtils.createAsyncView; +var createView = testUtils.createView; var TestRenderer = AbstractRenderer.extend({ _renderView: function () { @@ -48,7 +48,7 @@ QUnit.module('Views', { '<div class="hello_banner"/>' + '</div>'; - createAsyncView({ + createView({ View: TestView, model: 'test_model', data: this.data, @@ -56,7 +56,7 @@ QUnit.module('Views', { mockRPC: function (route, args) { if (route === '/module/hello_banner') { assert.step(route); - return $.when({html: banner_html}); + return Promise.resolve({html: banner_html}); } return this._super(route, args); }, diff --git a/addons/web/static/tests/views/abstract_view_tests.js b/addons/web/static/tests/views/abstract_view_tests.js index 3d4cc423ddd..ba819379279 100644 --- a/addons/web/static/tests/views/abstract_view_tests.js +++ b/addons/web/static/tests/views/abstract_view_tests.js @@ -6,7 +6,7 @@ var ajax = require('web.ajax'); var testUtils = require('web.test_utils'); var createActionManager = testUtils.createActionManager; -var createAsyncView = testUtils.createAsyncView; +var createView = testUtils.createView; QUnit.module('Views', { beforeEach: function () { @@ -31,15 +31,15 @@ QUnit.module('Views', { QUnit.module('AbstractView'); - QUnit.test('lazy loading of js libs (in parallel)', function (assert) { + QUnit.test('lazy loading of js libs (in parallel)', async function (assert) { var done = assert.async(); assert.expect(6); - var def = $.Deferred(); + var prom = testUtils.makeTestPromise(); var loadJS = ajax.loadJS; ajax.loadJS = function (url) { assert.step(url); - return def.then(function () { + return prom.then(function () { assert.step(url + ' loaded'); }); }; @@ -47,37 +47,37 @@ QUnit.module('Views', { var View = AbstractView.extend({ jsLibs: [['a', 'b']], }); - createAsyncView({ + createView({ View: View, arch: '<fake/>', data: this.data, model: 'fake_model', }).then(function (view) { - assert.verifySteps(['a', 'b', 'a loaded', 'b loaded'], + assert.verifySteps(['a loaded', 'b loaded'], "should wait for both libs to be loaded"); ajax.loadJS = loadJS; view.destroy(); done(); }); - assert.verifySteps(['a', 'b'], - "both libs should be loaded in parallel"); - def.resolve(); + await testUtils.nextTick(); + assert.verifySteps(['a', 'b'], "both libs should be loaded in parallel"); + prom.resolve(); }); - QUnit.test('lazy loading of js libs (sequentially)', function (assert) { + QUnit.test('lazy loading of js libs (sequentially)', async function (assert) { var done = assert.async(); assert.expect(10); - var defs = { - a: $.Deferred(), - b: $.Deferred(), - c: $.Deferred(), + var proms = { + a: testUtils.makeTestPromise(), + b: testUtils.makeTestPromise(), + c: testUtils.makeTestPromise(), }; var loadJS = ajax.loadJS; ajax.loadJS = function (url) { assert.step(url); - return defs[url].then(function () { + return proms[url].then(function () { assert.step(url + ' loaded'); }); }; @@ -88,34 +88,32 @@ QUnit.module('Views', { 'c', ], }); - createAsyncView({ + createView({ View: View, arch: '<fake/>', data: this.data, model: 'fake_model', }).then(function (view) { - assert.verifySteps(['a', 'b', 'b loaded', 'a loaded', 'c', 'c loaded'], - "should for all libs to be loaded"); + assert.verifySteps(['c loaded'], "should wait for all libs to be loaded"); ajax.loadJS = loadJS; view.destroy(); done(); }); - - assert.verifySteps(['a', 'b'], - "libs 'a' and 'b' should be loaded in parallel"); - defs.b.resolve(); - assert.verifySteps(['a', 'b', 'b loaded'], - "should wait for 'a' and 'b' to be loaded before loading 'c'"); - defs.a.resolve(); - assert.verifySteps(['a', 'b', 'b loaded', 'a loaded', 'c'], - "should load 'c' when 'a' and 'b' are loaded"); - defs.c.resolve(); + await testUtils.nextTick(); + assert.verifySteps(['a', 'b'], "libs 'a' and 'b' should be loaded in parallel"); + await proms.b.resolve(); + await testUtils.nextTick(); + assert.verifySteps(['b loaded'], "should wait for 'a' and 'b' to be loaded before loading 'c'"); + await proms.a.resolve(); + await testUtils.nextTick(); + assert.verifySteps(['a loaded', 'c'], "should load 'c' when 'a' and 'b' are loaded"); + await proms.c.resolve(); }); - QUnit.test('group_by from context can be a string, instead of a list of strings', function (assert) { + QUnit.test('group_by from context can be a string, instead of a list of strings', async function (assert) { assert.expect(1); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: [{ id: 1, name: 'Foo', @@ -139,7 +137,7 @@ QUnit.module('Views', { }, }); - actionManager.doAction(1); + await actionManager.doAction(1); actionManager.destroy(); }); diff --git a/addons/web/static/tests/views/basic_model_tests.js b/addons/web/static/tests/views/basic_model_tests.js index bf5261c5b6e..e19cadea2cf 100644 --- a/addons/web/static/tests/views/basic_model_tests.js +++ b/addons/web/static/tests/views/basic_model_tests.js @@ -1,92 +1,92 @@ odoo.define('web.basic_model_tests', function (require) { -"use strict"; - -var BasicModel = require('web.BasicModel'); -var testUtils = require('web.test_utils'); - -var createModel = testUtils.createModel; - -QUnit.module('Views', { - beforeEach: function () { - this.data = { - partner: { - fields: { - display_name: {string: "STRING", type: 'char'}, - total: {string: "Total", type: 'integer'}, - foo: {string: "Foo", type: 'char'}, - bar: {string: "Bar", type: 'integer'}, - qux: {string: "Qux", type: 'many2one', relation: 'partner'}, - product_id: {string: "Favorite product", type: 'many2one', relation: 'product'}, - product_ids: {string: "Favorite products", type: 'one2many', relation: 'product'}, - category: {string: "Category M2M", type: 'many2many', relation: 'partner_type'}, - date: {string: "Date Field", type: 'date'}, - reference: {string: "Reference Field", type: 'reference', selection: [["product", "Product"], ["partner_type", "Partner Type"], ["partner", "Partner"]]}, + "use strict"; + + var BasicModel = require('web.BasicModel'); + var testUtils = require('web.test_utils'); + + var createModel = testUtils.createModel; + + QUnit.module('Views', { + beforeEach: function () { + this.data = { + partner: { + fields: { + display_name: { string: "STRING", type: 'char' }, + total: { string: "Total", type: 'integer' }, + foo: { string: "Foo", type: 'char' }, + bar: { string: "Bar", type: 'integer' }, + qux: { string: "Qux", type: 'many2one', relation: 'partner' }, + product_id: { string: "Favorite product", type: 'many2one', relation: 'product' }, + product_ids: { string: "Favorite products", type: 'one2many', relation: 'product' }, + category: { string: "Category M2M", type: 'many2many', relation: 'partner_type' }, + date: { string: "Date Field", type: 'date' }, + reference: { string: "Reference Field", type: 'reference', selection: [["product", "Product"], ["partner_type", "Partner Type"], ["partner", "Partner"]] }, + }, + records: [ + { id: 1, foo: 'blip', bar: 1, product_id: 37, category: [12], display_name: "first partner", date: "2017-01-25" }, + { id: 2, foo: 'gnap', bar: 2, product_id: 41, display_name: "second partner" }, + ], + onchanges: {}, }, - records: [ - {id: 1, foo: 'blip', bar: 1, product_id: 37, category: [12], display_name: "first partner", date: "2017-01-25"}, - {id: 2, foo: 'gnap', bar: 2, product_id: 41, display_name: "second partner"}, - ], - onchanges: {}, - }, - product: { - fields: { - name: {string: "Product Name", type: "char"} + product: { + fields: { + name: { string: "Product Name", type: "char" } + }, + records: [ + { id: 37, display_name: "xphone" }, + { id: 41, display_name: "xpad" } + ] }, - records: [ - {id: 37, display_name: "xphone"}, - {id: 41, display_name: "xpad"} - ] - }, - partner_type: { - fields: { - display_name: {string: "Partner Type", type: "char"}, - date: {string: "Date Field", type: 'date'}, + partner_type: { + fields: { + display_name: { string: "Partner Type", type: "char" }, + date: { string: "Date Field", type: 'date' }, + }, + records: [ + { id: 12, display_name: "gold", date: "2017-01-25" }, + { id: 14, display_name: "silver" }, + { id: 15, display_name: "bronze" } + ] }, - records: [ - {id: 12, display_name: "gold", date: "2017-01-25"}, - {id: 14, display_name: "silver"}, - {id: 15, display_name: "bronze"} - ] - }, - }; - - // add related fields to category. - this.data.partner.fields.category.relatedFields = - $.extend(true, {}, this.data.partner_type.fields); - this.params = { - res_id: 2, - modelName: 'partner', - fields: this.data.partner.fields, - }; - } -}, function () { - QUnit.module('BasicModel'); - - QUnit.test('can load a record', function (assert) { - assert.expect(7); - - this.params.fieldNames = ['foo']; - this.params.context = {active_field: 2}; - - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - assert.deepEqual(args.kwargs.context, { - active_field: 2, - bin_size: true, - someKey: 'some value', - }, "should have sent the correct context"); - return this._super.apply(this, arguments); - }, - session: { - user_context: {someKey: 'some value'}, - } - }); + }; - assert.strictEqual(model.get(1), null, "should return null for non existing key"); + // add related fields to category. + this.data.partner.fields.category.relatedFields = + $.extend(true, {}, this.data.partner_type.fields); + this.params = { + res_id: 2, + modelName: 'partner', + fields: this.data.partner.fields, + }; + }, + }, function () { + QUnit.module('BasicModel'); + + QUnit.test('can load a record', async function (assert) { + assert.expect(7); + + this.params.fieldNames = ['foo']; + this.params.context = { active_field: 2 }; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + assert.deepEqual(args.kwargs.context, { + active_field: 2, + bin_size: true, + someKey: 'some value', + }, "should have sent the correct context"); + return this._super.apply(this, arguments); + }, + session: { + user_context: { someKey: 'some value' }, + } + }); - model.load(this.params).then(function (resultID) { + assert.strictEqual(model.get(1), null, "should return null for non existing key"); + + var resultID = await model.load(this.params); // it is a string, because it is used as a key in an object assert.strictEqual(typeof resultID, 'string', "result should be a valid id"); @@ -95,656 +95,637 @@ QUnit.module('Views', { assert.strictEqual(record.type, 'record', "should be of type 'record'"); assert.strictEqual(record.data.foo, "gnap", "should correctly read value"); assert.strictEqual(record.data.bar, undefined, "should not fetch the field 'bar'"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('rejects loading a record with invalid id', function (assert) { - assert.expect(1); + QUnit.test('rejects loading a record with invalid id', async function (assert) { + assert.expect(1); - this.params.res_id = 99; + this.params.res_id = 99; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); + try { + await model.load(this.params); + } + catch (e) { + assert.ok("load should return a rejected deferred for an invalid id"); + } - model.load(this.params).always(function (error) { - assert.strictEqual(this.state(), 'rejected', - "load should return a rejected deferred for an invalid id") + model.destroy(); }); - model.destroy(); - }); - QUnit.test('notify change with many2one', function (assert) { - assert.expect(2); + QUnit.test('notify change with many2one', async function (assert) { + assert.expect(2); - this.params.fieldNames = ['foo', 'qux']; + this.params.fieldNames = ['foo', 'qux']; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.qux, false, "qux field should be false"); - model.notifyChanges(resultID, {qux: {id: 1, display_name: "hello"}}); + await model.notifyChanges(resultID, { qux: { id: 1, display_name: "hello" } }); record = model.get(resultID); assert.strictEqual(record.data.qux.data.id, 1, "qux field should be 1"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('notify change on many2one: unset and reset same value', function (assert) { - assert.expect(3); + QUnit.test('notify change on many2one: unset and reset same value', async function (assert) { + assert.expect(3); - this.data.partner.records[1].qux = 1; + this.data.partner.records[1].qux = 1; - this.params.fieldNames = ['qux']; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + this.params.fieldNames = ['qux']; + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.qux.data.id, 1, "qux value should be 1"); - model.notifyChanges(resultID, {qux: false}); + await model.notifyChanges(resultID, { qux: false }); record = model.get(resultID); assert.strictEqual(record.data.qux, false, "qux should be unset"); - model.notifyChanges(resultID, {qux: {id: 1, display_name: 'second_partner'}}); + await model.notifyChanges(resultID, { qux: { id: 1, display_name: 'second_partner' } }); record = model.get(resultID); assert.strictEqual(record.data.qux.data.id, 1, "qux value should be 1 again"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('write on a many2one', function (assert) { - assert.expect(4); - var self = this; + QUnit.test('write on a many2one', async function (assert) { + assert.expect(4); + var self = this; - this.params.fieldNames = ['product_id']; + this.params.fieldNames = ['product_id']; - var rpcCount = 0; + var rpcCount = 0; - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - rpcCount++; - return this._super(route, args); - }, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + rpcCount++; + return this._super(route, args); + }, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.product_id.data.display_name, 'xpad', "should be initialized with correct value"); - model.notifyChanges(resultID, {product_id: {id: 37, display_name: 'xphone'}}); + await model.notifyChanges(resultID, { product_id: { id: 37, display_name: 'xphone' } }); record = model.get(resultID); assert.strictEqual(record.data.product_id.data.display_name, 'xphone', "should be changed with correct value"); - model.save(resultID); + await model.save(resultID); assert.strictEqual(self.data.partner.records[1].product_id, 37, "should have really saved the data"); assert.strictEqual(rpcCount, 3, "should have done 3 rpc: 1 read, 1 write, 1 read"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('basic onchange', function (assert) { - assert.expect(5); - - this.data.partner.fields.foo.onChange = true; - this.data.partner.onchanges.foo = function (obj) { - obj.bar = obj.foo.length; - }; - - this.params.fieldNames = ['foo', 'bar']; - this.params.context = {hello: 'world'}; - - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - if (args.method === 'onchange') { - var context = args.args[4]; - assert.deepEqual(context, {hello: 'world'}, - "context should be sent by the onchange"); - } - return this._super(route, args); - }, - }); + QUnit.test('basic onchange', async function (assert) { + assert.expect(5); + + this.data.partner.fields.foo.onChange = true; + this.data.partner.onchanges.foo = function (obj) { + obj.bar = obj.foo.length; + }; - model.load(this.params).then(function (resultID) { + this.params.fieldNames = ['foo', 'bar']; + this.params.context = { hello: 'world' }; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'onchange') { + var context = args.args[4]; + assert.deepEqual(context, { hello: 'world' }, + "context should be sent by the onchange"); + } + return this._super(route, args); + }, + }); + + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.foo, 'gnap', "foo field is properly initialized"); assert.strictEqual(record.data.bar, 2, "bar field is properly initialized"); - model.notifyChanges(resultID, {foo: 'mary poppins'}); + await model.notifyChanges(resultID, { foo: 'mary poppins' }); record = model.get(resultID); assert.strictEqual(record.data.foo, 'mary poppins', "onchange has been applied"); assert.strictEqual(record.data.bar, 12, "onchange has been applied"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('onchange with a many2one', function (assert) { - assert.expect(5); + QUnit.test('onchange with a many2one', async function (assert) { + assert.expect(5); - this.data.partner.fields.product_id.onChange = true; - this.data.partner.onchanges.product_id = function (obj) { - if (obj.product_id === 37) { - obj.foo = "space lollipop"; - } - }; + this.data.partner.fields.product_id.onChange = true; + this.data.partner.onchanges.product_id = function (obj) { + if (obj.product_id === 37) { + obj.foo = "space lollipop"; + } + }; - this.params.fieldNames = ['foo', 'product_id']; + this.params.fieldNames = ['foo', 'product_id']; - var rpcCount = 0; + var rpcCount = 0; - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - if (args.method === 'onchange') { - assert.strictEqual(args.args[2], "product_id", - "should send the only changed field as a string, not a list"); - } - rpcCount++; - return this._super(route, args); - }, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'onchange') { + assert.strictEqual(args.args[2], "product_id", + "should send the only changed field as a string, not a list"); + } + rpcCount++; + return this._super(route, args); + }, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.foo, 'gnap', "foo field is properly initialized"); assert.strictEqual(record.data.product_id.data.id, 41, "product_id field is properly initialized"); - model.notifyChanges(resultID, {product_id: {id: 37, display_name: 'xphone'}}); + await model.notifyChanges(resultID, { product_id: { id: 37, display_name: 'xphone' } }); record = model.get(resultID); assert.strictEqual(record.data.foo, 'space lollipop', "onchange has been applied"); assert.strictEqual(rpcCount, 2, "should have done 2 rpc: 1 read and 1 onchange"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('onchange on a one2many not in view (fieldNames)', function (assert) { - assert.expect(6); + QUnit.test('onchange on a one2many not in view (fieldNames)', async function (assert) { + assert.expect(6); - this.data.partner.fields.foo.onChange = true; - this.data.partner.onchanges.foo = function (obj) { - obj.bar = obj.foo.length; - obj.product_ids = []; - }; + this.data.partner.fields.foo.onChange = true; + this.data.partner.onchanges.foo = function (obj) { + obj.bar = obj.foo.length; + obj.product_ids = []; + }; - this.params.fieldNames = ['foo']; + this.params.fieldNames = ['foo']; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.foo, 'gnap', "foo field is properly initialized"); assert.strictEqual(record.data.bar, undefined, "bar field is not loaded"); assert.strictEqual(record.data.product_ids, undefined, "product_ids field is not loaded"); - model.notifyChanges(resultID, {foo: 'mary poppins'}); + await model.notifyChanges(resultID, { foo: 'mary poppins' }); record = model.get(resultID); assert.strictEqual(record.data.foo, 'mary poppins', "onchange has been applied"); assert.strictEqual(record.data.bar, 12, "onchange has been applied"); assert.strictEqual(record.data.product_ids, undefined, "onchange on product_ids (one2many) has not been applied"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('notifyChange on a one2many', function (assert) { - assert.expect(9); + QUnit.test('notifyChange on a one2many', async function (assert) { + assert.expect(9); - this.data.partner.records[1].product_ids = [37]; - this.params.fieldNames = ['product_ids']; + this.data.partner.records[1].product_ids = [37]; + this.params.fieldNames = ['product_ids']; - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - if (args.method === 'name_get') { - assert.strictEqual(args.model, 'product'); - } - return this._super(route, args); - }, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'name_get') { + assert.strictEqual(args.model, 'product'); + } + return this._super(route, args); + }, + }); - var o2mParams = { - modelName: 'product', - fields: this.data.product.fields, - fieldNames: ['display_name'], - }; - model.load(this.params).then(function (resultID) { - model.load(o2mParams).then(function (newRecordID) { - var record = model.get(resultID); - var x2mListID = record.data.product_ids.id; + var o2mParams = { + modelName: 'product', + fields: this.data.product.fields, + fieldNames: ['display_name'], + }; + var resultID = await model.load(this.params); + var newRecordID = await model.load(o2mParams); + var record = model.get(resultID); + var x2mListID = record.data.product_ids.id; - assert.strictEqual(record.data.product_ids.count, 1, - "there should be one record in the relation"); + assert.strictEqual(record.data.product_ids.count, 1, + "there should be one record in the relation"); - // trigger a 'ADD' command - model.notifyChanges(resultID, {product_ids: {operation: 'ADD', id: newRecordID}}); + // trigger a 'ADD' command + await model.notifyChanges(resultID, { product_ids: { operation: 'ADD', id: newRecordID } }); - assert.deepEqual(model.localData[x2mListID]._changes, [{ - operation: 'ADD', id: newRecordID, - }], "_changes should be correct"); - record = model.get(resultID); - assert.strictEqual(record.data.product_ids.count, 2, - "there should be two records in the relation"); + assert.deepEqual(model.localData[x2mListID]._changes, [{ + operation: 'ADD', id: newRecordID, + }], "_changes should be correct"); + record = model.get(resultID); + assert.strictEqual(record.data.product_ids.count, 2, + "there should be two records in the relation"); - // trigger a 'UPDATE' command - model.notifyChanges(resultID, {product_ids: {operation: 'UPDATE', id: newRecordID}}); + // trigger a 'UPDATE' command + await model.notifyChanges(resultID, { product_ids: { operation: 'UPDATE', id: newRecordID } }); - assert.deepEqual(model.localData[x2mListID]._changes, [{ - operation: 'ADD', id: newRecordID, - }, { - operation: 'UPDATE', id: newRecordID, - }], "_changes should be correct"); - record = model.get(resultID); - assert.strictEqual(record.data.product_ids.count, 2, - "there should be two records in the relation"); - - // trigger a 'DELETE' command on the existing record - var existingRecordID = record.data.product_ids.data[0].id; - model.notifyChanges(resultID, {product_ids: {operation: 'DELETE', ids: [existingRecordID]}}); - - assert.deepEqual(model.localData[x2mListID]._changes, [{ - operation: 'ADD', id: newRecordID, - }, { - operation: 'UPDATE', id: newRecordID, - }, { - operation: 'DELETE', id: existingRecordID, - }], - "_changes should be correct"); - record = model.get(resultID); - assert.strictEqual(record.data.product_ids.count, 1, - "there should be one record in the relation"); + assert.deepEqual(model.localData[x2mListID]._changes, [{ + operation: 'ADD', id: newRecordID, + }, { + operation: 'UPDATE', id: newRecordID, + }], "_changes should be correct"); + record = model.get(resultID); + assert.strictEqual(record.data.product_ids.count, 2, + "there should be two records in the relation"); - // trigger a 'DELETE' command on the new record - model.notifyChanges(resultID, {product_ids: {operation: 'DELETE', ids: [newRecordID]}}); + // trigger a 'DELETE' command on the existing record + var existingRecordID = record.data.product_ids.data[0].id; + await model.notifyChanges(resultID, { product_ids: { operation: 'DELETE', ids: [existingRecordID] } }); - assert.deepEqual(model.localData[x2mListID]._changes, [{ - operation: 'DELETE', id: existingRecordID, - }], "_changes should be correct"); - record = model.get(resultID); - assert.strictEqual(record.data.product_ids.count, 0, - "there should be no record in the relation"); - }); + assert.deepEqual(model.localData[x2mListID]._changes, [{ + operation: 'ADD', id: newRecordID, + }, { + operation: 'UPDATE', id: newRecordID, + }, { + operation: 'DELETE', id: existingRecordID, + }], + "_changes should be correct"); + record = model.get(resultID); + assert.strictEqual(record.data.product_ids.count, 1, + "there should be one record in the relation"); + + // trigger a 'DELETE' command on the new record + await model.notifyChanges(resultID, { product_ids: { operation: 'DELETE', ids: [newRecordID] } }); + + assert.deepEqual(model.localData[x2mListID]._changes, [{ + operation: 'DELETE', id: existingRecordID, + }], "_changes should be correct"); + record = model.get(resultID); + assert.strictEqual(record.data.product_ids.count, 0, + "there should be no record in the relation"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('notifyChange on a many2one, without display_name', function (assert) { - assert.expect(3); + QUnit.test('notifyChange on a many2one, without display_name', async function (assert) { + assert.expect(3); - this.params.fieldNames = ['product_id']; + this.params.fieldNames = ['product_id']; - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - if (args.method === 'name_get') { - assert.strictEqual(args.model, 'product'); - } - return this._super(route, args); - }, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'name_get') { + assert.strictEqual(args.model, 'product'); + } + return this._super(route, args); + }, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.product_id.data.display_name, 'xpad', "product_id field is set to xpad"); - model.notifyChanges(resultID, {product_id: {id: 37}}); + await model.notifyChanges(resultID, { product_id: { id: 37 } }); record = model.get(resultID); assert.strictEqual(record.data.product_id.data.display_name, 'xphone', "display_name should have been fetched"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('onchange on a char with an unchanged many2one', function (assert) { - assert.expect(2); + QUnit.test('onchange on a char with an unchanged many2one', async function (assert) { + assert.expect(2); - this.data.partner.fields.foo.onChange = true; - this.data.partner.onchanges.foo = function (obj) { - obj.foo = obj.foo + " alligator"; - }; + this.data.partner.fields.foo.onChange = true; + this.data.partner.onchanges.foo = function (obj) { + obj.foo = obj.foo + " alligator"; + }; - this.params.fieldNames = ['foo', 'product_id']; + this.params.fieldNames = ['foo', 'product_id']; - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - if (args.method === 'onchange') { - assert.strictEqual(args.args[1].product_id, 41, "should send correct value"); - } - return this._super(route, args); - }, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'onchange') { + assert.strictEqual(args.args[1].product_id, 41, "should send correct value"); + } + return this._super(route, args); + }, + }); - model.load(this.params).then(function (resultID) { - model.notifyChanges(resultID, {foo: 'cookie'}); + var resultID = await model.load(this.params); + await model.notifyChanges(resultID, { foo: 'cookie' }); var record = model.get(resultID); assert.strictEqual(record.data.foo, 'cookie alligator', "onchange has been applied"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('onchange on a char with another many2one not set to a value', function (assert) { - assert.expect(2); - this.data.partner.records[0].product_id = false; - this.data.partner.fields.foo.onChange = true; - this.data.partner.onchanges.foo = function (obj) { - obj.foo = obj.foo + " alligator"; - }; + QUnit.test('onchange on a char with another many2one not set to a value', async function (assert) { + assert.expect(2); + this.data.partner.records[0].product_id = false; + this.data.partner.fields.foo.onChange = true; + this.data.partner.onchanges.foo = function (obj) { + obj.foo = obj.foo + " alligator"; + }; - this.params.fieldNames = ['foo', 'product_id']; - this.params.res_id = 1; + this.params.fieldNames = ['foo', 'product_id']; + this.params.res_id = 1; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.product_id, false, "product_id is not set"); - model.notifyChanges(resultID, {foo: 'cookie'}); + await model.notifyChanges(resultID, { foo: 'cookie' }); record = model.get(resultID); assert.strictEqual(record.data.foo, 'cookie alligator', "onchange has been applied"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('can get a many2many', function (assert) { - assert.expect(3); + QUnit.test('can get a many2many', async function (assert) { + assert.expect(3); - this.params.res_id = 1; - this.params.fieldsInfo = { - default: { - category: { - fieldsInfo: {default: {display_name: {}}}, - relatedFields: {display_name: {type: "char"}}, - viewType: 'default', + this.params.res_id = 1; + this.params.fieldsInfo = { + default: { + category: { + fieldsInfo: { default: { display_name: {} } }, + relatedFields: { display_name: { type: "char" } }, + viewType: 'default', + }, }, - }, - }; + }; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.category.data[0].res_id, 12, "should have loaded many2many res_ids"); assert.strictEqual(record.data.category.data[0].data.display_name, "gold", "should have loaded many2many display_name"); - record = model.get(resultID, {raw: true}); + record = model.get(resultID, { raw: true }); assert.deepEqual(record.data.category, [12], "with option raw, category should only return ids"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('can use command add and get many2many value with date field', function (assert) { - assert.expect(2); + QUnit.test('can use command add and get many2many value with date field', async function (assert) { + assert.expect(2); - this.params.fieldsInfo = { - default: { - category: { - fieldsInfo: {default: {date: {}}}, - relatedFields: {date: {type: "date"}}, - viewType: 'default', + this.params.fieldsInfo = { + default: { + category: { + fieldsInfo: { default: { date: {} } }, + relatedFields: { date: { type: "date" } }, + viewType: 'default', + }, }, - }, - }; + }; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var changes = { - category: {operation: 'ADD_M2M', ids: [{id: 12}]} + category: { operation: 'ADD_M2M', ids: [{ id: 12 }] } }; - model.notifyChanges(resultID, changes).then(function () { - var record = model.get(resultID); - assert.strictEqual(record.data.category.data.length, 1, "should have added one category"); - assert.strictEqual(record.data.category.data[0].data.date instanceof moment, - true, "should have a date parsed in a moment object"); - }); + await model.notifyChanges(resultID, changes); + var record = model.get(resultID); + assert.strictEqual(record.data.category.data.length, 1, "should have added one category"); + assert.strictEqual(record.data.category.data[0].data.date instanceof moment, + true, "should have a date parsed in a moment object"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('many2many with ADD_M2M command and context with parent key', function (assert) { - assert.expect(1); + QUnit.test('many2many with ADD_M2M command and context with parent key', async function (assert) { + assert.expect(1); - this.data.partner_type.fields.some_char = {type: "char"}; - this.params.fieldsInfo = { - default: { - category: { - fieldsInfo: {default: {some_char: { context: "{'a': parent.foo}"}}}, - relatedFields: {some_char: {type: "char"}}, - viewType: 'default', + this.data.partner_type.fields.some_char = { type: "char" }; + this.params.fieldsInfo = { + default: { + category: { + fieldsInfo: { default: { some_char: { context: "{'a': parent.foo}" } } }, + relatedFields: { some_char: { type: "char" } }, + viewType: 'default', + }, + foo: {}, }, - foo: {}, - }, - }; + }; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var changes = { - category: {operation: 'ADD_M2M', ids: [{id: 12}]} + category: { operation: 'ADD_M2M', ids: [{ id: 12 }] } }; - model.notifyChanges(resultID, changes).then(function () { - var record = model.get(resultID); - var categoryRecord = record.data.category.data[0]; - assert.deepEqual(categoryRecord.getContext({fieldName: 'some_char'}), {a:'gnap'}, - "should properly evaluate context"); + await model.notifyChanges(resultID, changes); + var record = model.get(resultID); + var categoryRecord = record.data.category.data[0]; + assert.deepEqual(categoryRecord.getContext({ fieldName: 'some_char' }), { a: 'gnap' }, + "should properly evaluate context"); + model.destroy(); + }); + + QUnit.test('can fetch a list', async function (assert) { + assert.expect(4); + + this.params.fieldNames = ['foo']; + this.params.domain = []; + this.params.groupedBy = []; + this.params.res_id = undefined; + this.params.context = { active_field: 2 }; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + assert.strictEqual(args.context.active_field, 2, + "should have sent the correct context"); + return this._super(route, args); + }, }); - }); - model.destroy(); - }); - - QUnit.test('can fetch a list', function (assert) { - assert.expect(4); - - this.params.fieldNames = ['foo']; - this.params.domain = []; - this.params.groupedBy = []; - this.params.res_id = undefined; - this.params.context = {active_field: 2}; - - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - assert.strictEqual(args.context.active_field, 2, - "should have sent the correct context"); - return this._super(route, args); - }, - }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.type, 'list', "record fetched should be a list"); assert.strictEqual(record.data.length, 2, "should have fetched 2 records"); assert.strictEqual(record.data[0].data.foo, 'blip', "first record should have 'blip' in foo field"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('fetch x2manys in list, with not too many rpcs', function (assert) { - assert.expect(3); + QUnit.test('fetch x2manys in list, with not too many rpcs', async function (assert) { + assert.expect(3); - this.data.partner.records[0].category = [12, 15]; - this.data.partner.records[1].category = [12, 14]; + this.data.partner.records[0].category = [12, 15]; + this.data.partner.records[1].category = [12, 14]; - this.params.fieldNames = ['category']; - this.params.domain = []; - this.params.groupedBy = []; - this.params.res_id = undefined; + this.params.fieldNames = ['category']; + this.params.domain = []; + this.params.groupedBy = []; + this.params.res_id = undefined; - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - assert.step(route); - return this._super(route, args); - }, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + assert.step(route); + return this._super(route, args); + }, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data[0].data.category.data.length, 2, "first record should have 2 categories loaded"); assert.verifySteps(["/web/dataset/search_read"], "should have done 2 rpc (searchread and read category)"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('can make a default_record, no onchange', function (assert) { - assert.expect(5); - - this.params.context = {}; - this.params.fieldNames = ['product_id', 'category', 'product_ids']; - this.params.res_id = undefined; - this.params.type = 'record'; - - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - assert.step(args.method); - return this._super(route, args); - }, - }); + QUnit.test('can make a default_record, no onchange', async function (assert) { + assert.expect(5); - model.load(this.params).then(function (resultID) { + this.params.context = {}; + this.params.fieldNames = ['product_id', 'category', 'product_ids']; + this.params.res_id = undefined; + this.params.type = 'record'; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + assert.step(args.method); + return this._super(route, args); + }, + }); + + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.product_id, false, "m2o default value should be false"); assert.deepEqual(record.data.product_ids.data, [], "o2m default should be []"); assert.deepEqual(record.data.category.data, [], "m2m default should be []"); + + assert.verifySteps(['default_get'], + "there should be default_get"); + + model.destroy(); }); - assert.verifySteps(['default_get'], - "there should be default_get"); + QUnit.test('default_get returning a non requested field', async function (assert) { + // 'default_get' returns a default value for the fields given in + // arguments. It should not return a value for fields that have not be + // requested. However, it happens (e.g. res.users), and the webclient + // should not crash when this situation occurs (the field should simply + // be ignored). + assert.expect(2); - model.destroy(); - }); + this.params.context = {}; + this.params.fieldNames = ['category']; + this.params.res_id = undefined; + this.params.type = 'record'; - QUnit.test('default_get returning a non requested field', function (assert) { - // 'default_get' returns a default value for the fields given in - // arguments. It should not return a value for fields that have not be - // requested. However, it happens (e.g. res.users), and the webclient - // should not crash when this situation occurs (the field should simply - // be ignored). - assert.expect(2); - - this.params.context = {}; - this.params.fieldNames = ['category']; - this.params.res_id = undefined; - this.params.type = 'record'; - - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - var result = this._super(route, args); - if (args.method === 'default_get') { - result.product_ids = [[6, 0, [37, 41]]]; - } - return result; - }, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + var result = this._super(route, args); + if (args.method === 'default_get') { + result.product_ids = [[6, 0, [37, 41]]]; + } + return result; + }, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.ok('category' in record.data, "should have processed 'category'"); assert.notOk('product_ids' in record.data, "should have ignored 'product_ids'"); + + model.destroy(); }); - model.destroy(); - }); + QUnit.test('can make a default_record with default relational values', async function (assert) { + assert.expect(7); - QUnit.test('can make a default_record with default relational values', function (assert) { - assert.expect(7); - - this.data.partner.fields.product_id.default = 37; - this.data.partner.fields.product_ids.default = [ - [0, false, {name: 'xmac'}], - [0, false, {name: 'xcloud'}] - ]; - this.data.partner.fields.category.default = [ - [6, false, [12, 14]] - ]; - - this.params.fieldNames = ['product_id', 'category', 'product_ids']; - this.params.res_id = undefined; - this.params.type = 'record'; - this.params.fieldsInfo = { - form: { - category: {}, - product_id: {}, - product_ids: { - fieldsInfo: { - default: { name: {} }, + this.data.partner.fields.product_id.default = 37; + this.data.partner.fields.product_ids.default = [ + [0, false, { name: 'xmac' }], + [0, false, { name: 'xcloud' }] + ]; + this.data.partner.fields.category.default = [ + [6, false, [12, 14]] + ]; + + this.params.fieldNames = ['product_id', 'category', 'product_ids']; + this.params.res_id = undefined; + this.params.type = 'record'; + this.params.fieldsInfo = { + form: { + category: {}, + product_id: {}, + product_ids: { + fieldsInfo: { + default: { name: {} }, + }, + relatedFields: this.data.product.fields, + viewType: 'default', }, - relatedFields: this.data.product.fields, - viewType: 'default', }, - }, - }; - this.params.viewType = 'form'; - - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - assert.step(args.method); - return this._super(route, args); - }, - }); + }; + this.params.viewType = 'form'; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + assert.step(args.method); + return this._super(route, args); + }, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.deepEqual(record.data.product_id.data.display_name, 'xphone', "m2o default should be xphone"); @@ -754,217 +735,211 @@ QUnit.module('Views', { 'xmac', "first o2m default value should be xmac"); assert.deepEqual(record.data.category.res_ids, [12, 14], "m2m default should be [12, 14]"); - }); - assert.verifySteps(['default_get', 'name_get'], - "there should be default_get and name_get"); + assert.verifySteps(['default_get', 'name_get'], + "there should be default_get and name_get"); - model.destroy(); - }); + model.destroy(); + }); - QUnit.test('default_record, with onchange on many2one', function (assert) { - assert.expect(1); + QUnit.test('default_record, with onchange on many2one', async function (assert) { + assert.expect(1); - // the onchange is done by the mockRPC because we want to return a value - // of 'false', which does not work with the mockserver mockOnChange method. - this.data.partner.onchanges.product_id = true; + // the onchange is done by the mockRPC because we want to return a value + // of 'false', which does not work with the mockserver mockOnChange method. + this.data.partner.onchanges.product_id = true; - this.params.context = {}; - this.params.fieldNames = ['product_id']; - this.params.res_id = undefined; - this.params.type = 'record'; + this.params.context = {}; + this.params.fieldNames = ['product_id']; + this.params.res_id = undefined; + this.params.type = 'record'; - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - if (args.method === 'onchange') { - return $.when({value: { product_id: false }}); - } - return this._super(route, args); - }, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'onchange') { + return Promise.resolve({ value: { product_id: false } }); + } + return this._super(route, args); + }, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.product_id, false, "m2o default value should be false"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('default record: batch namegets on same model and res_id', function (assert) { - assert.expect(3); - - var rpcCount = 0; - var fields = this.data.partner.fields; - fields.other_product_id = _.extend({}, fields.product_id); - fields.product_id.default = 37; - fields.other_product_id.default = 41; - - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - rpcCount++; - return this._super(route, args); - }, - }); + QUnit.test('default record: batch namegets on same model and res_id', async function (assert) { + assert.expect(3); + + var rpcCount = 0; + var fields = this.data.partner.fields; + fields.other_product_id = _.extend({}, fields.product_id); + fields.product_id.default = 37; + fields.other_product_id.default = 41; - var params = { - context: {}, - fieldNames: ['other_product_id', 'product_id'], - fields: fields, - modelName: 'partner', - type: 'record', - }; + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + rpcCount++; + return this._super(route, args); + }, + }); + + var params = { + context: {}, + fieldNames: ['other_product_id', 'product_id'], + fields: fields, + modelName: 'partner', + type: 'record', + }; - model.load(params).then(function (resultID) { + var resultID = await model.load(params); var record = model.get(resultID); assert.strictEqual(record.data.product_id.data.display_name, "xphone", "should have fetched correct name"); assert.strictEqual(record.data.other_product_id.data.display_name, "xpad", "should have fetched correct name"); assert.strictEqual(rpcCount, 2, "should have done 2 rpcs: default_get and 1 name_get"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('undoing a change makes the record not dirty', function (assert) { - assert.expect(4); + QUnit.test('undoing a change makes the record not dirty', async function (assert) { + assert.expect(4); - this.params.fieldNames = ['foo']; + this.params.fieldNames = ['foo']; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.foo, "gnap", "foo field should properly be set"); assert.ok(!model.isDirty(resultID), "record should not be dirty"); - model.notifyChanges(resultID, {foo: "hello"}); + await model.notifyChanges(resultID, { foo: "hello" }); assert.ok(model.isDirty(resultID), "record should be dirty"); - model.notifyChanges(resultID, {foo: "gnap"}); + await model.notifyChanges(resultID, { foo: "gnap" }); assert.ok(!model.isDirty(resultID), "record should not be dirty"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('isDirty works correctly on list made empty', function (assert) { - assert.expect(3); + QUnit.test('isDirty works correctly on list made empty', async function (assert) { + assert.expect(3); - this.params.fieldNames = ['category']; - this.params.res_id = 1; + this.params.fieldNames = ['category']; + this.params.res_id = 1; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); var category_value = record.data.category; assert.ok(_.isObject(category_value), "category field should have been fetched"); assert.strictEqual(category_value.data.length, 1, "category field should contain one record"); - model.notifyChanges(resultID, {category: { - operation: 'DELETE', - ids: [category_value.data[0].id], - }}); + await model.notifyChanges(resultID, { + category: { + operation: 'DELETE', + ids: [category_value.data[0].id], + } + }); assert.ok(model.isDirty(resultID), "record should be considered dirty"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('can duplicate a record', function (assert) { - assert.expect(4); + QUnit.test('can duplicate a record', async function (assert) { + assert.expect(4); - this.params.fieldNames = ['foo']; + this.params.fieldNames = ['foo']; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.display_name, "second partner", "record should have correct display name"); assert.strictEqual(record.data.foo, "gnap", "foo should be set to correct value"); - model.duplicateRecord(resultID).then(function (duplicateID) { - var duplicate = model.get(duplicateID); - assert.strictEqual(duplicate.data.display_name, "second partner (copy)", - "record should have been duplicated"); - assert.strictEqual(duplicate.data.foo, "gnap", "foo should be set to correct value"); - }); + var duplicateID = await model.duplicateRecord(resultID); + var duplicate = model.get(duplicateID); + assert.strictEqual(duplicate.data.display_name, "second partner (copy)", + "record should have been duplicated"); + assert.strictEqual(duplicate.data.foo, "gnap", "foo should be set to correct value"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('record with many2one set to some value, then set it to none', function (assert) { - assert.expect(3); + QUnit.test('record with many2one set to some value, then set it to none', async function (assert) { + assert.expect(3); - this.params.fieldNames = ['product_id']; + this.params.fieldNames = ['product_id']; - var self = this; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var self = this; + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.product_id.data.display_name, 'xpad', "product_id should be set"); - model.notifyChanges(resultID, {product_id: false}); + await model.notifyChanges(resultID, { product_id: false }); record = model.get(resultID); assert.strictEqual(record.data.product_id, false, "product_id should not be set"); - model.save(resultID); + await model.save(resultID); assert.strictEqual(self.data.partner.records[1].product_id, false, "should have saved the new product_id value"); - }); - model.destroy(); - }); - - QUnit.test('internal state of groups remains when reloading', function (assert) { - assert.expect(10); - - this.params.fieldNames = ['foo']; - this.params.domain = []; - this.params.limit = 80; - this.params.groupedBy = ['product_id']; - this.params.res_id = undefined; - - var filterEnabled = false; - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - if (args.method === 'read_group' && filterEnabled) { - // as this is not yet supported by the MockServer, simulates - // a read_group that returns empty groups - // this is the case for several models (e.g. project.task - // grouped by stage_id) - return this._super.apply(this, arguments).then(function (result) { - // artificially filter out records of first group - result[0].product_id_count = 0; - return result; - }); - } - return this._super.apply(this, arguments); - }, - }); + model.destroy(); + }); + + QUnit.test('internal state of groups remains when reloading', async function (assert) { + assert.expect(10); + + this.params.fieldNames = ['foo']; + this.params.domain = []; + this.params.limit = 80; + this.params.groupedBy = ['product_id']; + this.params.res_id = undefined; + + var filterEnabled = false; + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'read_group' && filterEnabled) { + // as this is not yet supported by the MockServer, simulates + // a read_group that returns empty groups + // this is the case for several models (e.g. project.task + // grouped by stage_id) + return this._super.apply(this, arguments).then(function (result) { + // artificially filter out records of first group + result[0].product_id_count = 0; + return result; + }); + } + return this._super.apply(this, arguments); + }, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.length, 2, "should have 2 groups"); var groupID = record.data[0].id; assert.strictEqual(model.localData[groupID].parentID, resultID, "parentID should be correctly set on groups"); - model.toggleGroup(groupID); + await model.toggleGroup(groupID); record = model.get(resultID); assert.ok(record.data[0].isOpen, "first group should be open"); @@ -976,7 +951,7 @@ QUnit.module('Views', { // change the limit and offset of the first group model.localData[record.data[0].id].limit = 10; - model.reload(resultID); + await model.reload(resultID); record = model.get(resultID); assert.ok(record.data[0].isOpen, "first group should still be open"); assert.strictEqual(record.data[0].data.length, 1, @@ -987,193 +962,188 @@ QUnit.module('Views', { // filter some records out: the open group should stay open but now // be empty filterEnabled = true; - model.reload(resultID); + await model.reload(resultID); record = model.get(resultID); assert.strictEqual(record.data[0].count, 0, "first group's count should be 0"); assert.strictEqual(record.data[0].data.length, 0, "first group's data should be empty'"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('group on date field with magic grouping method', function (assert) { - assert.expect(1); + QUnit.test('group on date field with magic grouping method', async function (assert) { + assert.expect(1); - this.params.fieldNames = ['foo']; - this.params.groupedBy = ['date:month']; - this.params.res_id = undefined; + this.params.fieldNames = ['foo']; + this.params.groupedBy = ['date:month']; + this.params.res_id = undefined; - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - if (args.method === 'read_group') { - assert.deepEqual(args.kwargs.fields, ['foo', 'date'], - "should have correctly trimmed the magic grouping info from the field name"); - } - return this._super.apply(this, arguments); - }, + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'read_group') { + assert.deepEqual(args.kwargs.fields, ['foo', 'date'], + "should have correctly trimmed the magic grouping info from the field name"); + } + return this._super.apply(this, arguments); + }, + }); + + await model.load(this.params); + model.destroy(); }); - model.load(this.params); - model.destroy(); - }); + QUnit.test('read group when grouped by a selection field', async function (assert) { + assert.expect(5); - QUnit.test('read group when grouped by a selection field', function (assert) { - assert.expect(5); + this.data.partner.fields.selection = { + type: 'selection', + selection: [['a', 'A'], ['b', 'B']], + }; + this.data.partner.records[0].selection = 'a'; - this.data.partner.fields.selection = { - type: 'selection', - selection: [['a', 'A'], ['b', 'B']], - }; - this.data.partner.records[0].selection = 'a'; + var model = createModel({ + Model: BasicModel, + data: this.data, + }); + var params = { + modelName: 'partner', + fields: this.data.partner.fields, + fieldNames: ['foo'], + groupedBy: ['selection'], + }; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); - var params = { - modelName: 'partner', - fields: this.data.partner.fields, - fieldNames: ['foo'], - groupedBy: ['selection'], - }; - - model.load(params).then(function (resultID) { + var resultID = await model.load(params); var dataPoint = model.get(resultID); assert.strictEqual(dataPoint.data.length, 2, "should have two groups"); - var groupFalse = _.findWhere(dataPoint.data, {value: false}); + var groupFalse = _.findWhere(dataPoint.data, { value: false }); assert.ok(groupFalse, "should have a group for value false"); assert.deepEqual(groupFalse.domain, [['selection', '=', false]], "group's domain should be correct"); - var groupA = _.findWhere(dataPoint.data, {value: 'A'}); + var groupA = _.findWhere(dataPoint.data, { value: 'A' }); assert.ok(groupA, "should have a group for value 'a'"); assert.deepEqual(groupA.domain, [['selection', '=', 'a']], "group's domain should be correct"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('create record, then save', function (assert) { - assert.expect(5); - - this.params.fieldNames = ['product_ids']; - this.params.res_id = undefined; - this.params.type = 'record'; - this.params.context = {active_field: 2}; - - var id; - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - if (args.method === 'create') { - // has to be done before the call to _super - assert.notOk('product_ids' in args.args[0], "should not have any value"); - assert.notOk('category' in args.args[0], "should not have other fields"); - - assert.strictEqual(args.kwargs.context.active_field, 2, - "record's context should be correctly passed"); - } - var result = this._super(route, args); - if (args.method === 'create') { - result.then(function (res) { - id = res; - }); - } - return result; - }, - }); + QUnit.test('create record, then save', async function (assert) { + assert.expect(5); + + this.params.fieldNames = ['product_ids']; + this.params.res_id = undefined; + this.params.type = 'record'; + this.params.context = { active_field: 2 }; - model.load(this.params).then(function (resultID) { + var id; + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'create') { + // has to be done before the call to _super + assert.notOk('product_ids' in args.args[0], "should not have any value"); + assert.notOk('category' in args.args[0], "should not have other fields"); + + assert.strictEqual(args.kwargs.context.active_field, 2, + "record's context should be correctly passed"); + } + var result = this._super(route, args); + if (args.method === 'create') { + result.then(function (res) { + id = res; + }); + } + return result; + }, + }); + + var resultID = await model.load(this.params); var record = model.get(resultID); - model.save(record.id, {reload: false}); + await model.save(record.id, { reload: false }); record = model.get(resultID); assert.strictEqual(record.res_id, id, "should have correct id from server"); assert.strictEqual(record.data.id, id, "should have correct id from server"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('write commands on a one2many', function (assert) { - assert.expect(4); + QUnit.test('write commands on a one2many', async function (assert) { + assert.expect(4); - this.data.partner.records[1].product_ids = [37]; + this.data.partner.records[1].product_ids = [37]; - this.params.fieldNames = ['product_ids']; + this.params.fieldNames = ['product_ids']; - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - if (args.method === 'write') { - assert.deepEqual(args.args[0], [2], "should write on res_id = 2"); - var commands = args.args[1].product_ids; - assert.deepEqual(commands[0], [4, 37, false], "first command should be a 4"); - // TO DO: uncomment next line - // assert.strictEqual(commands[1], [0, false, {name: "toy"}], "second command should be a 0"); - assert.strictEqual(commands[1][0], 0, "second command should be a 0"); - } - return this._super(route, args); - }, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'write') { + assert.deepEqual(args.args[0], [2], "should write on res_id = 2"); + var commands = args.args[1].product_ids; + assert.deepEqual(commands[0], [4, 37, false], "first command should be a 4"); + // TO DO: uncomment next line + // assert.strictEqual(commands[1], [0, false, {name: "toy"}], "second command should be a 0"); + assert.strictEqual(commands[1][0], 0, "second command should be a 0"); + } + return this._super(route, args); + }, + }); - model.load(this.params).then(function (resultID) { - var record = model.get(resultID, {raw: true}); + var resultID = await model.load(this.params); + var record = model.get(resultID, { raw: true }); assert.deepEqual(record.data.product_ids, [37], "should have correct initial value"); - model.makeRecord('product', [{ - name: 'name', - string: "Product Name", - type: "char", - value: "xpod" - } - ]).then(function (relatedRecordID) { - model.notifyChanges(record.id, { - product_ids: {operation: "ADD", id: relatedRecordID} - }); - model.save(record.id); + var relatedRecordID = await model.makeRecord('product', [{ + name: 'name', + string: "Product Name", + type: "char", + value: "xpod" + } + ]); + await model.notifyChanges(record.id, { + product_ids: { operation: "ADD", id: relatedRecordID } }); + await model.save(record.id); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('create commands on a one2many', function (assert) { - assert.expect(3); + QUnit.test('create commands on a one2many', async function (assert) { + assert.expect(3); - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - return this._super(route, args); - }, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + return this._super(route, args); + }, + }); - this.params.fieldsInfo = { - default: { - product_ids: { - fieldsInfo: { - default: { - display_name: {type: 'string'}, - } - }, - viewType: 'default', + this.params.fieldsInfo = { + default: { + product_ids: { + fieldsInfo: { + default: { + display_name: { type: 'string' }, + } + }, + viewType: 'default', + } } - } - }; - this.params.res_id = undefined; - this.params.type = 'record'; + }; + this.params.res_id = undefined; + this.params.type = 'record'; - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.product_ids.data.length, 0, "one2many should start with a list of length 0"); - model.notifyChanges(record.id, { + await model.notifyChanges(record.id, { product_ids: { operation: "CREATE", data: { @@ -1186,279 +1156,275 @@ QUnit.module('Views', { "one2many should be a list of length 1"); assert.strictEqual(record.data.product_ids.data[0].data.display_name, "coucou", "one2many should have correct data"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('onchange with a one2many on a new record', function (assert) { - assert.expect(4); + QUnit.test('onchange with a one2many on a new record', async function (assert) { + assert.expect(4); - this.data.partner.fields.total.default = 50; - this.data.partner.fields.product_ids.onChange = true; - this.data.partner.onchanges.product_ids = function (obj) { - obj.total += 100; - }; + this.data.partner.fields.total.default = 50; + this.data.partner.fields.product_ids.onChange = true; + this.data.partner.onchanges.product_ids = function (obj) { + obj.total += 100; + }; - this.params.fieldNames = ['total', 'product_ids']; - this.params.res_id = undefined; - this.params.type = 'record'; - this.params.fieldsInfo = { - form: { - product_ids: { - fieldsInfo: { - default: { name: {} }, + this.params.fieldNames = ['total', 'product_ids']; + this.params.res_id = undefined; + this.params.type = 'record'; + this.params.fieldsInfo = { + form: { + product_ids: { + fieldsInfo: { + default: { name: {} }, + }, + relatedFields: this.data.product.fields, + viewType: 'default', }, - relatedFields: this.data.product.fields, - viewType: 'default', + total: {}, }, - total: {}, - }, - }; - this.params.viewType = 'form'; - - var o2mRecordParams = { - fields: this.data.product.fields, - fieldNames: ['name'], - modelName: 'product', - type: 'record', - }; - - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - if (args.method === 'onchange' && args.args[1].total === 150) { - assert.deepEqual(args.args[1].product_ids, [[0, args.args[1].product_ids[0][1], {name: "xpod"}]], - "Should have sent the create command in the onchange"); - } - return this._super(route, args); - }, - }); + }; + this.params.viewType = 'form'; + + var o2mRecordParams = { + fields: this.data.product.fields, + fieldNames: ['name'], + modelName: 'product', + type: 'record', + }; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'onchange' && args.args[1].total === 150) { + assert.deepEqual(args.args[1].product_ids, [[0, args.args[1].product_ids[0][1], { name: "xpod" }]], + "Should have sent the create command in the onchange"); + } + return this._super(route, args); + }, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.product_ids.data.length, 0, "one2many should start with a list of length 0"); // make a default record for the related model - model.load(o2mRecordParams).then(function (relatedRecordID) { - // update the subrecord - model.notifyChanges(relatedRecordID, {name: 'xpod'}); - // add the subrecord to the o2m of the main record - model.notifyChanges(resultID, { - product_ids: {operation: "ADD", id: relatedRecordID} - }); - - record = model.get(resultID); - assert.strictEqual(record.data.product_ids.data.length, 1, - "one2many should be a list of length 1"); - assert.strictEqual(record.data.product_ids.data[0].data.name, "xpod", - "one2many should have correct data"); + var relatedRecordID = await model.load(o2mRecordParams); + // update the subrecord + await model.notifyChanges(relatedRecordID, { name: 'xpod' }); + // add the subrecord to the o2m of the main record + await model.notifyChanges(resultID, { + product_ids: { operation: "ADD", id: relatedRecordID } }); + + record = model.get(resultID); + assert.strictEqual(record.data.product_ids.data.length, 1, + "one2many should be a list of length 1"); + assert.strictEqual(record.data.product_ids.data[0].data.name, "xpod", + "one2many should have correct data"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('dates are properly loaded and parsed (record)', function (assert) { - assert.expect(2); + QUnit.test('dates are properly loaded and parsed (record)', async function (assert) { + assert.expect(2); - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - var params = { - fieldNames: ['date'], - fields: this.data.partner.fields, - modelName: 'partner', - res_id: 1, - }; + var params = { + fieldNames: ['date'], + fields: this.data.partner.fields, + modelName: 'partner', + res_id: 1, + }; - model.load(params).then(function (resultID) { - var record = model.get(resultID); - assert.ok(record.data.date instanceof moment, - "fetched date field should have been formatted"); - }); + model.load(params).then(function (resultID) { + var record = model.get(resultID); + assert.ok(record.data.date instanceof moment, + "fetched date field should have been formatted"); + }); - params.res_id = 2; + params.res_id = 2; - model.load(params).then(function (resultID) { - var record = model.get(resultID); - assert.strictEqual(record.data.date, false, - "unset date field should be false"); + model.load(params).then(function (resultID) { + var record = model.get(resultID); + assert.strictEqual(record.data.date, false, + "unset date field should be false"); + }); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('dates are properly loaded and parsed (list)', function (assert) { - assert.expect(2); + QUnit.test('dates are properly loaded and parsed (list)', async function (assert) { + assert.expect(2); - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - var params = { - fieldNames: ['date'], - fields: this.data.partner.fields, - modelName: 'partner', - type: 'list', - }; + var params = { + fieldNames: ['date'], + fields: this.data.partner.fields, + modelName: 'partner', + type: 'list', + }; - model.load(params).then(function (resultID) { - var record = model.get(resultID); - var firstRecord = record.data[0]; - var secondRecord = record.data[1]; - assert.ok(firstRecord.data.date instanceof moment, - "fetched date field should have been formatted"); - assert.strictEqual(secondRecord.data.date, false, - "if date is not set, it should be false"); + model.load(params).then(function (resultID) { + var record = model.get(resultID); + var firstRecord = record.data[0]; + var secondRecord = record.data[1]; + assert.ok(firstRecord.data.date instanceof moment, + "fetched date field should have been formatted"); + assert.strictEqual(secondRecord.data.date, false, + "if date is not set, it should be false"); + }); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('dates are properly loaded and parsed (default_get)', function (assert) { - assert.expect(1); + QUnit.test('dates are properly loaded and parsed (default_get)', async function (assert) { + assert.expect(1); - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - var params = { - fieldNames: ['date'], - fields: this.data.partner.fields, - modelName: 'partner', - type: 'record', - }; + var params = { + fieldNames: ['date'], + fields: this.data.partner.fields, + modelName: 'partner', + type: 'record', + }; - model.load(params).then(function (resultID) { - var record = model.get(resultID); - assert.strictEqual(record.data.date, false, "date default value should be false"); + model.load(params).then(function (resultID) { + var record = model.get(resultID); + assert.strictEqual(record.data.date, false, "date default value should be false"); + }); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('default_get on x2many may return a list of ids', function (assert) { - assert.expect(1); + QUnit.test('default_get on x2many may return a list of ids', async function (assert) { + assert.expect(1); - this.data.partner.fields.category.default = [12, 14]; + this.data.partner.fields.category.default = [12, 14]; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - var params = { - fieldNames: ['category'], - fields: this.data.partner.fields, - modelName: 'partner', - type: 'record', - }; + var params = { + fieldNames: ['category'], + fields: this.data.partner.fields, + modelName: 'partner', + type: 'record', + }; - model.load(params).then(function (resultID) { - var record = model.get(resultID); - assert.ok(_.isEqual(record.data.category.res_ids, [12, 14]), - "category field should have correct default value"); - }); + model.load(params).then(function (resultID) { + var record = model.get(resultID); + assert.ok(_.isEqual(record.data.category.res_ids, [12, 14]), + "category field should have correct default value"); + }); - model.destroy(); - }); + model.destroy(); + }); - QUnit.test('default_get: fetch many2one with default (empty & not) inside x2manys', function (assert) { - assert.expect(4); + QUnit.test('default_get: fetch many2one with default (empty & not) inside x2manys', async function (assert) { + assert.expect(4); - this.data.partner.fields.o2m = { - string: "O2M", type: 'one2many', relation: 'partner', default: [ - [6, 0, []], - [0, 0, {category: false}], - [0, 0, {category: 12}], - ], - }; - this.data.partner.fields.category.type = 'many2one'; - - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - if (args.method === 'name_get' && args.model === 'partner_type') { - assert.deepEqual(args.args, [[12]], "should name_get on category 12"); - } - return this._super(route, args); - }, - }); + this.data.partner.fields.o2m = { + string: "O2M", type: 'one2many', relation: 'partner', default: [ + [6, 0, []], + [0, 0, { category: false }], + [0, 0, { category: 12 }], + ], + }; + this.data.partner.fields.category.type = 'many2one'; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'name_get' && args.model === 'partner_type') { + assert.deepEqual(args.args, [[12]], "should name_get on category 12"); + } + return this._super(route, args); + }, + }); - var params = { - fieldNames: ['o2m'], - fields: this.data.partner.fields, - fieldsInfo: { - form: { - o2m: { - relatedFields: this.data.partner.fields, - fieldsInfo: { - list: { - category: { - relatedFields: { display_name: {} }, + var params = { + fieldNames: ['o2m'], + fields: this.data.partner.fields, + fieldsInfo: { + form: { + o2m: { + relatedFields: this.data.partner.fields, + fieldsInfo: { + list: { + category: { + relatedFields: { display_name: {} }, + }, }, }, + viewType: 'list', }, - viewType: 'list', }, }, - }, - modelName: 'partner', - type: 'record', - viewType: 'form', - }; + modelName: 'partner', + type: 'record', + viewType: 'form', + }; - model.load(params).then(function (resultID) { + var resultID = await model.load(params); var record = model.get(resultID); assert.strictEqual(record.data.o2m.count, 2, "o2m field should contain 2 records"); assert.strictEqual(record.data.o2m.data[0].data.category, false, "first category field should be empty"); assert.strictEqual(record.data.o2m.data[1].data.category.data.display_name, "gold", "second category field should have been correctly fetched"); - }); - model.destroy(); - }); + model.destroy(); + }); - QUnit.test('default_get: fetch x2manys inside x2manys', function (assert) { - assert.expect(3); + QUnit.test('default_get: fetch x2manys inside x2manys', async function (assert) { + assert.expect(3); - this.data.partner.fields.o2m = { - string: "O2M", type: 'one2many', relation: 'partner', default: [[6, 0, [1]]], - }; + this.data.partner.fields.o2m = { + string: "O2M", type: 'one2many', relation: 'partner', default: [[6, 0, [1]]], + }; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - var params = { - fieldNames: ['o2m'], - fields: this.data.partner.fields, - fieldsInfo: { - form: { - o2m: { - relatedFields: this.data.partner.fields, - fieldsInfo: { - list: { - category: { - relatedFields: { display_name: {} }, + var params = { + fieldNames: ['o2m'], + fields: this.data.partner.fields, + fieldsInfo: { + form: { + o2m: { + relatedFields: this.data.partner.fields, + fieldsInfo: { + list: { + category: { + relatedFields: { display_name: {} }, + }, }, }, + viewType: 'list', }, - viewType: 'list', }, }, - }, - modelName: 'partner', - type: 'record', - viewType: 'form', - }; + modelName: 'partner', + type: 'record', + viewType: 'form', + }; - model.load(params).then(function (resultID) { + var resultID = await model.load(params); var record = model.get(resultID); assert.strictEqual(record.data.o2m.count, 1, "o2m field should contain 1 record"); var categoryList = record.data.o2m.data[0].data.category; @@ -1466,25 +1432,24 @@ QUnit.module('Views', { "category field should contain 1 record"); assert.strictEqual(categoryList.data[0].data.display_name, 'gold', "category records should have been fetched"); - }); - model.destroy(); - }); + model.destroy(); + }); - QUnit.test('contexts and domains can be properly fetched', function (assert) { - assert.expect(8); + QUnit.test('contexts and domains can be properly fetched', async function (assert) { + assert.expect(8); - this.data.partner.fields.product_id.context = "{'hello': 'world', 'test': foo}"; - this.data.partner.fields.product_id.domain = "[['hello', 'like', 'world'], ['test', 'like', foo]]"; + this.data.partner.fields.product_id.context = "{'hello': 'world', 'test': foo}"; + this.data.partner.fields.product_id.domain = "[['hello', 'like', 'world'], ['test', 'like', foo]]"; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - this.params.fieldNames = ['product_id', 'foo']; + this.params.fieldNames = ['product_id', 'foo']; - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var recordPartner = model.get(resultID); assert.strictEqual(typeof recordPartner.getContext, "function", "partner record should have a getContext function"); @@ -1495,280 +1460,273 @@ QUnit.module('Views', { assert.deepEqual(recordPartner.getDomain(), [], "asking for a domain without a field name should fetch the session/user/view domain"); assert.deepEqual( - recordPartner.getContext({fieldName: "product_id"}), - {hello: "world", test: "gnap"}, + recordPartner.getContext({ fieldName: "product_id" }), + { hello: "world", test: "gnap" }, "asking for a context with a field name should fetch the field context (evaluated)"); assert.deepEqual( - recordPartner.getDomain({fieldName: "product_id"}), + recordPartner.getDomain({ fieldName: "product_id" }), [["hello", "like", "world"], ["test", "like", "gnap"]], "asking for a domain with a field name should fetch the field domain (evaluated)"); - }); - model.destroy(); + model.destroy(); - // Try again with xml override of field domain and context - model = createModel({ - Model: BasicModel, - data: this.data, - }); + // Try again with xml override of field domain and context + model = createModel({ + Model: BasicModel, + data: this.data, + }); - this.params.fieldsInfo = { - default: { - foo: {}, - product_id: { - context: "{'hello2': 'world', 'test2': foo}", - domain: "[['hello2', 'like', 'world'], ['test2', 'like', foo]]", - }, - } - }; + this.params.fieldsInfo = { + default: { + foo: {}, + product_id: { + context: "{'hello2': 'world', 'test2': foo}", + domain: "[['hello2', 'like', 'world'], ['test2', 'like', foo]]", + }, + } + }; - model.load(this.params).then(function (resultID) { - var recordPartner = model.get(resultID); + resultID = await model.load(this.params); + recordPartner = model.get(resultID); assert.deepEqual( - recordPartner.getContext({fieldName: "product_id"}), - {hello2: "world", test2: "gnap"}, + recordPartner.getContext({ fieldName: "product_id" }), + { hello2: "world", test2: "gnap" }, "field context should have been overriden by xml attribute"); assert.deepEqual( - recordPartner.getDomain({fieldName: "product_id"}), + recordPartner.getDomain({ fieldName: "product_id" }), [["hello2", "like", "world"], ["test2", "like", "gnap"]], "field domain should have been overriden by xml attribute"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('dont write on readonly fields (write and create)', function (assert) { - assert.expect(6); - - this.params.fieldNames = ['foo', 'bar']; - this.data.partner.fields.foo.onChange = true; - this.data.partner.onchanges.foo = function (obj) { - obj.bar = obj.foo.length; - }; - this.params.fieldsInfo = { - default: { - foo: {}, - bar: { - modifiers: { - readonly: true, + QUnit.test('dont write on readonly fields (write and create)', async function (assert) { + assert.expect(6); + + this.params.fieldNames = ['foo', 'bar']; + this.data.partner.fields.foo.onChange = true; + this.data.partner.onchanges.foo = function (obj) { + obj.bar = obj.foo.length; + }; + this.params.fieldsInfo = { + default: { + foo: {}, + bar: { + modifiers: { + readonly: true, + }, }, - }, - } - }; - - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - if (args.method === 'write') { - assert.deepEqual(args.args[1], {foo: "verylongstring"}, - "should only save foo field"); } - if (args.method === 'create') { - assert.deepEqual(args.args[0], {foo: "anotherverylongstring"}, - "should only save foo field"); - } - return this._super(route, args); - }, - }); - model.load(this.params).then(function (resultID) { + }; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'write') { + assert.deepEqual(args.args[1], { foo: "verylongstring" }, + "should only save foo field"); + } + if (args.method === 'create') { + assert.deepEqual(args.args[0], { foo: "anotherverylongstring" }, + "should only save foo field"); + } + return this._super(route, args); + }, + }); + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.bar, 2, "should be initialized with correct value"); - model.notifyChanges(resultID, {foo: "verylongstring"}); + await model.notifyChanges(resultID, { foo: "verylongstring" }); record = model.get(resultID); assert.strictEqual(record.data.bar, 14, "should be changed with correct value"); - model.save(resultID); - }); + await model.save(resultID); - // start again, but with a new record - delete this.params.res_id; - model.load(this.params).then(function (resultID) { - var record = model.get(resultID); + // start again, but with a new record + delete this.params.res_id; + resultID = await model.load(this.params); + record = model.get(resultID); assert.strictEqual(record.data.bar, 0, "should be initialized with correct value (0 as integer)"); - model.notifyChanges(resultID, {foo: "anotherverylongstring"}); + await model.notifyChanges(resultID, { foo: "anotherverylongstring" }); record = model.get(resultID); assert.strictEqual(record.data.bar, 21, "should be changed with correct value"); - model.save(resultID); + await model.save(resultID); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('dont write on readonly fields unless save attribute is set', function (assert) { - assert.expect(6); - - this.params.fieldNames = ['foo', 'bar']; - this.data.partner.fields.foo.onChange = true; - this.data.partner.onchanges.foo = function (obj) { - obj.bar = obj.foo.length; - }; - this.params.fieldsInfo = { - default: { - foo: {}, - bar: { - modifiers: { - readonly: true, + QUnit.test('dont write on readonly fields unless save attribute is set', async function (assert) { + assert.expect(6); + + this.params.fieldNames = ['foo', 'bar']; + this.data.partner.fields.foo.onChange = true; + this.data.partner.onchanges.foo = function (obj) { + obj.bar = obj.foo.length; + }; + this.params.fieldsInfo = { + default: { + foo: {}, + bar: { + modifiers: { + readonly: true, + }, + force_save: true, }, - force_save: true, - }, - } - }; - - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - if (args.method === 'write') { - assert.deepEqual(args.args[1], {bar: 14, foo: "verylongstring"}, - "should only save foo field"); - } - if (args.method === 'create') { - assert.deepEqual(args.args[0], {bar: 21, foo: "anotherverylongstring"}, - "should only save foo field"); } - return this._super(route, args); - }, - }); + }; - model.load(this.params).then(function (resultID) { + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'write') { + assert.deepEqual(args.args[1], { bar: 14, foo: "verylongstring" }, + "should only save foo field"); + } + if (args.method === 'create') { + assert.deepEqual(args.args[0], { bar: 21, foo: "anotherverylongstring" }, + "should only save foo field"); + } + return this._super(route, args); + }, + }); + + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data.bar, 2, "should be initialized with correct value"); - model.notifyChanges(resultID, {foo: "verylongstring"}); + await model.notifyChanges(resultID, { foo: "verylongstring" }); record = model.get(resultID); assert.strictEqual(record.data.bar, 14, "should be changed with correct value"); - model.save(resultID); - }); + await model.save(resultID); - // start again, but with a new record - delete this.params.res_id; - model.load(this.params).then(function (resultID) { - var record = model.get(resultID); + // start again, but with a new record + delete this.params.res_id; + resultID = await model.load(this.params); + record = model.get(resultID); assert.strictEqual(record.data.bar, 0, "should be initialized with correct value (0 as integer)"); - model.notifyChanges(resultID, {foo: "anotherverylongstring"}); + await model.notifyChanges(resultID, { foo: "anotherverylongstring" }); record = model.get(resultID); assert.strictEqual(record.data.bar, 21, "should be changed with correct value"); - model.save(resultID); + await model.save(resultID); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('default_get with one2many values', function (assert) { - assert.expect(1); - - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - if (args.method === 'default_get') { - return $.when({ - product_ids: [[0, 0, {"name": "xdroid"}]] - }); - } - return this._super(route, args); - }, - }); - var params = { - fieldNames: ['product_ids'], - fields: this.data.partner.fields, - modelName: 'partner', - type: 'record', - fieldsInfo: { - form: { - product_ids: { - fieldsInfo: { - default: { name: {} }, + QUnit.test('default_get with one2many values', async function (assert) { + assert.expect(1); + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'default_get') { + return Promise.resolve({ + product_ids: [[0, 0, { "name": "xdroid" }]] + }); + } + return this._super(route, args); + }, + }); + var params = { + fieldNames: ['product_ids'], + fields: this.data.partner.fields, + modelName: 'partner', + type: 'record', + fieldsInfo: { + form: { + product_ids: { + fieldsInfo: { + default: { name: {} }, + }, + relatedFields: this.data.product.fields, + viewType: 'default', }, - relatedFields: this.data.product.fields, - viewType: 'default', }, }, - }, - viewType: 'form', - }; - model.load(params).then(function (resultID) { + viewType: 'form', + }; + var resultID = await model.load(params); assert.strictEqual(typeof resultID, 'string', "result should be a valid id"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('call makeRecord with a pre-fetched many2one field', function (assert) { - assert.expect(3); - var rpcCount = 0; - - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - rpcCount++; - return this._super(route, args); - }, - }); + QUnit.test('call makeRecord with a pre-fetched many2one field', async function (assert) { + assert.expect(3); + var rpcCount = 0; - model.makeRecord('coucou', [{ - name: 'partner_id', - relation: 'partner', - type: 'many2one', - value: [1, 'first partner'], - }], { - partner_id: { - options: { - no_open: true, + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + rpcCount++; + return this._super(route, args); }, - }, - }).then(function (recordID) { - var record = model.get(recordID); - assert.deepEqual(record.fieldsInfo.default.partner_id, {options: {no_open: true}}, - "makeRecord should have generated the fieldsInfo"); - assert.deepEqual(record.data.partner_id.data, {id: 1, display_name: 'first partner'}, - "many2one should contain the partner with id 1"); - assert.strictEqual(rpcCount, 0, "makeRecord should not have done any rpc"); - }); - model.destroy(); - }); + }); - QUnit.test('call makeRecord with a many2many field', function (assert) { - assert.expect(5); - var rpcCount = 0; - - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - rpcCount++; - return this._super(route, args); - }, + model.makeRecord('coucou', [{ + name: 'partner_id', + relation: 'partner', + type: 'many2one', + value: [1, 'first partner'], + }], { + partner_id: { + options: { + no_open: true, + }, + }, + }).then(function (recordID) { + var record = model.get(recordID); + assert.deepEqual(record.fieldsInfo.default.partner_id, { options: { no_open: true } }, + "makeRecord should have generated the fieldsInfo"); + assert.deepEqual(record.data.partner_id.data, { id: 1, display_name: 'first partner' }, + "many2one should contain the partner with id 1"); + assert.strictEqual(rpcCount, 0, "makeRecord should not have done any rpc"); + }); + model.destroy(); }); - model.makeRecord('coucou', [{ - name: 'partner_ids', - fields: [{ - name: 'id', - type: 'integer', - }, { - name: 'display_name', - type: 'char', - }], - relation: 'partner', - type: 'many2many', - value: [1, 2], - }]).then(function (recordID) { + QUnit.test('call makeRecord with a many2many field', async function (assert) { + assert.expect(5); + var rpcCount = 0; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + rpcCount++; + return this._super(route, args); + }, + }); + + var recordID = await model.makeRecord('coucou', [{ + name: 'partner_ids', + fields: [{ + name: 'id', + type: 'integer', + }, { + name: 'display_name', + type: 'char', + }], + relation: 'partner', + type: 'many2many', + value: [1, 2], + }]); var record = model.get(recordID); assert.deepEqual(record.fieldsInfo.default.partner_ids, {}, "makeRecord should have generated the fieldsInfo"); @@ -1776,45 +1734,44 @@ QUnit.module('Views', { "there should be 2 elements in the many2many"); assert.strictEqual(record.data.partner_ids.data.length, 2, "many2many should be a list of length 2"); - assert.deepEqual(record.data.partner_ids.data[0].data, {id: 1, display_name: 'first partner'}, + assert.deepEqual(record.data.partner_ids.data[0].data, { id: 1, display_name: 'first partner' }, "many2many should contain the partner with id 1"); assert.strictEqual(rpcCount, 1, "makeRecord should have done 1 rpc"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('call makeRecord with a pre-fetched many2many field', function (assert) { - assert.expect(5); - var rpcCount = 0; - - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - rpcCount++; - return this._super(route, args); - }, - }); + QUnit.test('call makeRecord with a pre-fetched many2many field', async function (assert) { + assert.expect(5); + var rpcCount = 0; - model.makeRecord('coucou', [{ - name: 'partner_ids', - fields: [{ - name: 'id', - type: 'integer', - }, { - name: 'display_name', - type: 'char', - }], - relation: 'partner', - type: 'many2many', - value: [{ - id: 1, - display_name: "first partner", - }, { - id: 2, - display_name: "second partner", - }], - }]).then(function (recordID) { + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + rpcCount++; + return this._super(route, args); + }, + }); + + var recordID = await model.makeRecord('coucou', [{ + name: 'partner_ids', + fields: [{ + name: 'id', + type: 'integer', + }, { + name: 'display_name', + type: 'char', + }], + relation: 'partner', + type: 'many2many', + value: [{ + id: 1, + display_name: "first partner", + }, { + id: 2, + display_name: "second partner", + }], + }]); var record = model.get(recordID); assert.deepEqual(record.fieldsInfo.default.partner_ids, {}, "makeRecord should have generated the fieldsInfo"); @@ -1822,154 +1779,149 @@ QUnit.module('Views', { "there should be 2 elements in the many2many"); assert.strictEqual(record.data.partner_ids.data.length, 2, "many2many should be a list of length 2"); - assert.deepEqual(record.data.partner_ids.data[0].data, {id: 1, display_name: 'first partner'}, + assert.deepEqual(record.data.partner_ids.data[0].data, { id: 1, display_name: 'first partner' }, "many2many should contain the partner with id 1"); assert.strictEqual(rpcCount, 0, "makeRecord should not have done any rpc"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('check id, active_id, active_ids, active_model values in record\'s context', function (assert) { - assert.expect(2); + QUnit.test('check id, active_id, active_ids, active_model values in record\'s context', async function (assert) { + assert.expect(2); - this.data.partner.fields.product_id.context = "{'id': id, 'active_id': active_id, 'active_ids': active_ids, 'active_model': active_model}"; + this.data.partner.fields.product_id.context = "{'id': id, 'active_id': active_id, 'active_ids': active_ids, 'active_model': active_model}"; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - this.params.fieldNames = ['product_id']; + this.params.fieldNames = ['product_id']; - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var recordPartner = model.get(resultID); assert.deepEqual( - recordPartner.getContext({fieldName: "product_id"}), - {id: 2, active_id: 2, active_ids: [2], active_model: "partner"}, + recordPartner.getContext({ fieldName: "product_id" }), + { id: 2, active_id: 2, active_ids: [2], active_model: "partner" }, "wrong values for id, active_id, active_ids or active_model"); - }); - // Try again without record - this.params.res_id = undefined; + // Try again without record + this.params.res_id = undefined; - model.load(this.params).then(function (resultID) { - var recordPartner = model.get(resultID); + resultID = await model.load(this.params); + recordPartner = model.get(resultID); assert.deepEqual( - recordPartner.getContext({fieldName: "product_id"}), - {id: false, active_id: false, active_ids: [], active_model: "partner"}, + recordPartner.getContext({ fieldName: "product_id" }), + { id: false, active_id: false, active_ids: [], active_model: "partner" }, "wrong values for id, active_id, active_ids or active_model. Have to be defined even if there is no record."); + + model.destroy(); }); - model.destroy(); - }); + QUnit.test('load model with many2many field properly fetched', async function (assert) { + assert.expect(2); - QUnit.test('load model with many2many field properly fetched', function (assert) { - assert.expect(2); + this.params.fieldNames = ['category']; + this.params.res_id = 1; - this.params.fieldNames = ['category']; - this.params.res_id = 1; + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + assert.step(args.method); + return this._super(route, args); + }, + }); - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - assert.step(args.method); - return this._super(route, args); - }, + model.load(this.params); + assert.verifySteps(['read'], + "there should be only one read"); + model.destroy(); }); - model.load(this.params); - assert.verifySteps(['read'], - "there should be only one read"); - model.destroy(); - }); + QUnit.test('data should contain all fields in view, default being false', async function (assert) { + assert.expect(1); - QUnit.test('data should contain all fields in view, default being false', function (assert) { - assert.expect(1); + this.data.partner.fields.product_ids.default = [ + [6, 0, []], + [0, 0, { name: 'new' }], + ]; + this.data.product.fields.date = { string: "Date", type: "date" }; + + var params = { + fieldNames: ['product_ids'], + modelName: 'partner', + fields: this.data.partner.fields, + fieldsInfo: { + form: { + product_ids: { + relatedFields: this.data.product.fields, + fieldsInfo: { list: { name: {}, date: {} } }, + viewType: 'list', + } + }, + }, + res_id: undefined, + type: 'record', + viewType: 'form', + }; - this.data.partner.fields.product_ids.default = [ - [6, 0, []], - [0, 0, {name: 'new'}], - ]; - this.data.product.fields.date = { string: "Date", type: "date" }; + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - var params = { - fieldNames: ['product_ids'], - modelName: 'partner', - fields: this.data.partner.fields, - fieldsInfo: { - form: { - product_ids: { - relatedFields: this.data.product.fields, - fieldsInfo: { list: { name: {}, date: {} } }, - viewType: 'list', - } - }, - }, - res_id: undefined, - type: 'record', - viewType: 'form', - }; - - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + model.load(params).then(function (resultID) { + var record = model.get(resultID); + assert.strictEqual(record.data.product_ids.data[0].data.date, false, + "date value should be in data, and should be false"); + }); - model.load(params).then(function (resultID) { - var record = model.get(resultID); - assert.strictEqual(record.data.product_ids.data[0].data.date, false, - "date value should be in data, and should be false"); + model.destroy(); }); - model.destroy(); - }); - - QUnit.test('changes are discarded when reloading from a new record', function (assert) { - // practical use case: click on 'Create' to open a form view in edit - // mode (new record), click on 'Discard', then open an existing record - assert.expect(2); + QUnit.test('changes are discarded when reloading from a new record', async function (assert) { + // practical use case: click on 'Create' to open a form view in edit + // mode (new record), click on 'Discard', then open an existing record + assert.expect(2); - this.data.partner.fields.foo.default = 'default'; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + this.data.partner.fields.foo.default = 'default'; + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - // load a new record (default_get) - var params = _.extend(this.params, { - res_id: undefined, - type: 'record', - fieldNames: ['foo'], - }); - model.load(params).then(function (resultID) { + // load a new record (default_get) + var params = _.extend(this.params, { + res_id: undefined, + type: 'record', + fieldNames: ['foo'], + }); + var resultID = await model.load(params); var record = model.get(resultID); assert.strictEqual(record.data.foo, 'default', "should be the default value"); // reload with id 2 - model.reload(record.id, {currentId: 2}).then(function (resultID) { - var record = model.get(resultID); - assert.strictEqual(record.data.foo, 'gnap', - "should be the value of record 2"); - }); - }); + resultID = await model.reload(record.id, { currentId: 2 }); + record = model.get(resultID); + assert.strictEqual(record.data.foo, 'gnap', + "should be the value of record 2"); - model.destroy(); - }); + model.destroy(); + }); - QUnit.test('has a proper evaluation context', function (assert) { - assert.expect(1); + QUnit.test('has a proper evaluation context', async function (assert) { + assert.expect(1); - this.params.fieldNames = Object.keys(this.data.partner.fields); - this.params.res_id = 1; + this.params.fieldNames = Object.keys(this.data.partner.fields); + this.params.res_id = 1; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.deepEqual(record.evalContext, { active_id: 1, @@ -1988,42 +1940,41 @@ QUnit.module('Views', { reference: false, total: 0 }, "should use the proper eval context"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('x2manys in contexts and domains are correctly evaluated', function (assert) { - assert.expect(4); - - this.data.partner.records[0].product_ids = [37, 41]; - this.params.fieldNames = Object.keys(this.data.partner.fields); - this.params.fieldsInfo = { - form: { - qux: { - context: "{'category': category, 'product_ids': product_ids}", - domain: "[['id', 'in', category], ['id', 'in', product_ids]]", - relatedFields: this.data.partner.fields, - }, - category: { - relatedFields: this.data.partner_type.fields, - }, - product_ids: { - relatedFields: this.data.product.fields, + QUnit.test('x2manys in contexts and domains are correctly evaluated', async function (assert) { + assert.expect(4); + + this.data.partner.records[0].product_ids = [37, 41]; + this.params.fieldNames = Object.keys(this.data.partner.fields); + this.params.fieldsInfo = { + form: { + qux: { + context: "{'category': category, 'product_ids': product_ids}", + domain: "[['id', 'in', category], ['id', 'in', product_ids]]", + relatedFields: this.data.partner.fields, + }, + category: { + relatedFields: this.data.partner_type.fields, + }, + product_ids: { + relatedFields: this.data.product.fields, + }, }, - }, - }; - this.params.viewType = 'form'; - this.params.res_id = 1; - - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + }; + this.params.viewType = 'form'; + this.params.res_id = 1; + + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); - var context = record.getContext({fieldName: 'qux'}); - var domain = record.getDomain({fieldName: 'qux'}); + var context = record.getContext({ fieldName: 'qux' }); + var domain = record.getDomain({ fieldName: 'qux' }); assert.deepEqual(context, { category: [12], @@ -2038,177 +1989,171 @@ QUnit.module('Views', { ], "x2many values in domains should be lists of ids"); assert.strictEqual(JSON.stringify(domain), "[[\"id\",\"in\",[12]],[\"id\",\"in\",[37,41]]]", - "x2many values in domains should be lists of ids"); + "x2many values in domains should be lists of ids"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('fetch references in list, with not too many rpcs', function (assert) { - assert.expect(5); - - this.data.partner.records[0].reference = 'product,37'; - this.data.partner.records[1].reference = 'product,41'; - - this.params.fieldNames = ['reference']; - this.params.domain = []; - this.params.groupedBy = []; - this.params.res_id = undefined; - - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - assert.step(route); - if (route === "/web/dataset/call_kw/product/name_get") { - assert.deepEqual(args.args, [[37, 41]], - "the name_get should contain the product ids"); - } - return this._super(route, args); - }, - }); + QUnit.test('fetch references in list, with not too many rpcs', async function (assert) { + assert.expect(5); + + this.data.partner.records[0].reference = 'product,37'; + this.data.partner.records[1].reference = 'product,41'; + + this.params.fieldNames = ['reference']; + this.params.domain = []; + this.params.groupedBy = []; + this.params.res_id = undefined; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + assert.step(route); + if (route === "/web/dataset/call_kw/product/name_get") { + assert.deepEqual(args.args, [[37, 41]], + "the name_get should contain the product ids"); + } + return this._super(route, args); + }, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.strictEqual(record.data[0].data.reference.data.display_name, "xphone", "name_get should have been correctly fetched"); - assert.verifySteps(["/web/dataset/search_read", "/web/dataset/call_kw/product/name_get"], + assert.verifySteps(["/web/dataset/search_read", "/web/dataset/call_kw/product/name_get"], "should have done 2 rpc (searchread and name_get for product)"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('reload a new record', function (assert) { - assert.expect(6); - - this.params.context = {}; - this.params.fieldNames = ['product_id', 'category', 'product_ids']; - this.params.res_id = undefined; - this.params.type = 'record'; - - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route, args) { - assert.step(args.method); - return this._super(route, args); - }, - }); + QUnit.test('reload a new record', async function (assert) { + assert.expect(6); - model.load(this.params).then(function (recordID) { - model.reload(recordID).then(function (recordID) { - assert.verifySteps(['default_get', 'default_get'], - "two default_get RPCs should have been done"); - var record = model.get(recordID); - assert.strictEqual(record.data.product_id, false, - "m2o default value should be false"); - assert.deepEqual(record.data.product_ids.data, [], - "o2m default should be []"); - assert.deepEqual(record.data.category.data, [], - "m2m default should be []"); + this.params.context = {}; + this.params.fieldNames = ['product_id', 'category', 'product_ids']; + this.params.res_id = undefined; + this.params.type = 'record'; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + assert.step(args.method); + return this._super(route, args); + }, }); - }); - model.destroy(); - }); + var recordID = await model.load(this.params); + recordID = await model.reload(recordID); + assert.verifySteps(['default_get', 'default_get'], + "two default_get RPCs should have been done"); + var record = model.get(recordID); + assert.strictEqual(record.data.product_id, false, + "m2o default value should be false"); + assert.deepEqual(record.data.product_ids.data, [], + "o2m default should be []"); + assert.deepEqual(record.data.category.data, [], + "m2m default should be []"); + + model.destroy(); + }); - QUnit.test('default_get with value false for a one2many', function (assert) { - assert.expect(1); + QUnit.test('default_get with value false for a one2many', async function (assert) { + assert.expect(1); - this.data.partner.fields.product_ids.default = false; - this.params.fieldNames = ['product_ids']; - this.params.res_id = undefined; - this.params.type = 'record'; + this.data.partner.fields.product_ids.default = false; + this.params.fieldNames = ['product_ids']; + this.params.res_id = undefined; + this.params.type = 'record'; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); assert.deepEqual(record.data.product_ids.data, [], "o2m default should be []"); - }); - - model.destroy(); - }); - QUnit.test('only x2many lists (static) should be sorted client-side', function (assert) { - assert.expect(1); - - this.params.modelName = 'partner_type'; - this.params.res_id = undefined; - this.params.orderedBy = [{name: 'display_name', asc: true}]; - - var model = createModel({ - Model: BasicModel, - data: this.data, - mockRPC: function (route) { - if (route === '/web/dataset/search_read') { - // simulate randomn sort form the server - return $.when({ - length: 3, - records: [ - {id: 12, display_name: "gold", date: "2017-01-25"}, - {id: 15, display_name: "bronze"}, - {id: 14, display_name: "silver"}, - ], - }); - } - return this._super.apply(this, arguments); - }, - }); + model.destroy(); + }); + + QUnit.test('only x2many lists (static) should be sorted client-side', async function (assert) { + assert.expect(1); + + this.params.modelName = 'partner_type'; + this.params.res_id = undefined; + this.params.orderedBy = [{ name: 'display_name', asc: true }]; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route) { + if (route === '/web/dataset/search_read') { + // simulate randomn sort form the server + return Promise.resolve({ + length: 3, + records: [ + { id: 12, display_name: "gold", date: "2017-01-25" }, + { id: 15, display_name: "bronze" }, + { id: 14, display_name: "silver" }, + ], + }); + } + return this._super.apply(this, arguments); + }, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var list = model.get(resultID); assert.deepEqual(_.map(list.data, 'res_id'), [12, 15, 14], "should have kept the order from the server"); + model.destroy(); }); - model.destroy(); - }); - QUnit.test('onchange on a boolean field', function (assert) { - assert.expect(2); - - var newFields = { - foobool: { - type: 'boolean', - string: 'foobool', - }, - foobool2: { - type: 'boolean', - string: 'foobool2', - }, - }; - _.extend(this.data.partner.fields, newFields); - - this.data.partner.fields.foobool.onChange = true; - this.data.partner.onchanges.foobool = function (obj) { - if (obj.foobool) { - obj.foobool2 = true; - } - }; + QUnit.test('onchange on a boolean field', async function (assert) { + assert.expect(2); + + var newFields = { + foobool: { + type: 'boolean', + string: 'foobool', + }, + foobool2: { + type: 'boolean', + string: 'foobool2', + }, + }; + _.extend(this.data.partner.fields, newFields); + + this.data.partner.fields.foobool.onChange = true; + this.data.partner.onchanges.foobool = function (obj) { + if (obj.foobool) { + obj.foobool2 = true; + } + }; - this.data.partner.records[0].foobool = false; - this.data.partner.records[0].foobool2 = true; + this.data.partner.records[0].foobool = false; + this.data.partner.records[0].foobool2 = true; - this.params.res_id = 1; - this.params.fieldNames = ['foobool', 'foobool2']; - this.params.fields = this.data.partner.fields; - var model = createModel({ - Model: BasicModel, - data: this.data, - }); + this.params.res_id = 1; + this.params.fieldNames = ['foobool', 'foobool2']; + this.params.fields = this.data.partner.fields; + var model = createModel({ + Model: BasicModel, + data: this.data, + }); - model.load(this.params).then(function (resultID) { + var resultID = await model.load(this.params); var record = model.get(resultID); - model.notifyChanges(resultID, {foobool2: false}); + await model.notifyChanges(resultID, { foobool2: false }); record = model.get(resultID); assert.strictEqual(record.data.foobool2, false, "foobool2 field should be false"); - model.notifyChanges(resultID, {foobool: true}); + await model.notifyChanges(resultID, { foobool: true }); record = model.get(resultID); assert.strictEqual(record.data.foobool2, true, "foobool2 field should be true"); + model.destroy(); }); - model.destroy(); }); -});}); +}); diff --git a/addons/web/static/tests/views/calendar_mobile_tests.js b/addons/web/static/tests/views/calendar_mobile_tests.js index 969476b0081..60b5ed5f85f 100644 --- a/addons/web/static/tests/views/calendar_mobile_tests.js +++ b/addons/web/static/tests/views/calendar_mobile_tests.js @@ -4,7 +4,7 @@ odoo.define('web.calendar_mobile_tests', function (require) { var CalendarView = require('web.CalendarView'); var testUtils = require('web.test_utils'); -var createAsyncView = testUtils.createAsyncView; +var createView = testUtils.createView; var initialDate = new Date(2016, 11, 12, 8, 0, 0); initialDate = new Date(initialDate.getTime() - initialDate.getTimezoneOffset()*60*1000); @@ -24,7 +24,7 @@ QUnit.module('Views', { {id: 1, name: "event 1", start: "2016-12-11 00:00:00", stop: "2016-12-11 00:00:00"}, ], check_access_rights: function () { - return $.when(true); + return Promise.resolve(true); } }, }; @@ -37,7 +37,7 @@ QUnit.module('Views', { assert.expect(3); var done = assert.async(); - createAsyncView({ + createView({ View: CalendarView, model: 'event', data: this.data, diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js index 9e0e7dc1e84..8061df08af4 100644 --- a/addons/web/static/tests/views/calendar_tests.js +++ b/addons/web/static/tests/views/calendar_tests.js @@ -7,7 +7,6 @@ var Dialog = require('web.Dialog'); var fieldUtils = require('web.field_utils'); var mixins = require('web.mixins'); var testUtils = require('web.test_utils'); -var time = require('web.time'); var session = require('web.session'); var createActionManager = testUtils.createActionManager; @@ -23,8 +22,7 @@ CalendarRenderer.include({ }); -var createView = testUtils.createView; -var createAsyncView = testUtils.createAsyncView; +var createCalendarView = testUtils.createCalendarView; var initialDate = new Date(2016, 11, 12, 8, 0, 0); initialDate = new Date(initialDate.getTime() - initialDate.getTimezoneOffset()*60*1000); @@ -57,7 +55,7 @@ QUnit.module('Views', { {id: 6, user_id: session.uid, partner_id: 1, name: "event 6", start: "2016-12-18 08:00:00", stop: "2016-12-18 09:00:00", allday: false, partner_ids: [3], type: 3} ], check_access_rights: function () { - return $.when(true); + return Promise.resolve(true); } }, user: { @@ -98,7 +96,7 @@ QUnit.module('Views', { ] }, }; - } + }, }, function () { QUnit.module('CalendarView'); @@ -125,9 +123,8 @@ QUnit.module('Views', { '</form>', }; - QUnit.test('simple calendar rendering', function (assert) { - assert.expect(25); - var done = assert.async(); + QUnit.test('simple calendar rendering', async function (assert) { + assert.expect(24); this.data.event.records.push({ id: 7, @@ -141,7 +138,7 @@ QUnit.module('Views', { type: 1 }); - createAsyncView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -159,73 +156,72 @@ QUnit.module('Views', { '<field name="partner_ids" write_model="filter_partner" write_field="partner_id"/>'+ '</calendar>', archs: archs, - viewOptions: { initialDate: initialDate, }, - }).then(function (calendar) { - - assert.ok(calendar.$('.o_calendar_view').find('.fc-view-container').length, "should instance of fullcalendar"); - - var $sidebar = calendar.$('.o_calendar_sidebar'); - - assert.strictEqual($sidebar.find('.ui-state-active').text(), "12", "should highlight the target day"); - - // test view scales - - assert.containsN(calendar, '.fc-event', 9, "should display 9 events on the week (4 event + 5 days event)"); - assert.strictEqual($sidebar.find('.o_selected_range').length, 7, "week scale should highlight 7 days in mini calendar"); - - calendar.$buttons.find('.o_calendar_button_day').trigger('click'); // display only one day - assert.containsN(calendar, '.fc-event', 2, "should display 2 events on the day"); - assert.strictEqual($sidebar.find('.o_selected_range').length, 1, "should highlight the target day in mini calendar"); - - calendar.$buttons.find('.o_calendar_button_month').trigger('click'); // display all the month - assert.containsN(calendar, '.fc-event', 7, "should display 7 events on the month (5 events + 2 week event - 1 'event 6' is filtered + 1 'Undefined event')"); - assert.strictEqual($sidebar.find('.o_selected_range').length, 31, "month scale should highlight all days in mini calendar"); - - // test filters - - assert.strictEqual($sidebar.find('.o_calendar_filter').length, 2, "should display 3 filters"); - - var $typeFilter = $sidebar.find('.o_calendar_filter:has(h3:contains(user))'); - assert.ok($typeFilter.length, "should display 'user' filter"); - assert.strictEqual($typeFilter.find('.o_calendar_filter_item').length, 3, "should display 3 filter items for 'user'"); + }); - // filters which has no value should show with string "Undefined", should not have any user image and should show at the last - assert.strictEqual($typeFilter.find('.o_calendar_filter_item:last').data('value'), false, "filters having false value should be displayed at last in filter items"); - assert.strictEqual($typeFilter.find('.o_calendar_filter_item:last span').text(), "Undefined", "filters having false value should display 'Undefined' string"); - assert.strictEqual($typeFilter.find('.o_calendar_filter_item:last label img').length, 0, "filters having false value should not have any user image"); + assert.ok(calendar.$('.o_calendar_view').find('.fc-view-container').length, + "should instance of fullcalendar"); - var $attendeesFilter = $sidebar.find('.o_calendar_filter:has(h3:contains(attendees))'); - assert.ok($attendeesFilter.length, "should display 'attendees' filter"); - assert.strictEqual($attendeesFilter.find('.o_calendar_filter_item').length, 3, "should display 3 filter items for 'attendees' who use write_model (2 saved + Everything)"); - assert.ok($attendeesFilter.find('.o_field_many2one').length, "should display one2many search bar for 'attendees' filter"); + var $sidebar = calendar.$('.o_calendar_sidebar'); - assert.containsN(calendar, '.fc-event', 7, - "should display 7 events ('event 5' counts for 2 because it spans two weeks and thus generate two fc-event elements)"); - testUtils.dom.click(calendar.$('.o_calendar_filter .custom-checkbox input').first()); - assert.containsN(calendar, '.fc-event', 4, "should now only display 4 event"); - testUtils.dom.click(calendar.$('.o_calendar_filter .custom-checkbox input').eq(1)); - assert.containsNone(calendar, '.fc-event', "should not display any event anymore"); + // test view scales + assert.containsN(calendar, '.fc-event', 9, + "should display 9 events on the week (4 event + 5 days event)"); + assert.containsN($sidebar, '.o_selected_range', 7, + "week scale should highlight 7 days in mini calendar"); - // test search bar in filter - $sidebar.find('input[type="text"]').trigger('click'); - assert.strictEqual($('ul.ui-autocomplete li:not(.o_m2o_dropdown_option)').length, 2, "should display 2 choices in one2many autocomplete"); // TODO: remove :not(.o_m2o_dropdown_option) because can't have "create & edit" choice - $('ul.ui-autocomplete li:first').trigger('click'); - assert.strictEqual($sidebar.find('.o_calendar_filter:has(h3:contains(attendees)) .o_calendar_filter_item').length, 4, "should display 4 filter items for 'attendees'"); - $sidebar.find('input[type="text"]').trigger('click'); - assert.strictEqual($('ul.ui-autocomplete li:not(.o_m2o_dropdown_option)').text(), "partner 4", "should display the last choice in one2many autocomplete"); // TODO: remove :not(.o_m2o_dropdown_option) because can't have "create & edit" choice - $sidebar.find('.o_calendar_filter_item .o_remove').first().trigger('click'); - assert.ok($('.modal-footer button.btn:contains(Ok)').length, "should display the confirm message"); - $('.modal-footer button.btn:contains(Ok)').trigger('click'); - assert.strictEqual($sidebar.find('.o_calendar_filter:has(h3:contains(attendees)) .o_calendar_filter_item').length, 3, "click on remove then should display 3 filter items for 'attendees'"); - calendar.destroy(); - done(); - }); + await testUtils.dom.click(calendar.$buttons.find('.o_calendar_button_day')); // display only one day + assert.containsN(calendar, '.fc-event', 2, "should display 2 events on the day"); + assert.containsOnce($sidebar, '.o_selected_range', + "should highlight the target day in mini calendar"); + + await testUtils.dom.click(calendar.$buttons.find('.o_calendar_button_month')); // display all the month + assert.containsN(calendar, '.fc-event', 7, + "should display 7 events on the month (5 events + 2 week event - 1 'event 6' is filtered + 1 'Undefined event')"); + assert.containsN($sidebar, '.o_selected_range', 31, + "month scale should highlight all days in mini calendar"); + + // test filters + assert.containsN($sidebar, '.o_calendar_filter', 2, "should display 2 filters"); + + var $typeFilter = $sidebar.find('.o_calendar_filter:has(h3:contains(user))'); + assert.ok($typeFilter.length, "should display 'user' filter"); + assert.containsN($typeFilter, '.o_calendar_filter_item', 3, "should display 3 filter items for 'user'"); + + // filters which has no value should show with string "Undefined", should not have any user image and should show at the last + assert.strictEqual($typeFilter.find('.o_calendar_filter_item:last').data('value'), false, "filters having false value should be displayed at last in filter items"); + assert.strictEqual($typeFilter.find('.o_calendar_filter_item:last span').text(), "Undefined", "filters having false value should display 'Undefined' string"); + assert.strictEqual($typeFilter.find('.o_calendar_filter_item:last label img').length, 0, "filters having false value should not have any user image"); + + var $attendeesFilter = $sidebar.find('.o_calendar_filter:has(h3:contains(attendees))'); + assert.ok($attendeesFilter.length, "should display 'attendees' filter"); + assert.containsN($attendeesFilter, '.o_calendar_filter_item', 3, "should display 3 filter items for 'attendees' who use write_model (2 saved + Everything)"); + assert.ok($attendeesFilter.find('.o_field_many2one').length, "should display one2many search bar for 'attendees' filter"); + + assert.containsN(calendar, '.fc-event', 7, + "should display 7 events ('event 5' counts for 2 because it spans two weeks and thus generate two fc-event elements)"); + await testUtils.dom.click(calendar.$('.o_calendar_filter .custom-checkbox input').first()); + assert.containsN(calendar, '.fc-event', 4, "should now only display 4 event"); + await testUtils.dom.click(calendar.$('.o_calendar_filter .custom-checkbox input').eq(1)); + assert.containsNone(calendar, '.fc-event', "should not display any event anymore"); + + // test search bar in filter + await testUtils.dom.click($sidebar.find('input[type="text"]')); + assert.strictEqual($('ul.ui-autocomplete li:not(.o_m2o_dropdown_option)').length, 2,"should display 2 choices in one2many autocomplete"); // TODO: remove :not(.o_m2o_dropdown_option) because can't have "create & edit" choice + await testUtils.dom.click($('ul.ui-autocomplete li:first')); + assert.containsN($sidebar, '.o_calendar_filter:has(h3:contains(attendees)) .o_calendar_filter_item', 4, "should display 4 filter items for 'attendees'"); + await testUtils.dom.click($sidebar.find('input[type="text"]')); + assert.strictEqual($('ul.ui-autocomplete li:not(.o_m2o_dropdown_option)').text(), "partner 4", "should display the last choice in one2many autocomplete"); // TODO: remove :not(.o_m2o_dropdown_option) because can't have "create & edit" choice + await testUtils.dom.click($sidebar.find('.o_calendar_filter_item .o_remove').first(), {allowInvisible: true}); + assert.ok($('.modal-footer button.btn:contains(Ok)').length, "should display the confirm message"); + await testUtils.dom.click($('.modal-footer button.btn:contains(Ok)')); + assert.containsN($sidebar, '.o_calendar_filter:has(h3:contains(attendees)) .o_calendar_filter_item', 3, "click on remove then should display 3 filter items for 'attendees'"); + calendar.destroy(); }); - QUnit.test('breadcrumbs are updated with the displayed period', function (assert) { + QUnit.test('breadcrumbs are updated with the displayed period', async function (assert) { assert.expect(3); var archs = { @@ -246,35 +242,34 @@ QUnit.module('Views', { views: [[1, 'calendar']], }]; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: actions, archs: archs, data: this.data, }); - actionManager.doAction(1); - + await actionManager.doAction(1); // displays month mode by default assert.strictEqual($('.o_control_panel .breadcrumb-item').text(), 'Meetings Test (Dec 11 – 17, 2016)', "should display the current week"); // switch to day mode - testUtils.dom.click($('.o_control_panel .o_calendar_button_day')); + await testUtils.dom.click($('.o_control_panel .o_calendar_button_day')); assert.strictEqual($('.o_control_panel .breadcrumb-item').text(), 'Meetings Test (December 12, 2016)', "should display the current day"); // switch to month mode - testUtils.dom.click($('.o_control_panel .o_calendar_button_month')); + await testUtils.dom.click($('.o_control_panel .o_calendar_button_month')); assert.strictEqual($('.o_control_panel .breadcrumb-item').text(), 'Meetings Test (December 2016)', "should display the current month"); actionManager.destroy(); }); - QUnit.test('create and change events', function (assert) { + QUnit.test('create and change events', async function (assert) { assert.expect(26); - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -299,19 +294,19 @@ QUnit.module('Views', { // click on an existing event to open the formViewDialog - calendar.$('.fc-event:contains(event 4) .fc-content').trigger('click'); + await testUtils.dom.click(calendar.$('.fc-event:contains(event 4) .fc-content')); assert.ok($('.modal-body').length, "should open the form view in dialog when click on event"); assert.ok($('.modal-footer button.btn:contains(Edit)').length, "formViewDialog should be in readonly mode"); assert.ok($('.modal-footer button.btn:contains(Delete)').length, "formViewDialog should have a delete button"); - $('.modal-footer button.btn:contains(Edit)').trigger('click'); + await testUtils.dom.click($('.modal-footer button.btn:contains(Edit)')); assert.ok($('.modal-body').length, "should switch the modal in edit mode"); assert.notOk($('.modal-footer button.btn:contains(Delete)').length, "formViewDialog should not have a delete button in edit mode"); - testUtils.fields.editInput($('.modal-body input:first'), 'event 4 modified'); - $('.modal-footer button.btn:contains(Save)').trigger('click'); + await testUtils.fields.editInput($('.modal-body input:first'), 'event 4 modified'); + await testUtils.dom.click($('.modal-footer button.btn:contains(Save)')); assert.notOk($('.modal-body').length, "save button should close the modal"); assert.ok(calendar.$('.fc-event:contains(event 4 modified)').length, "should display the updated records"); @@ -322,11 +317,12 @@ QUnit.module('Views', { testUtils.dom.triggerMouseEvent($cell, "mousedown"); testUtils.dom.triggerMouseEvent($cell, "mouseup"); + await testUtils.nextTick(); assert.ok($('.modal-sm').length, "should open the quick create dialog"); - testUtils.fields.editInput($('.modal-body input:first'), 'new event in quick create'); - $('.modal-footer button.btn:contains(Create)').trigger('click').trigger('click'); + await testUtils.fields.editInput($('.modal-body input:first'), 'new event in quick create'); + await testUtils.dom.click($('.modal-footer button.btn:contains(Create)')); assert.strictEqual(calendar.$('.fc-event:contains(new event in quick create)').length, 1, "should display the new record after quick create"); assert.containsN(calendar, 'td.fc-event-container[colspan]', 2, "should the new record have only one day"); @@ -335,15 +331,19 @@ QUnit.module('Views', { testUtils.dom.triggerMouseEvent($cell, "mousedown"); testUtils.dom.triggerMouseEvent($cell, "mouseup"); + await testUtils.nextTick(); assert.ok($('.modal-sm').length, "should open the quick create dialog"); + await testUtils.fields.editInput($('.modal-body input:first'), + 'new event in quick create validated by pressing enter key.'); $('.modal-body input:first') .val('new event in quick create validated by pressing enter key.') .trigger($.Event('keyup', {keyCode: $.ui.keyCode.ENTER})) .trigger($.Event('keyup', {keyCode: $.ui.keyCode.ENTER})); - - assert.strictEqual(calendar.$('.fc-event:contains(new event in quick create validated by pressing enter key.)').length, 1, "should display the new record by pressing enter key"); + await testUtils.nextTick(); + assert.containsOnce(calendar, '.fc-event:contains(new event in quick create validated by pressing enter key.)', + "should display the new record by pressing enter key"); // create a new event and edit it @@ -352,11 +352,12 @@ QUnit.module('Views', { testUtils.dom.triggerMouseEvent($cell, "mousedown"); testUtils.dom.triggerMouseEvent($cell, "mouseup"); + await testUtils.nextTick(); assert.strictEqual($('.modal-sm').length, 1, "should open the quick create dialog"); testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); - $('.modal-footer button.btn:contains(Edit)').trigger('click'); + await testUtils.dom.click($('.modal-footer button.btn:contains(Edit)')); assert.strictEqual($('.modal-lg .o_form_view').length, 1, "should open the slow create dialog"); assert.strictEqual($('.modal-lg .modal-title').text(), "Create: Events", @@ -364,9 +365,10 @@ QUnit.module('Views', { assert.strictEqual($('.modal-lg .o_form_view input[name="name"]').val(), "coucou", "should have set the name from the quick create dialog"); - $('.modal-lg button.btn:contains(Save)').trigger('click'); + await testUtils.dom.click($('.modal-lg button.btn:contains(Save)')); - assert.strictEqual(calendar.$('.fc-event:contains(coucou)').length, 1, "should display the new record with string attribute"); + assert.strictEqual(calendar.$('.fc-event:contains(coucou)').length, 1, + "should display the new record with string attribute"); // create a new event with 2 days @@ -375,41 +377,44 @@ QUnit.module('Views', { testUtils.dom.triggerMouseEvent($cell, "mousedown"); testUtils.dom.triggerMouseEvent($cell.next(), "mousemove"); testUtils.dom.triggerMouseEvent($cell.next(), "mouseup"); + await testUtils.nextTick(); testUtils.fields.editInput($('.modal-dialog input:first'), 'new event in quick create 2'); - $('.modal-footer button.btn:contains(Edit)').trigger('click'); + await testUtils.dom.click($('.modal-footer button.btn:contains(Edit)')); - assert.strictEqual($('.modal-lg input:first').val(), 'new event in quick create 2', "should open the formViewDialog with default values"); + assert.strictEqual($('.modal-lg input:first').val(),'new event in quick create 2', + "should open the formViewDialog with default values"); - $('.modal-lg button.btn:contains(Save)').trigger('click'); + await testUtils.dom.click($('.modal-lg button.btn:contains(Save)')); assert.notOk($('.modal').length, "should close dialogs"); var $newevent2 = calendar.$('.fc-event:contains(new event in quick create 2)'); assert.ok($newevent2.length, "should display the 2 days new record"); - assert.hasAttrValue($newevent2.closest('.fc-event-container'), 'colspan', "2", "the new record should have 2 days"); + assert.hasAttrValue($newevent2.closest('.fc-event-container'), + 'colspan', "2","the new record should have 2 days"); // delete the a record - calendar.$('.fc-event:contains(event 4) .fc-content').trigger('click'); - $('.modal-footer button.btn:contains(Delete)').trigger('click'); + await testUtils.dom.click(calendar.$('.fc-event:contains(event 4) .fc-content')); + await testUtils.dom.click($('.modal-footer button.btn:contains(Delete)')); assert.ok($('.modal-footer button.btn:contains(Ok)').length, "should display the confirm message"); - $('.modal-footer button.btn:contains(Ok)').trigger('click'); + await testUtils.dom.click($('.modal-footer button.btn:contains(Ok)')); assert.notOk(calendar.$('.fc-event:contains(event 4) .fc-content').length, "the record should be deleted"); assert.containsN(calendar, '.fc-event-container .fc-event', 10, "should display 10 events"); // move to next month - testUtils.dom.click(calendar.$buttons.find('.o_calendar_button_next')); + await testUtils.dom.click(calendar.$buttons.find('.o_calendar_button_next')); assert.containsNone(calendar, '.fc-event-container .fc-event', "should display 0 events"); calendar.destroy(); }); - QUnit.test('quickcreate switching to actual create for required fields', function (assert) { + QUnit.test('quickcreate switching to actual create for required fields', async function (assert) { assert.expect(4); var event = $.Event(); - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -430,11 +435,14 @@ QUnit.module('Views', { }, mockRPC: function (route, args) { if (args.method === "create") { - return $.Deferred().reject({ - code: 200, - data: {}, - message: "Odoo server error", - }, event); + return Promise.reject({ + message: { + code: 200, + data: {}, + message: "Odoo server error", + }, + event: event + }); } return this._super(route, args); }, @@ -444,12 +452,14 @@ QUnit.module('Views', { var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); testUtils.dom.triggerMouseEvent($cell, "mousedown"); testUtils.dom.triggerMouseEvent($cell, "mouseup"); + await testUtils.nextTick(); assert.strictEqual($('.modal-sm .modal-title').text(), 'Create: Events', "should open the quick create dialog"); - testUtils.fields.editInput($('.modal-body input:first'), 'new event in quick create'); - $('.modal-footer button.btn:contains(Create)').trigger('click').trigger('click'); + await testUtils.fields.editInput($('.modal-body input:first'), 'new event in quick create'); + await testUtils.dom.click($('.modal-footer button.btn:contains(Create)')); + await testUtils.nextTick(); // If the event is not default-prevented, a traceback will be raised, which we do not want assert.ok(event.isDefaultPrevented(), "fail deferred event should have been default-prevented"); @@ -462,12 +472,12 @@ QUnit.module('Views', { calendar.destroy(); }); - QUnit.test('create event with timezone in week mode European locale', function (assert) { + QUnit.test('create event with timezone in week mode European locale', async function (assert) { assert.expect(5); this.data.event.records = []; - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -507,11 +517,7 @@ QUnit.module('Views', { } return this._super(route, args); }, - }); - - - var $view = $('#qunit-fixture').contents(); - $view.prependTo('body'); // => select with click position + }, {positionalClicks: true}); var top = calendar.$('.fc-axis:contains(8:00)').offset().top + 5; var left = calendar.$('.fc-day:eq(2)').offset().left + 5; @@ -520,7 +526,6 @@ QUnit.module('Views', { testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); } catch (e) { calendar.destroy(); - $view.remove(); throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); } @@ -529,9 +534,10 @@ QUnit.module('Views', { assert.strictEqual(calendar.$('.fc-content .fc-time').text(), "08:00 - 10:00", "should display the time in the calendar sticker"); - testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mouseup"); - testUtils.fields.editInput($('.modal input:first'), 'new event'); - $('.modal button.btn:contains(Create)').trigger('click'); + await testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mouseup"); + await testUtils.nextTick(); + await testUtils.fields.editInput($('.modal input:first'), 'new event'); + await testUtils.dom.click($('.modal button.btn:contains(Create)')); var $newevent = calendar.$('.fc-event:contains(new event)'); assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "08:00-10:00newevent12/13/201608:00:00False", @@ -550,13 +556,12 @@ QUnit.module('Views', { // delete record - $newevent.trigger('click'); - $('.modal button.btn-secondary:contains(Delete)').trigger('click'); - $('.modal button.btn-primary:contains(Ok)').trigger('click'); + await testUtils.dom.click($newevent); + await testUtils.dom.click($('.modal button.btn-secondary:contains(Delete)')); + await testUtils.dom.click($('.modal button.btn-primary:contains(Ok)')); assert.containsNone(calendar, '.fc-content', "should delete the record"); calendar.destroy(); - $view.remove(); }); QUnit.test('default week start (US)', function (assert) { @@ -564,7 +569,7 @@ QUnit.module('Views', { assert.expect(1); var done = assert.async(); - createAsyncView({ + createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -592,7 +597,7 @@ QUnit.module('Views', { assert.expect(1); var done = assert.async(); - createAsyncView({ + createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -625,7 +630,7 @@ QUnit.module('Views', { assert.expect(1); var done = assert.async(); - createAsyncView({ + createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -651,7 +656,7 @@ QUnit.module('Views', { }); }); - QUnit.test('create event with timezone in week mode with formViewDialog European locale', function (assert) { + QUnit.test('create event with timezone in week mode with formViewDialog European locale', async function (assert) { assert.expect(8); this.data.event.records = []; @@ -667,7 +672,7 @@ QUnit.module('Views', { } }; - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -709,10 +714,7 @@ QUnit.module('Views', { } return this._super(route, args); }, - }); - - var $view = $('#qunit-fixture').contents(); - $view.prependTo('body'); // => select with click position + }, {positionalClicks: true}); var top = calendar.$('.fc-axis:contains(8:00)').offset().top + 5; var left = calendar.$('.fc-day:eq(2)').offset().left + 5; @@ -721,42 +723,42 @@ QUnit.module('Views', { testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); } catch (e) { calendar.destroy(); - $view.remove(); throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); } testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mousemove"); testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mouseup"); - testUtils.fields.editInput($('.modal input:first'), 'new event'); - $('.modal button.btn:contains(Edit)').trigger('click'); - - assert.strictEqual($('.o_field_widget[name="start"] input').val(), "12/13/2016 08:00:00", - "should display the datetime"); + await testUtils.nextTick(); + await testUtils.fields.editInput($('.modal input:first'), 'new event'); + await testUtils.dom.click($('.modal button.btn:contains(Edit)')); - $('.modal-lg .o_field_boolean[name="allday"] input').trigger('click'); + assert.strictEqual($('.o_field_widget[name="start"] input').val(), + "12/13/2016 08:00:00", "should display the datetime"); - assert.strictEqual($('.o_field_widget[name="start_date"] input').val(), "12/13/2016", - "should display the date"); + await testUtils.dom.click($('.modal-lg .o_field_boolean[name="allday"] input')); + await testUtils.nextTick(); + assert.strictEqual($('input[name="start_date"]').val(), + "12/13/2016", "should display the date"); - $('.modal-lg .o_field_boolean[name="allday"] input').trigger('click'); + await testUtils.dom.click($('.modal-lg .o_field_boolean[name="allday"] input')); - assert.strictEqual($('.o_field_widget[name="start"] input').val(), "12/13/2016 02:00:00", - "should display the datetime from the date with the timezone"); + assert.strictEqual($('.o_field_widget[name="start"] input').val(), + "12/13/2016 02:00:00", "should display the datetime from the date with the timezone"); // use datepicker to enter a date: 12/13/2016 08:00:00 testUtils.dom.openDatepicker($('.o_field_widget[name="start"].o_datepicker')); - $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]').trigger('click'); - $('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour').trigger('click'); - $('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(08)').trigger('click'); - $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]').trigger('click'); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(08)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]')); // use datepicker to enter a date: 12/13/2016 10:00:00 testUtils.dom.openDatepicker($('.o_field_widget[name="stop"].o_datepicker')); - $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]').trigger('click'); - $('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour').trigger('click'); - $('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(10)').trigger('click'); - $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]').trigger('click'); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(10)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]')); - $('.modal-lg button.btn:contains(Save)').trigger('click'); + await testUtils.dom.click($('.modal-lg button.btn:contains(Save)')); var $newevent = calendar.$('.fc-event:contains(new event)'); assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "08:00-10:00newevent", @@ -787,6 +789,7 @@ QUnit.module('Views', { left = calendar.$('.fc-day:eq(1)').offset().left + 5; testUtils.dom.triggerPositionalMouseEvent(left, top, "mousemove"); testUtils.dom.triggerPositionalMouseEvent(left, top, "mouseup"); + await testUtils.nextTick(); // Move to "All day" expectedEvent = { @@ -798,17 +801,17 @@ QUnit.module('Views', { top = calendar.$('.fc-day:eq(1)').offset().top + 5; testUtils.dom.triggerPositionalMouseEvent(left, top, "mousemove"); testUtils.dom.triggerPositionalMouseEvent(left, top, "mouseup"); + await testUtils.nextTick(); calendar.destroy(); - $view.remove(); }); - QUnit.test('create event with timezone in week mode American locale', function (assert) { + QUnit.test('create event with timezone in week mode American locale', async function (assert) { assert.expect(5); this.data.event.records = []; - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -848,11 +851,7 @@ QUnit.module('Views', { } return this._super(route, args); }, - }); - - - var $view = $('#qunit-fixture').contents(); - $view.prependTo('body'); // => select with click position + }, {positionalClicks: true}); var top = calendar.$('.fc-axis:contains(8am)').offset().top + 5; var left = calendar.$('.fc-day:eq(2)').offset().left + 5; @@ -861,7 +860,6 @@ QUnit.module('Views', { testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); } catch (e) { calendar.destroy(); - $view.remove(); throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); } @@ -871,8 +869,9 @@ QUnit.module('Views', { "should display the time in the calendar sticker"); testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mouseup"); + await testUtils.nextTick(); testUtils.fields.editInput($('.modal input:first'), 'new event'); - $('.modal button.btn:contains(Create)').trigger('click'); + await testUtils.dom.click($('.modal button.btn:contains(Create)')); var $newevent = calendar.$('.fc-event:contains(new event)'); assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "8:00am-10:00amnewevent12/13/201608:00:00False", @@ -891,16 +890,15 @@ QUnit.module('Views', { // delete record - $newevent.trigger('click'); - $('.modal button.btn-secondary:contains(Delete)').trigger('click'); - $('.modal button.btn-primary:contains(Ok)').trigger('click'); + await testUtils.dom.click($newevent); + await testUtils.dom.click($('.modal button.btn-secondary:contains(Delete)')); + await testUtils.dom.click($('.modal button.btn-primary:contains(Ok)')); assert.containsNone(calendar, '.fc-content', "should delete the record"); calendar.destroy(); - $view.remove(); }); - QUnit.test('create event with timezone in week mode with formViewDialog American locale', function (assert) { + QUnit.test('create event with timezone in week mode with formViewDialog American locale', async function (assert) { assert.expect(8); this.data.event.records = []; @@ -916,7 +914,7 @@ QUnit.module('Views', { } }; - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -958,10 +956,7 @@ QUnit.module('Views', { } return this._super(route, args); }, - }); - - var $view = $('#qunit-fixture').contents(); - $view.prependTo('body'); // => select with click position + }, {positionalClicks: true}); var top = calendar.$('.fc-axis:contains(8am)').offset().top + 5; var left = calendar.$('.fc-day:eq(2)').offset().left + 5; @@ -970,42 +965,42 @@ QUnit.module('Views', { testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); } catch (e) { calendar.destroy(); - $view.remove(); throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); } testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mousemove"); testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mouseup"); + await testUtils.nextTick(); testUtils.fields.editInput($('.modal input:first'), 'new event'); - $('.modal button.btn:contains(Edit)').trigger('click'); + await testUtils.dom.click($('.modal button.btn:contains(Edit)')); assert.strictEqual($('.o_field_widget[name="start"] input').val(), "12/13/2016 08:00:00", "should display the datetime"); - $('.modal-lg .o_field_boolean[name="allday"] input').trigger('click'); + await testUtils.dom.click($('.modal-lg .o_field_boolean[name="allday"] input')); assert.strictEqual($('.o_field_widget[name="start_date"] input').val(), "12/13/2016", "should display the date"); - $('.modal-lg .o_field_boolean[name="allday"] input').trigger('click'); + await testUtils.dom.click($('.modal-lg .o_field_boolean[name="allday"] input')); assert.strictEqual($('.o_field_widget[name="start"] input').val(), "12/13/2016 02:00:00", "should display the datetime from the date with the timezone"); // use datepicker to enter a date: 12/13/2016 08:00:00 testUtils.dom.openDatepicker($('.o_field_widget[name="start"].o_datepicker')); - $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]').trigger('click'); - $('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour').trigger('click'); - $('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(08)').trigger('click'); - $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]').trigger('click'); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(08)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]')); // use datepicker to enter a date: 12/13/2016 10:00:00 testUtils.dom.openDatepicker($('.o_field_widget[name="stop"].o_datepicker')); - $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]').trigger('click'); - $('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour').trigger('click'); - $('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(10)').trigger('click'); - $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]').trigger('click'); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(10)')); + await testUtils.dom.click($('.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]')); - $('.modal-lg button.btn:contains(Save)').trigger('click'); + await testUtils.dom.click($('.modal-lg button.btn:contains(Save)')); var $newevent = calendar.$('.fc-event:contains(new event)'); assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "8:00am-10:00amnewevent", @@ -1036,6 +1031,7 @@ QUnit.module('Views', { left = calendar.$('.fc-day:eq(1)').offset().left + 5; testUtils.dom.triggerPositionalMouseEvent(left, top, "mousemove"); testUtils.dom.triggerPositionalMouseEvent(left, top, "mouseup"); + await testUtils.nextTick(); // Move to "All day" expectedEvent = { @@ -1047,15 +1043,15 @@ QUnit.module('Views', { top = calendar.$('.fc-day:eq(1)').offset().top + 5; testUtils.dom.triggerPositionalMouseEvent(left, top, "mousemove"); testUtils.dom.triggerPositionalMouseEvent(left, top, "mouseup"); + await testUtils.nextTick(); calendar.destroy(); - $view.remove(); }); - QUnit.test('check calendar week column timeformat and event content timeformat', function (assert) { + QUnit.test('check calendar week column timeformat and event content timeformat', async function (assert) { assert.expect(2); - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -1079,12 +1075,12 @@ QUnit.module('Views', { calendar.destroy(); }); - QUnit.test('create all day event in week mode', function (assert) { + QUnit.test('create all day event in week mode', async function (assert) { assert.expect(3); this.data.event.records = []; - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -1107,26 +1103,22 @@ QUnit.module('Views', { return 120; }, }, - }); - - var $view = $('#qunit-fixture').contents(); - $view.prependTo('body'); // => select with click position - + }, {positionalClicks: true}); var pos = calendar.$('.fc-bg td:eq(4)').offset(); try { testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousedown"); } catch (e) { calendar.destroy(); - $view.remove(); throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); } pos = calendar.$('.fc-bg td:eq(5)').offset(); testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousemove"); testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mouseup"); + await testUtils.nextTick(); testUtils.fields.editInput($('.modal input:first'), 'new event'); - $('.modal button.btn:contains(Create)').trigger('click'); + await testUtils.dom.click($('.modal button.btn:contains(Create)')); var $newevent = calendar.$('.fc-event:contains(new event)'); assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "newevent", @@ -1146,15 +1138,14 @@ QUnit.module('Views', { "the new record should have the utc datetime (quickCreate)"); calendar.destroy(); - $view.remove(); }); - QUnit.test('create all day event in week mode (no quickCreate)', function (assert) { + QUnit.test('create all day event in week mode (no quickCreate)', async function (assert) { assert.expect(1); this.data.event.records = []; - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -1186,18 +1177,13 @@ QUnit.module('Views', { "should send the correct data to create events"); }, }, - }); - - var $view = $('#qunit-fixture').contents(); - $view.prependTo('body'); // => select with click position - + }, {positionalClicks: true}); var pos = calendar.$('.fc-bg td:eq(4)').offset(); try { testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousedown"); } catch (e) { calendar.destroy(); - $view.remove(); throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); } pos = calendar.$('.fc-bg td:eq(5)').offset(); @@ -1205,15 +1191,14 @@ QUnit.module('Views', { testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mouseup"); calendar.destroy(); - $view.remove(); }); - QUnit.test('create event in month mode', function (assert) { + QUnit.test('create event in month mode', async function (assert) { assert.expect(4); this.data.event.records = []; - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -1246,25 +1231,22 @@ QUnit.module('Views', { } return this._super(route, args); }, - }); - - var $view = $('#qunit-fixture').contents(); - $view.prependTo('body'); // => select with click position + }, {positionalClicks: true}); var pos = calendar.$('.fc-bg td:eq(20)').offset(); try { testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousedown"); } catch (e) { calendar.destroy(); - $view.remove(); throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); } pos = calendar.$('.fc-bg td:eq(21)').offset(); testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousemove"); testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mouseup"); + await testUtils.nextTick(); testUtils.fields.editInput($('.modal input:first'), 'new event'); - $('.modal button.btn:contains(Create)').trigger('click'); + await testUtils.dom.click($('.modal button.btn:contains(Create)')); var $newevent = calendar.$('.fc-event:contains(new event)'); assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "newevent", @@ -1281,13 +1263,12 @@ QUnit.module('Views', { }, "the new record should have the utc datetime (quickCreate)"); calendar.destroy(); - $view.remove(); }); - QUnit.test('use mini calendar', function (assert) { + QUnit.test('use mini calendar', async function (assert) { assert.expect(12); - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -1314,36 +1295,36 @@ QUnit.module('Views', { assert.containsOnce(calendar, '.fc-agendaWeek-view', "should be in week mode"); assert.containsN(calendar, '.fc-event', 9, "should display 9 events on the week (4 event + 5 days event)"); - testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(19)')); + await testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(19)')); // Clicking on a day in another week should switch to the other week view assert.containsOnce(calendar, '.fc-agendaWeek-view', "should be in week mode"); assert.containsN(calendar, '.fc-event', 4, "should display 4 events on the week (1 event + 3 days event)"); // Clicking on a day in the same week should switch to that particular day view - testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(18)')); + await testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(18)')); assert.containsOnce(calendar, '.fc-agendaDay-view', "should be in day mode"); assert.containsN(calendar, '.fc-event', 2, "should display 2 events on the day"); // Clicking on the same day should toggle between day, month and week views - testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(18)')); + await testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(18)')); assert.containsOnce(calendar, '.fc-month-view', "should be in month mode"); assert.containsN(calendar, '.fc-event', 7, "should display 7 events on the month (event 5 is on multiple weeks and generates to .fc-event)"); - testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(18)')); + await testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(18)')); assert.containsOnce(calendar, '.fc-agendaWeek-view', "should be in week mode"); assert.containsN(calendar, '.fc-event', 4, "should display 4 events on the week (1 event + 3 days event)"); - testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(18)')); + await testUtils.dom.click(calendar.$('.o_calendar_mini a:contains(18)')); assert.containsOnce(calendar, '.fc-agendaDay-view', "should be in day mode"); assert.containsN(calendar, '.fc-event', 2, "should display 2 events on the day"); calendar.destroy(); }); - QUnit.test('rendering, with many2many', function (assert) { + QUnit.test('rendering, with many2many', async function (assert) { assert.expect(5); this.data.event.fields.partner_ids.type = 'many2many'; this.data.event.records[0].partner_ids = [1,2,3,4,5]; this.data.partner.records.push({id: 5, display_name: "partner 5", image: 'EEE'}); - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -1365,21 +1346,21 @@ QUnit.module('Views', { "should have 3 avatars in the side bar"); var $event1Avatars = calendar.$('.fc-event .o_calendar_avatars').first(); - assert.strictEqual($event1Avatars.find('img').length, 1, "should have 1 avatar"); - assert.strictEqual($event1Avatars.find('span').length, 0, + assert.containsOnce($event1Avatars, 'img', "should have 1 avatar"); + assert.containsNone($event1Avatars, 'span', "should not have a span for more attendees since there is only one"); var $event2Avatars = calendar.$('.fc-event:contains(All day) .o_calendar_avatars'); - assert.strictEqual($event2Avatars.find('img').length, 3, "should have 3 avatars"); + assert.containsN($event2Avatars, 'img', 3, "should have 3 avatars"); assert.strictEqual($event2Avatars.find('span').text(), '+2', "should indicate there are 2 more attendees that we don't show"); calendar.destroy(); }); - QUnit.test('open form view', function (assert) { + QUnit.test('open form view', async function (assert) { assert.expect(3); - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -1399,7 +1380,7 @@ QUnit.module('Views', { }, mockRPC: function (route, args) { if (args.method === "get_formview_id") { - return $.Deferred().resolve('A view'); + return Promise.resolve('A view'); } return this._super(route, args); }, @@ -1419,13 +1400,14 @@ QUnit.module('Views', { }, "should open the form view"); }); - calendar.$('.fc-event:contains(event 4) .fc-content').trigger('click'); + await testUtils.dom.click(calendar.$('.fc-event:contains(event 4) .fc-content')); // create a new event and edit it var $cell = calendar.$('.fc-day-grid .fc-row:eq(4) .fc-day:eq(2)'); testUtils.dom.triggerMouseEvent($cell, "mousedown"); testUtils.dom.triggerMouseEvent($cell, "mouseup"); + await testUtils.nextTick(); testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); testUtils.mock.intercept(calendar, 'do_action', function (event) { @@ -1445,17 +1427,17 @@ QUnit.module('Views', { "should open the form view with the context default values"); }); - $('.modal button.btn:contains(Edit)').trigger('click'); + testUtils.dom.click($('.modal button.btn:contains(Edit)')); calendar.destroy(); assert.strictEqual($('#ui-datepicker-div:empty').length, 0, "should have a clean body"); }); - QUnit.test('create and edit event in month mode (all_day: false)', function (assert) { + QUnit.test('create and edit event in month mode (all_day: false)', async function (assert) { assert.expect(2); - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -1483,7 +1465,8 @@ QUnit.module('Views', { var $cell = calendar.$('.fc-day-grid .fc-row:eq(4) .fc-day:eq(2)'); testUtils.dom.triggerMouseEvent($cell, "mousedown"); testUtils.dom.triggerMouseEvent($cell, "mouseup"); - testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); + await testUtils.nextTick(); + await testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); testUtils.mock.intercept(calendar, 'do_action', function (event) { assert.deepEqual(event.data.action, @@ -1501,18 +1484,18 @@ QUnit.module('Views', { "should open the form view with the context default values"); }); - $('.modal button.btn:contains(Edit)').trigger('click'); + await testUtils.dom.click($('.modal button.btn:contains(Edit)')); calendar.destroy(); assert.strictEqual($('#ui-datepicker-div:empty').length, 0, "should have a clean body"); }); - QUnit.test('readonly date_start field', function (assert) { + QUnit.test('readonly date_start field', async function (assert) { assert.expect(4); this.data.event.fields.start.readonly = true; - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -1532,7 +1515,7 @@ QUnit.module('Views', { }, mockRPC: function (route, args) { if (args.method === "get_formview_id") { - return $.when(false); + return Promise.resolve(false); } return this._super(route, args); }, @@ -1554,14 +1537,15 @@ QUnit.module('Views', { }, "should open the form view"); }); - calendar.$('.fc-event:contains(event 4) .fc-content').trigger('click'); + await testUtils.dom.click(calendar.$('.fc-event:contains(event 4) .fc-content')); // create a new event and edit it var $cell = calendar.$('.fc-day-grid .fc-row:eq(4) .fc-day:eq(2)'); testUtils.dom.triggerMouseEvent($cell, "mousedown"); testUtils.dom.triggerMouseEvent($cell, "mouseup"); - testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); + await testUtils.nextTick(); + await testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); testUtils.mock.intercept(calendar, 'do_action', function (event) { assert.deepEqual(event.data.action, @@ -1580,14 +1564,14 @@ QUnit.module('Views', { "should open the form view with the context default values"); }); - $('.modal button.btn:contains(Edit)').trigger('click'); + await testUtils.dom.click($('.modal button.btn:contains(Edit)')); calendar.destroy(); assert.strictEqual($('#ui-datepicker-div:empty').length, 0, "should have a clean body"); }); - QUnit.test('"all" filter', function (assert) { + QUnit.test('"all" filter', async function (assert) { assert.expect(6); var interval = [ @@ -1603,7 +1587,7 @@ QUnit.module('Views', { var i = 0; - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -1636,26 +1620,26 @@ QUnit.module('Views', { "should display 9 events on the week"); // Select the events only associated with partner 2 - testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-id=2] input')); + await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-id=2] input')); assert.containsN(calendar, '.fc-event', 4, "should display 4 events on the week"); // Click on the 'all' filter to reload all events - testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=all] input')); + await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=all] input')); assert.containsN(calendar, '.fc-event', 9, "should display 9 events on the week"); calendar.destroy(); }); - QUnit.test('create event with filters', function (assert) { + QUnit.test('create event with filters', async function (assert) { assert.expect(7); this.data.event.fields.user_id.default = 5; this.data.event.fields.partner_id.default = 3; this.data.user.records.push({id: 5, display_name: "user 5", partner_id: 3}); - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -1675,34 +1659,32 @@ QUnit.module('Views', { viewOptions: { initialDate: initialDate, }, - }); - - var $view = $('#qunit-fixture').contents(); - $view.prependTo('body'); // => select with click position + }, {positionalClicks: true}); - testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); + await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); assert.containsN(calendar, '.o_calendar_filter_item', 5, "should display 5 filter items"); assert.containsN(calendar, '.fc-event', 3, "should display 3 events"); // quick create a record var left = calendar.$('.fc-bg td:eq(4)').offset().left+15; - var top = calendar.$('.fc-slats tr:eq(4) td:first').offset().top+15; + var top = calendar.$('.fc-slats tr:eq(12) td:first').offset().top+15; try { testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); } catch (e) { calendar.destroy(); - $view.remove(); throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); } testUtils.dom.triggerPositionalMouseEvent(left, top + 200, "mousemove"); testUtils.dom.triggerPositionalMouseEvent(left, top + 200, "mouseup"); + await testUtils.nextTick(); - testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); - $('.modal-footer button.btn:contains(Create)').trigger('click'); + await testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); + await testUtils.dom.click($('.modal-footer button.btn:contains(Create)')); assert.containsN(calendar, '.o_calendar_filter_item', 6, "should add the missing filter (active)"); assert.containsN(calendar, '.fc-event', 4, "should display the created item"); + await testUtils.nextTick(); // change default value for quick create an hide record this.data.event.fields.user_id.default = 4; @@ -1710,26 +1692,26 @@ QUnit.module('Views', { // quick create and other record left = calendar.$('.fc-bg td:eq(3)').offset().left+15; - top = calendar.$('.fc-slats tr:eq(4) td:first').offset().top+15; + top = calendar.$('.fc-slats tr:eq(12) td:first').offset().top+15; testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); testUtils.dom.triggerPositionalMouseEvent(left, top + 200, "mousemove"); testUtils.dom.triggerPositionalMouseEvent(left, top + 200, "mouseup"); + await testUtils.nextTick(); testUtils.fields.editInput($('.modal-body input:first'), 'coucou 2'); - $('.modal-footer button.btn:contains(Create)').trigger('click'); + await testUtils.dom.click($('.modal-footer button.btn:contains(Create)')); assert.containsN(calendar, '.o_calendar_filter_item', 6, "should have the same filters"); assert.containsN(calendar, '.fc-event', 4, "should not display the created item"); - testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); + await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); assert.containsN(calendar, '.fc-event', 11, "should display all records"); calendar.destroy(); - $view.remove(); }); - QUnit.test('create event with filters (no quickCreate)', function (assert) { + QUnit.test('create event with filters (no quickCreate)', async function (assert) { assert.expect(4); this.data.event.fields.user_id.default = 5; @@ -1740,7 +1722,7 @@ QUnit.module('Views', { partner_id: 3 }); - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -1772,42 +1754,39 @@ QUnit.module('Views', { viewOptions: { initialDate: initialDate, }, - }); + }, {positionalClicks: true}); - var $view = $('#qunit-fixture').contents(); - $view.prependTo('body'); // => select with click position - - testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); + await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); assert.containsN(calendar, '.o_calendar_filter_item', 5, "should display 5 filter items"); assert.containsN(calendar, '.fc-event', 3, "should display 3 events"); + await testUtils.nextTick(); // quick create a record var left = calendar.$('.fc-bg td:eq(4)').offset().left+15; - var top = calendar.$('.fc-slats tr:eq(4) td:first').offset().top+15; + var top = calendar.$('.fc-slats tr:eq(12) td:first').offset().top+15; try { testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); } catch (e) { calendar.destroy(); - $view.remove(); throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); } testUtils.dom.triggerPositionalMouseEvent(left, top + 200, "mousemove"); testUtils.dom.triggerPositionalMouseEvent(left, top + 200, "mouseup"); + await testUtils.nextTick(); - testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); + await testUtils.fields.editInput($('.modal-body input:first'), 'coucou'); - $('.modal-footer button.btn:contains(Edit)').trigger('click'); - $('.modal-footer button.btn:contains(Save)').trigger('click'); + await testUtils.dom.click($('.modal-footer button.btn:contains(Edit)')); + await testUtils.dom.click($('.modal-footer button.btn:contains(Save)')); assert.containsN(calendar, '.o_calendar_filter_item', 6, "should add the missing filter (active)"); assert.containsN(calendar, '.fc-event', 4, "should display the created item"); calendar.destroy(); - $view.remove(); }); - QUnit.test('Update event with filters', function (assert) { + QUnit.test('Update event with filters', async function (assert) { assert.expect(4); var records = this.data.user.records; @@ -1823,7 +1802,7 @@ QUnit.module('Views', { } }; - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -1857,16 +1836,16 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); + await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); assert.containsN(calendar, '.o_calendar_filter_item', 5, "should display 5 filter items"); assert.containsN(calendar, '.fc-event', 3, "should display 3 events"); - calendar.$('.fc-event:contains(event 2) .fc-content').trigger('click'); - $('.modal button.btn:contains(Edit)').trigger('click'); - $('.modal .o_field_widget[name="user_id"] input').trigger('click'); - $('.ui-menu-item a:contains(user 5)').trigger('mouseenter').trigger('click'); - $('.modal button.btn:contains(Save)').trigger('click'); + await testUtils.dom.click(calendar.$('.fc-event:contains(event 2) .fc-content')); + await testUtils.dom.click($('.modal button.btn:contains(Edit)')); + await testUtils.dom.click($('.modal .o_field_widget[name="user_id"] input')); + await testUtils.dom.click($('.ui-menu-item a:contains(user 5)').trigger('mouseenter')); + await testUtils.dom.click($('.modal button.btn:contains(Save)')); assert.containsN(calendar, '.o_calendar_filter_item', 6, "should add the missing filter (active)"); assert.containsN(calendar, '.fc-event', 3, "should display the updated item"); @@ -1874,7 +1853,7 @@ QUnit.module('Views', { calendar.destroy(); }); - QUnit.test('change pager with filters', function (assert) { + QUnit.test('change pager with filters', async function (assert) { assert.expect(3); this.data.user.records.push({ @@ -1914,7 +1893,7 @@ QUnit.module('Views', { type: 1 }); - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -1936,8 +1915,8 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); - testUtils.dom.click($('.o_calendar_button_prev')); + await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=4] input')); + await testUtils.dom.click($('.o_calendar_button_prev')); assert.containsN(calendar, '.o_calendar_filter_item', 6, "should display 6 filter items"); assert.containsN(calendar, '.fc-event', 2, "should display 2 events"); @@ -1947,10 +1926,10 @@ QUnit.module('Views', { calendar.destroy(); }); - QUnit.test('ensure events are still shown if filters give an empty domain', function (assert) { + QUnit.test('ensure events are still shown if filters give an empty domain', async function (assert) { assert.expect(2); - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -1964,16 +1943,16 @@ QUnit.module('Views', { assert.containsN(calendar, '.fc-event', 5, "should display 5 events"); - testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=all] input[type=checkbox]')); + await testUtils.dom.click(calendar.$('.o_calendar_filter_item[data-value=all] input[type=checkbox]')); assert.containsN(calendar, '.fc-event', 5, "should display 5 events"); calendar.destroy(); }); - QUnit.test('events starting at midnight', function (assert) { - assert.expect(2); + QUnit.test('events starting at midnight', async function (assert) { + assert.expect(3); - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -1984,19 +1963,22 @@ QUnit.module('Views', { translateParameters: { // Avoid issues due to localization formats time_format: "%H:%M:%S", }, - }); + }, {positionalClicks: true}); + + // Reset the scroll to 0 as we want to create an event from midnight + assert.ok(calendar.$('.fc-scroller')[0].scrollTop > 0, + "should scroll to 6:00 by default (this is true at least for resolutions up to 1900x1600)"); + calendar.$('.fc-scroller')[0].scrollTop = 0; // Click on Tuesday 12am - var $view = $('#qunit-fixture').contents(); - $view.prependTo('body'); var top = calendar.$('.fc-axis:contains(0:00)').offset().top + 5; var left = calendar.$('.fc-day:eq(2)').offset().left + 5; try { testUtils.dom.triggerPositionalMouseEvent(left, top, "mousedown"); testUtils.dom.triggerPositionalMouseEvent(left, top, "mouseup"); + await testUtils.nextTick(); } catch (e) { calendar.destroy(); - $view.remove(); throw new Error('The test failed to simulate a click on the screen.' + 'Your screen is probably too small or your dev tools are open.'); } @@ -2005,20 +1987,19 @@ QUnit.module('Views', { // Creating the event testUtils.fields.editInput($('.modal-body input:first'), 'new event in quick create'); - $('.modal-footer button.btn:contains(Create)').trigger('click').trigger('click'); + await testUtils.dom.click($('.modal-footer button.btn:contains(Create)')); assert.strictEqual(calendar.$('.fc-event:contains(new event in quick create)').length, 1, "should display the new record after quick create dialog"); calendar.destroy(); - $view.remove(); }); - QUnit.test('set event as all day when field is date', function (assert) { + QUnit.test('set event as all day when field is date', async function (assert) { assert.expect(2); this.data.event.records[0].start_date = "2016-12-14"; - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -2051,11 +2032,11 @@ QUnit.module('Views', { calendar.destroy(); }); - QUnit.test('quickcreate avoid double event creation', function (assert) { + QUnit.test('quickcreate avoid double event creation', async function (assert) { assert.expect(1); var createCount = 0; - var def = $.Deferred(); - var calendar = createView({ + var prom = testUtils.makeTestPromise(); + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -2077,7 +2058,7 @@ QUnit.module('Views', { var result = this._super(route, args); if (args.method === "create") { createCount++; - return def.then(_.constant(result)); + return prom.then(_.constant(result)); } return result; }, @@ -2087,22 +2068,26 @@ QUnit.module('Views', { var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); testUtils.dom.triggerMouseEvent($cell, "mousedown"); testUtils.dom.triggerMouseEvent($cell, "mouseup"); + await testUtils.nextTick(); + var $input = $('.modal input:first'); - testUtils.fields.editInput($input, 'new event in quick create'); + await testUtils.fields.editInput($input, 'new event in quick create'); // Simulate ENTER pressed on Create button (after a TAB) $input.trigger($.Event('keyup', { which: $.ui.keyCode.ENTER, keyCode: $.ui.keyCode.ENTER, })); - testUtils.dom.click($('.modal-footer button:first')); - def.resolve(); + await testUtils.nextTick(); + await testUtils.dom.click($('.modal-footer button:first')); + prom.resolve(); + await testUtils.nextTick(); assert.strictEqual(createCount, 1, "should create only one event"); calendar.destroy(); }); - QUnit.test('check if the view destroys all widgets and instances', function (assert) { + QUnit.test('check if the view destroys all widgets and instances', async function (assert) { assert.expect(1); var instanceNumber = 0; @@ -2141,13 +2126,13 @@ QUnit.module('Views', { }, }; - var calendar = createView(params); + var calendar = await createCalendarView(params); calendar.destroy(); var initialInstanceNumber = instanceNumber; instanceNumber = 0; - calendar = createView(params); + calendar = await createCalendarView(params); // call destroy function of controller to ensure that it correctly destroys everything calendar.__destroy(); @@ -2161,18 +2146,18 @@ QUnit.module('Views', { testUtils.mock.unpatch(mixins.ParentedMixin); }); - QUnit.test('create an event (async dialog) [REQUIRE FOCUS]', function (assert) { + QUnit.test('create an event (async dialog) [REQUIRE FOCUS]', async function (assert) { assert.expect(3); - var def = $.Deferred(); + var prom = testUtils.makeTestPromise(); testUtils.mock.patch(Dialog, { open: function () { var _super = this._super.bind(this); - def.then(_super); + prom.then(_super); return this; }, }); - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -2197,11 +2182,13 @@ QUnit.module('Views', { var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); testUtils.dom.triggerMouseEvent($cell, "mousedown"); testUtils.dom.triggerMouseEvent($cell, "mouseup"); + await testUtils.nextTick(); assert.strictEqual($('.modal').length, 0, "should not have opened the dialog yet"); - def.resolve(); + prom.resolve(); + await testUtils.nextTick(); assert.strictEqual($('.modal').length, 1, "should have opened the dialog"); @@ -2212,7 +2199,7 @@ QUnit.module('Views', { testUtils.mock.unpatch(Dialog); }); - QUnit.test('calendar is configured to have no groupBy menu', function (assert) { + QUnit.test('calendar is configured to have no groupBy menu', async function (assert) { assert.expect(1); var archs = { @@ -2233,22 +2220,22 @@ QUnit.module('Views', { views: [[1, 'calendar']] }]; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: actions, archs: archs, data: this.data, }); - actionManager.doAction(1); + await actionManager.doAction(1); assert.containsNone(actionManager.$('.o_control_panel .o_search_options span.fa.fa-bars'), "the control panel has no groupBy menu"); actionManager.destroy(); }); - QUnit.test('timezone does not affect current day', function (assert) { + QUnit.test('timezone does not affect current day', async function (assert) { assert.expect(2); - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -2273,17 +2260,17 @@ QUnit.module('Views', { assert.strictEqual($sidebar.find('.ui-datepicker-current-day').text(), "12", "should highlight the target day"); // go to previous day - testUtils.dom.click($sidebar.find('.ui-datepicker-current-day').prev()); + await testUtils.dom.click($sidebar.find('.ui-datepicker-current-day').prev()); assert.strictEqual($sidebar.find('.ui-datepicker-current-day').text(), "11", "should highlight the selected day"); calendar.destroy(); }); - QUnit.test('form_view_id attribute works (for creating events)', function (assert) { + QUnit.test('form_view_id attribute works (for creating events)', async function (assert) { assert.expect(1); - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -2305,7 +2292,7 @@ QUnit.module('Views', { // reject the create rpc (quick create), then the web client // fall back to a form view. This happens typically when a // model has required fields - return $.Deferred().reject('None shall pass!'); + return Promise.reject('None shall pass!'); } return this._super(route, args); }, @@ -2320,17 +2307,19 @@ QUnit.module('Views', { var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); testUtils.dom.triggerMouseEvent($cell, "mousedown"); testUtils.dom.triggerMouseEvent($cell, "mouseup"); - var $input = $('.modal-body input:first'); - testUtils.fields.editInput($input, "It's just a fleshwound"); - $('.modal button.btn:contains(Create)').trigger('click'); + await testUtils.nextTick(); + var $input = $('.modal-body input:first'); + await testUtils.fields.editInput($input, "It's just a fleshwound"); + await testUtils.dom.click($('.modal button.btn:contains(Create)')); + await testUtils.nextTick(); // wait a little before to finish the test calendar.destroy(); }); - QUnit.test('calendar fallback to form view id in action if necessary', function (assert) { + QUnit.test('calendar fallback to form view id in action if necessary', async function (assert) { assert.expect(1); - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, @@ -2352,7 +2341,7 @@ QUnit.module('Views', { // reject the create rpc (quick create), then the web client // fall back to a form view. This happens typically when a // model has required fields - return $.Deferred().reject('None shall pass!'); + return Promise.reject('None shall pass!'); } return this._super(route, args); }, @@ -2367,14 +2356,15 @@ QUnit.module('Views', { var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)'); testUtils.dom.triggerMouseEvent($cell, "mousedown"); testUtils.dom.triggerMouseEvent($cell, "mouseup"); - var $input = $('.modal-body input:first'); - testUtils.fields.editInput($input, "It's just a fleshwound"); - $('.modal button.btn:contains(Create)').trigger('click'); + await testUtils.nextTick(); + var $input = $('.modal-body input:first'); + await testUtils.fields.editInput($input, "It's just a fleshwound"); + await testUtils.dom.click($('.modal button.btn:contains(Create)')); calendar.destroy(); }); - QUnit.test('fullcalendar initializes with right locale', function (assert) { + QUnit.test('fullcalendar initializes with right locale', async function (assert) { assert.expect(1); var initialLocale = moment.locale(); @@ -2386,7 +2376,7 @@ QUnit.module('Views', { weekdaysShort: ["zz1.", "zz2.", "zz3.", "zz4.", "zz5.", "zz6.", "zz7."], }); - var calendar = createView({ + var calendar = await createCalendarView({ View: CalendarView, model: 'event', data: this.data, diff --git a/addons/web/static/tests/views/control_panel_tests.js b/addons/web/static/tests/views/control_panel_tests.js index f2654e55e1b..2e0a369ab4a 100644 --- a/addons/web/static/tests/views/control_panel_tests.js +++ b/addons/web/static/tests/views/control_panel_tests.js @@ -353,7 +353,7 @@ QUnit.module('Views', { search_disable_custom_filters: true, }, }); - testUtils.dom.click(controlPanel.$('.o_filters_menu_button')); + await testUtils.dom.click(controlPanel.$('.o_filters_menu_button')); assert.containsOnce(controlPanel, '.o_menu_item a:contains("A")'); assert.containsNone(controlPanel, '.o_menu_item a:contains("B")'); @@ -371,8 +371,8 @@ QUnit.module('Views', { data: this.data, searchMenuTypes: ['filter'], }); - testUtils.dom.click(controlPanel.$('.o_filters_menu_button')); - testUtils.dom.click($('.o_menu_item a')); + await testUtils.dom.click(controlPanel.$('.o_filters_menu_button')); + await testUtils.dom.click($('.o_menu_item a')); assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').text().trim(), 'A', 'should have a facet with A'); @@ -381,14 +381,16 @@ QUnit.module('Views', { which: $.ui.keyCode.BACKSPACE, keyCode: $.ui.keyCode.BACKSPACE, })); + await testUtils.nextTick(); assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').length, 0, - 'there should be no facet'); + 'there should be no facet'); // delete nothing (should not crash) controlPanel.$('input.o_searchview_input').trigger($.Event('keydown', { which: $.ui.keyCode.BACKSPACE, keyCode: $.ui.keyCode.BACKSPACE, })); + await testUtils.nextTick(); controlPanel.destroy(); }); @@ -409,7 +411,7 @@ QUnit.module('Views', { search_disable_custom_filters: true, }, }); - testUtils.dom.click(controlPanel.$('.o_filters_menu_button')); + await testUtils.dom.click(controlPanel.$('.o_filters_menu_button')); assert.containsOnce(controlPanel, '.o_menu_item a:contains("A")'); assert.containsNone(controlPanel, '.o_menu_item a:contains("B")'); diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index b1df6260359..96d36d8eec9 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -16,7 +16,6 @@ var Widget = require('web.Widget'); var _t = core._t; var createView = testUtils.createView; -var createAsyncView = testUtils.createAsyncView; var createActionManager = testUtils.createActionManager; QUnit.module('Views', { @@ -105,7 +104,6 @@ QUnit.module('Views', { ] }, }; - this.actions = [{ id: 1, name: 'Partners Action 1', @@ -113,15 +111,15 @@ QUnit.module('Views', { type: 'ir.actions.act_window', views: [[false, 'kanban'], [false, 'form']], }]; - } + }, }, function () { QUnit.module('FormView'); - QUnit.test('simple form rendering', function (assert) { + QUnit.test('simple form rendering', async function (assert) { assert.expect(12); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -174,10 +172,11 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('attributes are transferred on async widgets', function (assert) { + QUnit.test('attributes are transferred on async widgets', async function (assert) { assert.expect(1); + var done = assert.async(); - var def = $.Deferred(); + var def = testUtils.makeTestPromise(); var FieldChar = fieldRegistry.get('char'); fieldRegistry.add('asyncwidget', FieldChar.extend({ @@ -186,7 +185,7 @@ QUnit.module('Views', { }, })); - createAsyncView({ + createView({ View: FormView, model: 'partner', data: this.data, @@ -201,14 +200,16 @@ QUnit.module('Views', { "should apply style attribute on fields"); form.destroy(); delete fieldRegistry.map.asyncwidget; + done(); }); def.resolve(); + await testUtils.nextTick(); }); - QUnit.test('decoration works on widgets', function (assert) { + QUnit.test('decoration works on widgets', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -224,10 +225,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('decoration on widgets are reevaluated if necessary', function (assert) { + QUnit.test('decoration on widgets are reevaluated if necessary', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -239,15 +240,15 @@ QUnit.module('Views', { viewOptions: {mode: 'edit'}, }); assert.doesNotHaveClass(form.$('input[name="display_name"]'), 'text-danger'); - testUtils.fields.editInput(form.$('input[name=int_field]'), 3); + await testUtils.fields.editInput(form.$('input[name=int_field]'), 3); assert.hasClass(form.$('input[name="display_name"]'), 'text-danger'); form.destroy(); }); - QUnit.test('decoration on widgets works on same widget', function (assert) { + QUnit.test('decoration on widgets works on same widget', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -258,15 +259,15 @@ QUnit.module('Views', { viewOptions: {mode: 'edit'}, }); assert.doesNotHaveClass(form.$('input[name="int_field"]'), 'text-danger'); - testUtils.fields.editInput(form.$('input[name=int_field]'), 3); + await testUtils.fields.editInput(form.$('input[name=int_field]'), 3); assert.hasClass(form.$('input[name="int_field"]'), 'text-danger'); form.destroy(); }); - QUnit.test('only necessary fields are fetched with correct context', function (assert) { + QUnit.test('only necessary fields are fetched with correct context', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -287,10 +288,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('group rendering', function (assert) { + QUnit.test('group rendering', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -308,14 +309,14 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('Form and subview with _view_ref contexts', function (assert) { + QUnit.test('Form and subview with _view_ref contexts', async function (assert) { assert.expect(2); this.data.product.fields.partner_type_ids = {string: "one2many field", type: "one2many", relation: "partner_type"}, this.data.product.records = [{id: 1, name: 'Tromblon', partner_type_ids: [12,14]}]; this.data.partner.records[0].product_id = 1; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ data: this.data, archs: { 'product,false,form': '<form>'+ @@ -344,7 +345,7 @@ QUnit.module('Views', { }, }); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -356,7 +357,7 @@ QUnit.module('Views', { mockRPC: function(route, args) { if (args.method === 'get_formview_action') { - return $.when({ + return Promise.resolve({ res_id: 1, type: 'ir.actions.act_window', target: 'current', @@ -376,14 +377,14 @@ QUnit.module('Views', { }, }, }); - form.$('.o_field_widget[name="product_id"]').click(); + await testUtils.dom.click(form.$('.o_field_widget[name="product_id"]')); form.destroy(); actionManager.destroy(); }); - QUnit.test('invisible fields are properly hidden', function (assert) { + QUnit.test('invisible fields are properly hidden', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -410,10 +411,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('invisible elements are properly hidden', function (assert) { + QUnit.test('invisible elements are properly hidden', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -446,7 +447,8 @@ QUnit.module('Views', { // we set the value bar to simulate a falsy boolean value. this.data.partner.records[0].bar = false; - var form = createView({ + + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -459,9 +461,11 @@ QUnit.module('Views', { '</group></sheet>' + '</form>', res_id: 1, + viewOptions: { + mode:'edit' + }, }); - testUtils.form.clickEdit(form); assert.hasClass(form.$('.foo_field'), 'o_invisible_modifier'); assert.hasClass(form.$('.bar_field'), 'o_invisible_modifier'); @@ -472,10 +476,11 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('asynchronous fields can be set invisible', function (assert) { + QUnit.test('asynchronous fields can be set invisible', async function (assert) { assert.expect(1); + var done = assert.async(); - var def = $.Deferred(); + var def = testUtils.makeTestPromise(); // we choose this widget because it is a quite simple widget with a non // empty qweb template @@ -486,7 +491,7 @@ QUnit.module('Views', { }, })); - createAsyncView({ + createView({ View: FormView, model: 'partner', data: this.data, @@ -501,15 +506,15 @@ QUnit.module('Views', { assert.hasClass(form.$('.o_field_widget[name="int_field"]'), 'o_invisible_modifier'); form.destroy(); delete fieldRegistry.map.asyncwidget; + done(); }); def.resolve(); }); - - QUnit.test('properly handle modifiers and attributes on notebook tags', function (assert) { + QUnit.test('properly handle modifiers and attributes on notebook tags', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -531,10 +536,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('invisible attrs on first notebook page', function (assert) { + QUnit.test('invisible attrs on first notebook page', async function (assert) { assert.expect(6); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -554,12 +559,12 @@ QUnit.module('Views', { res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.hasClass(form.$('.o_notebook .nav .nav-link:first()'), 'active'); assert.doesNotHaveClass(form.$('.o_notebook .nav .nav-item:first()'), 'o_invisible_modifier'); // set a value on the m2o - testUtils.fields.many2one.searchAndClickItem('product_id'); + await testUtils.fields.many2one.searchAndClickItem('product_id'); assert.doesNotHaveClass(form.$('.o_notebook .nav .nav-link:first()'), 'active'); assert.hasClass(form.$('.o_notebook .nav .nav-item:first()'), 'o_invisible_modifier'); assert.hasClass(form.$('.o_notebook .nav .nav-link:nth(1)'), 'active'); @@ -567,10 +572,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('first notebook page invisible', function (assert) { + QUnit.test('first notebook page invisible', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -597,10 +602,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('autofocus on second notebook page', function (assert) { + QUnit.test('autofocus on second notebook page', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -626,10 +631,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('invisible attrs on group are re-evaluated on field change', function (assert) { + QUnit.test('invisible attrs on group are re-evaluated on field change', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -643,22 +648,24 @@ QUnit.module('Views', { '</group>' + '</sheet>' + '</form>', - res_id: 1 + res_id: 1, + viewOptions: { + mode: 'edit' + }, }); assert.containsOnce(form, 'div.o_group:visible'); - testUtils.form.clickEdit(form); - testUtils.dom.click('.o_field_boolean input', form); + await testUtils.dom.click('.o_field_boolean input', form); assert.containsOnce(form, 'div.o_group:hidden'); form.destroy(); }); - QUnit.test('invisible attrs with zero value in domain and unset value in data', function (assert) { + QUnit.test('invisible attrs with zero value in domain and unset value in data', async function (assert) { assert.expect(1); this.data.partner.fields.int_field.type = 'monetary'; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -677,10 +684,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('rendering stat buttons', function (assert) { + QUnit.test('rendering stat buttons', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -706,18 +713,18 @@ QUnit.module('Views', { assert.containsOnce(form, 'button.oe_stat_button.o_invisible_modifier'); var count = 0; - testUtils.mock.intercept(form, "execute_action", function () { + await testUtils.mock.intercept(form, "execute_action", function () { count++; }); - testUtils.dom.click('.oe_stat_button'); + await testUtils.dom.click('.oe_stat_button'); assert.strictEqual(count, 1, "should have triggered a execute action"); form.destroy(); }); - QUnit.test('label uses the string attribute', function (assert) { + QUnit.test('label uses the string attribute', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -736,10 +743,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('readonly attrs on fields are re-evaluated on field change', function (assert) { + QUnit.test('readonly attrs on fields are re-evaluated on field change', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -753,24 +760,24 @@ QUnit.module('Views', { '</form>', res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, 'span[name="foo"]', "the foo field widget should be readonly"); - testUtils.dom.click(form.$('.o_field_boolean input')); + await testUtils.dom.click(form.$('.o_field_boolean input')); assert.containsOnce(form, 'input[name="foo"]', "the foo field widget should have been rerendered to now be editable"); - testUtils.dom.click(form.$('.o_field_boolean input')); + await testUtils.dom.click(form.$('.o_field_boolean input')); assert.containsOnce(form, 'span[name="foo"]', "the foo field widget should have been rerendered to now be readonly again"); - testUtils.dom.click(form.$('.o_field_boolean input')); + await testUtils.dom.click(form.$('.o_field_boolean input')); assert.containsOnce(form, 'input[name="foo"]', "the foo field widget should have been rerendered to now be editable again"); form.destroy(); }); - QUnit.test('empty fields have o_form_empty class in readonly mode', function (assert) { + QUnit.test('empty fields have o_form_empty class in readonly mode', async function (assert) { assert.expect(8); this.data.partner.fields.foo.default = false; // no default value for this test @@ -783,7 +790,7 @@ QUnit.module('Views', { } }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -804,21 +811,21 @@ QUnit.module('Views', { assert.containsN(form, '.o_form_label_empty', 2, "should have 2 muted labels (for the empty fieds) in readonly"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, '.o_field_empty', "in edit mode, only empty readonly fields should have the o_field_empty class"); assert.containsOnce(form, '.o_form_label_empty', "in edit mode, only labels associated to empty readonly fields should have the o_form_label_empty class"); - testUtils.fields.editInput(form.$('input[name=foo]'), 'test'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'test'); assert.containsNone(form, '.o_field_empty', "after readonly modifier change, the o_field_empty class should have been removed"); assert.containsNone(form, '.o_form_label_empty', "after readonly modifier change, the o_form_label_empty class should have been removed"); - testUtils.fields.editInput(form.$('input[name=foo]'), 'hello'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'hello'); assert.containsOnce(form, '.o_field_empty', "after value changed to false for a readonly field, the o_field_empty class should have been added"); @@ -828,14 +835,14 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('empty fields\' labels still get the empty class after widget rerender', function (assert) { + QUnit.test('empty fields\' labels still get the empty class after widget rerender', async function (assert) { assert.expect(6); this.data.partner.fields.foo.default = false; // no default value for this test this.data.partner.records[1].foo = false; // 1 is record with id=2 this.data.partner.records[1].display_name = false; // 1 is record with id=2 - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -848,21 +855,21 @@ QUnit.module('Views', { res_id: 2, }); - assert.containsN(form, '.o_field_widget.o_field_empty', 2) + assert.containsN(form, '.o_field_widget.o_field_empty', 2); assert.containsN(form, '.o_form_label_empty', 2, "should have 1 muted label (for the empty fied) in readonly"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsNone(form, '.o_field_empty', "in edit mode, only empty readonly fields should have the o_field_empty class"); assert.containsNone(form, '.o_form_label_empty', "in edit mode, only labels associated to empty readonly fields should have the o_form_label_empty class"); - testUtils.fields.editInput(form.$('input[name=foo]'), 'readonly'); - testUtils.fields.editInput(form.$('input[name=foo]'), 'edit'); - testUtils.fields.editInput(form.$('input[name=display_name]'), 'some name'); - testUtils.fields.editInput(form.$('input[name=foo]'), 'readonly'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'readonly'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'edit'); + await testUtils.fields.editInput(form.$('input[name=display_name]'), 'some name'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'readonly'); assert.containsNone(form, '.o_field_empty', "there still should not be any empty class on fields as the readonly one is now set"); @@ -872,11 +879,11 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('empty inner readonly fields don\'t have o_form_empty class in "create" mode', function (assert) { + QUnit.test('empty inner readonly fields don\'t have o_form_empty class in "create" mode', async function (assert) { assert.expect(2); this.data.partner.fields.product_id.readonly = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -897,10 +904,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('form view can switch to edit mode', function (assert) { + QUnit.test('form view can switch to edit mode', async function (assert) { assert.expect(9); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -915,7 +922,7 @@ QUnit.module('Views', { assert.isVisible(form.$buttons.find('.o_form_buttons_view')); assert.isNotVisible(form.$buttons.find('.o_form_buttons_edit')); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.mode, 'edit', 'form view should be in edit mode'); assert.hasClass(form.$('.o_form_view'), 'o_form_editable'); @@ -925,10 +932,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('required attrs on fields are re-evaluated on field change', function (assert) { + QUnit.test('required attrs on fields are re-evaluated on field change', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -942,25 +949,25 @@ QUnit.module('Views', { '</form>', res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, 'input[name="foo"].o_required_modifier', "the foo field widget should be required"); - testUtils.dom.click('.o_field_boolean input'); + await testUtils.dom.click('.o_field_boolean input'); assert.containsOnce(form, 'input[name="foo"]:not(.o_required_modifier)', "the foo field widget should now have been marked as non-required"); - testUtils.dom.click('.o_field_boolean input'); + await testUtils.dom.click('.o_field_boolean input'); assert.containsOnce(form, 'input[name="foo"].o_required_modifier', "the foo field widget should now have been marked as required again"); form.destroy(); }); - QUnit.test('required fields should have o_required_modifier in readonly mode', function (assert) { + QUnit.test('required fields should have o_required_modifier in readonly mode', async function (assert) { assert.expect(2); this.data.partner.fields.foo.required = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -976,17 +983,17 @@ QUnit.module('Views', { assert.containsOnce(form, 'span.o_required_modifier', form); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, 'input.o_required_modifier', "in edit mode, should have 1 input with o_required_modifier"); form.destroy(); }); - QUnit.test('required float fields works as expected', function (assert) { + QUnit.test('required float fields works as expected', async function (assert) { assert.expect(10); this.data.partner.fields.qux.required = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1007,29 +1014,29 @@ QUnit.module('Views', { assert.strictEqual(form.$('input[name="qux"]').val(), "0.0", "qux input is 0 by default (float field)"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.containsNone(form.$('input[name="qux"]'), "should have switched to readonly"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); - testUtils.fields.editInput(form.$('input[name=qux]'), '1'); + await testUtils.fields.editInput(form.$('input[name=qux]'), '1'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); - assert.strictEqual(form.$('input[name="qux"]').val(), "1.0", + assert.strictEqual(form.$('input[name="qux"]').val(), "1.0", "qux input is properly formatted"); assert.verifySteps(['default_get', 'create', 'read', 'write', 'read']); form.destroy(); }); - QUnit.test('separators', function (assert) { + QUnit.test('separators', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1048,10 +1055,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('invisible attrs on separators', function (assert) { + QUnit.test('invisible attrs on separators', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1071,12 +1078,12 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('buttons in form view', function (assert) { + QUnit.test('buttons in form view', async function (assert) { assert.expect(8); var rpcCount = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1104,7 +1111,7 @@ QUnit.module('Views', { assert.containsOnce(form, 'button.p[name="post"]:contains(Confirm)'); assert.containsN(form, '.o_form_statusbar button:visible', 2); - testUtils.mock.intercept(form, 'execute_action', function (ev) { + await testUtils.mock.intercept(form, 'execute_action', function (ev) { assert.strictEqual(ev.data.action_data.name, 'post', "should trigger execute_action with correct method name"); assert.deepEqual(ev.data.env.currentID, 2, "should have correct id in ev data"); @@ -1112,14 +1119,14 @@ QUnit.module('Views', { ev.data.on_closed(); }); rpcCount = 0; - testUtils.dom.click('.o_form_statusbar button.p', form); + await testUtils.dom.click('.o_form_statusbar button.p', form); assert.strictEqual(rpcCount, 1, "should have done 1 rpcs to reload"); - testUtils.mock.intercept(form, 'execute_action', function (ev) { + await testUtils.mock.intercept(form, 'execute_action', function (ev) { ev.data.on_fail(); }); - testUtils.dom.click('.o_form_statusbar button.s', form); + await testUtils.dom.click('.o_form_statusbar button.s', form); assert.strictEqual(rpcCount, 1, "should have done 1 rpc, because we do not reload anymore if the server action fails"); @@ -1127,10 +1134,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('buttons classes in form view', function (assert) { + QUnit.test('buttons classes in form view', async function (assert) { assert.expect(16); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1180,13 +1187,13 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('buttons in form view, new record', function (assert) { + QUnit.test('buttons in form view, new record', async function (assert) { // this simulates a situation similar to the settings forms. assert.expect(7); var resID; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1213,20 +1220,20 @@ QUnit.module('Views', { }, }); - testUtils.mock.intercept(form, 'execute_action', function (event) { + await testUtils.mock.intercept(form, 'execute_action', function (event) { assert.step('execute_action'); assert.deepEqual(event.data.env.currentID, resID, "execute action should be done on correct record id"); event.data.on_success(); event.data.on_closed(); }); - testUtils.dom.click('.o_form_statusbar button.p', form); + await testUtils.dom.click('.o_form_statusbar button.p', form); assert.verifySteps(['default_get', 'create', 'read', 'execute_action', 'read']); form.destroy(); }); - QUnit.test('buttons in form view, new record, with field id in view', function (assert) { + QUnit.test('buttons in form view, new record, with field id in view', async function (assert) { assert.expect(7); // buttons in form view are one of the rare example of situation when we // save a record without reloading it immediately, because we only care @@ -1236,7 +1243,7 @@ QUnit.module('Views', { var resID; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1263,22 +1270,22 @@ QUnit.module('Views', { }, }); - testUtils.mock.intercept(form, 'execute_action', function (event) { + await testUtils.mock.intercept(form, 'execute_action', function (event) { assert.step('execute_action'); assert.deepEqual(event.data.env.currentID, resID, "execute action should be done on correct record id"); event.data.on_success(); event.data.on_closed(); }); - testUtils.dom.click('.o_form_statusbar button.p', form); + await testUtils.dom.click('.o_form_statusbar button.p', form); assert.verifySteps(['default_get', 'create', 'read', 'execute_action', 'read']); form.destroy(); }); - QUnit.test('change and save char', function (assert) { + QUnit.test('change and save char', async function (assert) { assert.expect(6); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1298,11 +1305,11 @@ QUnit.module('Views', { assert.containsOnce(form, 'span:contains(blip)', "should contain span with field value"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.mode, 'edit', 'form view should be in edit mode'); - testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); - testUtils.form.clickSave(form); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + await testUtils.form.clickSave(form); assert.strictEqual(form.mode, 'readonly', 'form view should be in readonly mode'); assert.containsOnce(form, 'span:contains(tralala)', @@ -1310,10 +1317,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('properly reload data from server', function (assert) { + QUnit.test('properly reload data from server', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1329,18 +1336,18 @@ QUnit.module('Views', { res_id: 2, }); - testUtils.form.clickEdit(form); - testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); - testUtils.form.clickSave(form); + await testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + await testUtils.form.clickSave(form); assert.containsOnce(form, 'span:contains(apple)', "should contain span with field value"); form.destroy(); }); - QUnit.test('disable buttons until reload data from server', function (assert) { + QUnit.test('disable buttons until reload data from server', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1353,22 +1360,23 @@ QUnit.module('Views', { } else if (args.method === 'read') { // Block the 'read' call var result = this._super.apply(this, arguments); - return $.when(def).then(_.constant(result)); + return Promise.resolve(def).then(result); } return this._super(route, args); }, res_id: 2, }); - var def = $.Deferred(); - testUtils.form.clickEdit(form); - testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); - testUtils.form.clickSave(form); + var def = testUtils.makeTestPromise(); + await testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + await testUtils.form.clickSave(form); // Save button should be disabled assert.hasAttrValue(form.$buttons.find('.o_form_button_save'), 'disabled', 'disabled'); // Release the 'read' call - def.resolve(); + await def.resolve(); + await testUtils.nextTick(); // Edit button should be enabled after the reload assert.hasAttrValue(form.$buttons.find('.o_form_button_edit'), 'disabled', undefined); @@ -1376,7 +1384,7 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('properly apply onchange in simple case', function (assert) { + QUnit.test('properly apply onchange in simple case', async function (assert) { assert.expect(2); this.data.partner.onchanges = { @@ -1384,7 +1392,7 @@ QUnit.module('Views', { obj.int_field = obj.foo.length + 1000; }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1394,19 +1402,19 @@ QUnit.module('Views', { res_id: 2, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('input[name=int_field]').val(), "9", "should contain input with initial value"); - testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); assert.strictEqual(form.$('input[name=int_field]').val(), "1007", "should contain input with onchange applied"); form.destroy(); }); - QUnit.test('properly apply onchange when changed field is active field', function (assert) { + QUnit.test('properly apply onchange when changed field is active field', async function (assert) { assert.expect(3); this.data.partner.onchanges = { @@ -1414,7 +1422,7 @@ QUnit.module('Views', { obj.int_field = 14; }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1429,27 +1437,27 @@ QUnit.module('Views', { assert.strictEqual(form.$('input[name=int_field]').val(), "9", "should contain input with initial value"); - testUtils.fields.editInput(form.$('input[name=int_field]'), '666'); + await testUtils.fields.editInput(form.$('input[name=int_field]'), '666'); assert.strictEqual(form.$('input[name=int_field]').val(), "14", "value should have been set to 14 by onchange"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); - assert.strictEqual(form.$('.o_field_widget[name=int_field]').text(), "14", + assert.strictEqual(form.$('.o_field_widget[name=int_field]').text(), "14", "value should still be 14"); form.destroy(); }); - QUnit.test('onchange send only the present fields to the server', function (assert) { + QUnit.test('onchange send only the present fields to the server', async function (assert) { assert.expect(1); this.data.partner.records[0].product_id = false; this.data.partner.onchanges.foo = function (obj) { obj.foo = obj.foo + " alligator"; }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1477,17 +1485,17 @@ QUnit.module('Views', { }, }); - testUtils.form.clickEdit(form); - testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + await testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); form.destroy(); }); - QUnit.test('onchange only send present fields value', function (assert) { + QUnit.test('onchange only send present fields value', async function (assert) { assert.expect(1); this.data.partner.onchanges.foo = function (obj) {}; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1515,22 +1523,24 @@ QUnit.module('Views', { }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // add a o2m row - testUtils.dom.click('.o_field_x2many_list_row_add a'); + await testUtils.dom.click('.o_field_x2many_list_row_add a'); form.$('.o_field_one2many input:first').focus(); - testUtils.fields.editInput(form.$('.o_field_one2many input[name=display_name]'), 'valid line'); + await testUtils.nextTick(); + await testUtils.fields.editInput(form.$('.o_field_one2many input[name=display_name]'), 'valid line'); form.$('.o_field_one2many input:last').focus(); - testUtils.fields.editInput(form.$('.o_field_one2many input[name=qux]'), '12.4'); + await testUtils.nextTick(); + await testUtils.fields.editInput(form.$('.o_field_one2many input[name=qux]'), '12.4'); // trigger an onchange by modifying foo - testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); form.destroy(); }); - QUnit.test('evaluate in python field options', function (assert) { + QUnit.test('evaluate in python field options', async function (assert) { assert.expect(1); var isOk = false; @@ -1541,7 +1551,7 @@ QUnit.module('Views', { } return tmp.apply(tmp, arguments); }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1557,10 +1567,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('can create a record with default values', function (assert) { + QUnit.test('can create a record with default values', async function (assert) { assert.expect(5); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1586,23 +1596,23 @@ QUnit.module('Views', { }); var n = this.data.partner.records.length; - testUtils.form.clickCreate(form); + await testUtils.form.clickCreate(form); assert.strictEqual(form.mode, 'edit', 'form view should be in edit mode'); - assert.strictEqual(form.$('input[name=foo]').val(), "My little Foo Value", + assert.strictEqual(form.$('input:first').val(), "My little Foo Value", "should have correct default_get value"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.mode, 'readonly', 'form view should be in readonly mode'); assert.strictEqual(this.data.partner.records.length, n + 1, "should have created a record"); form.destroy(); }); - QUnit.test('default record with a one2many and an onchange on sub field', function (assert) { + QUnit.test('default record with a one2many and an onchange on sub field', async function (assert) { assert.expect(4); this.data.partner.onchanges.foo = function () {}; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1628,10 +1638,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('sidebar is hidden when switching to edit mode', function (assert) { + QUnit.test('sidebar is hidden when switching to edit mode', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1645,21 +1655,21 @@ QUnit.module('Views', { }); assert.isVisible(form.sidebar); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.isNotVisible(form.sidebar); - testUtils.form.clickDiscard(form); + await testUtils.form.clickDiscard(form); assert.isVisible(form.sidebar); form.destroy(); }); - QUnit.test('basic default record', function (assert) { + QUnit.test('basic default record', async function (assert) { assert.expect(2); this.data.partner.fields.foo.default = "default foo value"; var count = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1677,7 +1687,7 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('make default record with non empty one2many', function (assert) { + QUnit.test('make default record with non empty one2many', async function (assert) { assert.expect(4); this.data.partner.fields.p.default = [ @@ -1688,7 +1698,7 @@ QUnit.module('Views', { var nameGetCount = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1717,7 +1727,7 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('make default record with non empty many2one', function (assert) { + QUnit.test('make default record with non empty many2one', async function (assert) { var done = assert.async(); assert.expect(2); @@ -1725,7 +1735,7 @@ QUnit.module('Views', { var nameGetCount = 0; - createAsyncView({ + createView({ View: FormView, model: 'partner', data: this.data, @@ -1749,10 +1759,10 @@ QUnit.module('Views', { }); }); - QUnit.test('form view properly change its title', function (assert) { + QUnit.test('form view properly change its title', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1765,17 +1775,17 @@ QUnit.module('Views', { assert.strictEqual(form.$('.o_control_panel .breadcrumb').text(), 'first record', "should have the display name of the record as title"); - testUtils.form.clickCreate(form); + await testUtils.form.clickCreate(form); assert.strictEqual(form.$('.o_control_panel .breadcrumb').text(), _t("New"), "should have the display name of the record as title"); form.destroy(); }); - QUnit.test('can duplicate a record', function (assert) { + QUnit.test('can duplicate a record', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1789,8 +1799,8 @@ QUnit.module('Views', { assert.strictEqual(form.$('.o_control_panel .breadcrumb').text(), 'first record', "should have the display name of the record as title"); - testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); - testUtils.dom.click(form.sidebar.$('a:contains(Duplicate)')); + await testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + await testUtils.dom.click(form.sidebar.$('a:contains(Duplicate)')); assert.strictEqual(form.$('.o_control_panel .breadcrumb').text(), 'first record (copy)', "should have duplicated the record"); @@ -1799,10 +1809,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('duplicating a record preserve the context', function (assert) { + QUnit.test('duplicating a record preserve the context', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1822,16 +1832,16 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); - testUtils.dom.click(form.sidebar.$('a:contains(Duplicate)')); + await testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + await testUtils.dom.click(form.sidebar.$('a:contains(Duplicate)')); form.destroy(); }); - QUnit.test('cannot duplicate a record', function (assert) { + QUnit.test('cannot duplicate a record', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1849,10 +1859,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('clicking on stat buttons in edit mode', function (assert) { + QUnit.test('clicking on stat buttons in edit mode', async function (assert) { assert.expect(9); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1878,30 +1888,30 @@ QUnit.module('Views', { }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); var count = 0; - testUtils.mock.intercept(form, "execute_action", function (event) { + await testUtils.mock.intercept(form, "execute_action", function (event) { event.stopPropagation(); count++; }); - testUtils.dom.click('.oe_stat_button'); + await testUtils.dom.click('.oe_stat_button'); assert.strictEqual(count, 1, "should have triggered a execute action"); assert.strictEqual(form.mode, "edit", "form view should be in edit mode"); - testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); - testUtils.dom.click('.oe_stat_button:first'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + await testUtils.dom.click('.oe_stat_button:first'); - assert.strictEqual(form.mode, "edit", "form view should be in edit mode"); + assert.strictEqual(form.mode, "edit", "form view should be in edit mode"); assert.strictEqual(count, 2, "should have triggered a execute action"); assert.verifySteps(['read', 'write', 'read']); form.destroy(); }); - QUnit.test('clicking on stat buttons save and reload in edit mode', function (assert) { + QUnit.test('clicking on stat buttons save and reload in edit mode', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1930,23 +1940,23 @@ QUnit.module('Views', { assert.strictEqual(form.$('.o_control_panel .breadcrumb').text(), 'second record', "should have correct display_name"); - testUtils.form.clickEdit(form); - testUtils.fields.editInput(form.$('input[name=name]'), 'some other name'); + await testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name=name]'), 'some other name'); - testUtils.dom.click('.oe_stat_button'); + await testUtils.dom.click('.oe_stat_button'); assert.strictEqual(form.$('.o_control_panel .breadcrumb').text(), 'GOLDORAK', "should have correct display_name"); form.destroy(); }); - QUnit.test('buttons with attr "special" do not trigger a save', function (assert) { + QUnit.test('buttons with attr "special" do not trigger a save', async function (assert) { assert.expect(4); var executeActionCount = 0; var writeCount = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1961,34 +1971,35 @@ QUnit.module('Views', { writeCount++; } return this._super(route, args); - } + }, }); - testUtils.mock.intercept(form, "execute_action", function () { + await testUtils.mock.intercept(form, "execute_action", function () { executeActionCount++; }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // make the record dirty - testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); - testUtils.dom.click(form.$('button:contains(Do something)')); + await testUtils.dom.click(form.$('button:contains(Do something)')); + //TODO: VSC: add a next tick ? assert.strictEqual(writeCount, 1, "should have triggered a write"); assert.strictEqual(executeActionCount, 1, "should have triggered a execute action"); - testUtils.fields.editInput(form.$('input[name=foo]'), 'abcdef'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'abcdef'); - testUtils.dom.click(form.$('button:contains(Or discard)')); + await testUtils.dom.click(form.$('button:contains(Or discard)')); assert.strictEqual(writeCount, 1, "should not have triggered a write"); assert.strictEqual(executeActionCount, 2, "should have triggered a execute action"); form.destroy(); }); - QUnit.test('buttons with attr "special=save" save', function (assert) { + QUnit.test('buttons with attr "special=save" save', async function (assert) { assert.expect(5); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2011,18 +2022,18 @@ QUnit.module('Views', { }, }); - testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); - testUtils.dom.click(form.$('.o_form_view button')); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + await testUtils.dom.click(form.$('button[special="save"]')); assert.verifySteps(['read', 'write', 'read', 'execute_action']); form.destroy(); }); - QUnit.test('missing widgets do not crash', function (assert) { + QUnit.test('missing widgets do not crash', async function (assert) { assert.expect(1); this.data.partner.fields.foo.type = 'new field type without widget'; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2035,10 +2046,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('nolabel', function (assert) { + QUnit.test('nolabel', async function (assert) { assert.expect(6); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2073,13 +2084,13 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('many2one in a one2many', function (assert) { + QUnit.test('many2one in a one2many', async function (assert) { assert.expect(1); this.data.partner.records[0].p = [2]; this.data.partner.records[1].product_id = 37; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2096,13 +2107,13 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('circular many2many\'s', function (assert) { + QUnit.test('circular many2many\'s', async function (assert) { assert.expect(4); - this.data.partner_type.fields.partner_ids = {string: "partners", type: "many2many", relation: 'partner'} + this.data.partner_type.fields.partner_ids = {string: "partners", type: "many2many", relation: 'partner'}; this.data.partner.records[0].timmy = [12]; this.data.partner_type.records[0].partner_ids = [1]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2128,24 +2139,24 @@ QUnit.module('Views', { assert.containsOnce(form, 'td:contains(gold)', "should display the name of the many2many on the original form"); - testUtils.dom.click(form.$('td:contains(gold)')); + await testUtils.dom.click(form.$('td:contains(gold)')); assert.containsOnce(document.body, '.modal'); assert.containsOnce($('.modal'), 'td:contains(first record)', "should display the name of the many2many on the modal form"); - testUtils.dom.click('.modal td:contains(first record)'); + await testUtils.dom.click('.modal td:contains(first record)'); assert.containsN(document.body, '.modal', 2, "there should be 2 modals (partner on top of partner_type) opened"); form.destroy(); }); - QUnit.test('discard changes on a non dirty form view', function (assert) { + QUnit.test('discard changes on a non dirty form view', async function (assert) { assert.expect(4); var nbWrite = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2160,25 +2171,24 @@ QUnit.module('Views', { }); // switch to edit mode - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('input[name=foo]').val(), 'yop', "input should contain yop"); // click on discard - testUtils.form.clickDiscard(form); - assert.containsNone(document.body, '.modal', "no confirm modal should be displayed"); - assert.strictEqual(form.$('.o_field_widget').text(), 'yop', - "field in readonly should display yop"); + await testUtils.form.clickDiscard(form); + assert.containsNone(document.body, '.modal', 'no confirm modal should be displayed'); + assert.strictEqual(form.$('.o_field_widget').text(), 'yop', 'field in readonly should display yop'); assert.strictEqual(nbWrite, 0, "no write RPC should have been done"); form.destroy(); }); - QUnit.test('discard changes on a dirty form view', function (assert) { + QUnit.test('discard changes on a dirty form view', async function (assert) { assert.expect(7); var nbWrite = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2193,31 +2203,29 @@ QUnit.module('Views', { }); // switch to edit mode and edit the foo field - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('input[name=foo]').val(), 'yop', "input should contain yop"); - testUtils.fields.editInput(form.$('input[name=foo]'), 'new value'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'new value'); assert.strictEqual(form.$('input[name=foo]').val(), 'new value', "input should contain new value"); // click on discard and cancel the confirm request - testUtils.form.clickDiscard(form); + await testUtils.form.clickDiscard(form); assert.containsOnce(document.body, '.modal', "a confirm modal should be displayed"); - testUtils.dom.click('.modal-footer .btn-secondary'); - assert.strictEqual(form.$('input[name=foo]').val(), 'new value', - "input should still contain new value"); + await testUtils.dom.click('.modal-footer .btn-secondary'); + assert.strictEqual(form.$('input').val(), 'new value', 'input should still contain new value'); // click on discard and confirm - testUtils.form.clickDiscard(form); + await testUtils.form.clickDiscard(form); assert.containsOnce(document.body, '.modal', "a confirm modal should be displayed"); - testUtils.dom.click('.modal-footer .btn-primary'); - assert.strictEqual(form.$('.o_field_widget').text(), 'yop', - "field in readonly should display yop"); + await testUtils.dom.click('.modal-footer .btn-primary'); + assert.strictEqual(form.$('.o_field_widget').text(), 'yop', 'field in readonly should display yop'); assert.strictEqual(nbWrite, 0, "no write RPC should have been done"); form.destroy(); }); - QUnit.test('discard changes on a dirty form view (for date field)', function (assert) { + QUnit.test('discard changes on a dirty form view (for date field)', async function (assert) { assert.expect(1); // this test checks that the basic model properly handles date object @@ -2226,7 +2234,7 @@ QUnit.module('Views', { // then parsed into string, which is wrong. this.data.partner.fields.date.default = "2017-01-25"; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2243,18 +2251,18 @@ QUnit.module('Views', { // event to properly focusout the input, otherwise it crashes later on // when the 'blur' event is triggered by the re-rendering) form.$buttons.find('.o_form_button_cancel').focus(); - testUtils.dom.click('.o_form_button_cancel'); + await testUtils.dom.click('.o_form_button_cancel'); form.$buttons.find('.o_form_button_save').focus(); - testUtils.dom.click('.o_form_button_save'); + await testUtils.dom.click('.o_form_button_save'); assert.containsOnce(form, 'span:contains(2017)'); form.destroy(); }); - QUnit.test('discard changes on relational data on new record', function (assert) { + QUnit.test('discard changes on relational data on new record', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2278,28 +2286,28 @@ QUnit.module('Views', { }); // edit the p field - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.many2one.clickOpenDropdown('product_id'); - testUtils.fields.many2one.clickHighlightedItem('product_id'); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.many2one.clickOpenDropdown('product_id'); + await testUtils.fields.many2one.clickHighlightedItem('product_id'); assert.strictEqual(form.$('.o_field_widget[name=product_id] input').val(), 'xphone', "input should contain xphone"); // click on discard and confirm - testUtils.form.clickDiscard(form); - testUtils.dom.click('.modal-footer .btn-primary'); // click on confirm + await testUtils.form.clickDiscard(form); + await testUtils.dom.click('.modal-footer .btn-primary'); // click on confirm assert.notOk(form.$el.prop('outerHTML').match('xphone'), "the string xphone should not be present after discarding"); form.destroy(); }); - QUnit.test('discard changes on a new (non dirty, except for defaults) form view', function (assert) { + QUnit.test('discard changes on a new (non dirty, except for defaults) form view', async function (assert) { assert.expect(3); this.data.partner.fields.foo.default = "ABC"; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2315,7 +2323,7 @@ QUnit.module('Views', { assert.strictEqual(form.$('input[name=foo]').val(), 'ABC', "input should contain ABC"); - testUtils.form.clickDiscard(form); + await testUtils.form.clickDiscard(form); assert.containsNone(document.body, '.modal', "there should not be a confirm modal"); @@ -2323,12 +2331,12 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('discard changes on a new (dirty) form view', function (assert) { + QUnit.test('discard changes on a new (dirty) form view', async function (assert) { assert.expect(8); this.data.partner.fields.foo.default = "ABC"; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2346,35 +2354,30 @@ QUnit.module('Views', { }); // edit the foo field - assert.strictEqual(form.$('input[name=foo]').val(), 'ABC', - "input should contain ABC"); - testUtils.fields.editInput(form.$('input[name=foo]'), 'DEF'); + assert.strictEqual(form.$('input').val(), 'ABC', 'input should contain ABC'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'DEF'); // discard the changes and check it has properly been discarded - testUtils.form.clickDiscard(form); - assert.containsOnce(document.body, '.modal', "there should be a confirm modal"); - assert.strictEqual(form.$('input[name=foo]').val(), 'DEF', - "input should be DEF"); - testUtils.dom.click('.modal-footer .btn-primary'); // click on confirm - assert.strictEqual(form.$('input[name=foo]').val(), 'ABC', - "input should now be ABC"); + await testUtils.form.clickDiscard(form); + assert.containsOnce(document.body, '.modal', 'there should be a confirm modal'); + assert.strictEqual(form.$('input').val(), 'DEF', 'input should be DEF'); + await testUtils.dom.click('.modal-footer .btn-primary'); // click on confirm + assert.strictEqual(form.$('input').val(), 'ABC', 'input should now be ABC'); // redirty and discard the field foo (to make sure initial changes haven't been lost) - testUtils.fields.editInput(form.$('input[name=foo]'), 'GHI'); - testUtils.form.clickDiscard(form); - assert.strictEqual(form.$('input[name=foo]').val(), 'GHI', - "input should be GHI"); - testUtils.dom.click('.modal-footer .btn-primary') // click on confirm - assert.strictEqual(form.$('input[name=foo]').val(), 'ABC', - "input should now be ABC"); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'GHI'); + await testUtils.form.clickDiscard(form); + assert.strictEqual(form.$('input').val(), 'GHI', 'input should be GHI'); + await testUtils.dom.click('.modal-footer .btn-primary'); // click on confirm + assert.strictEqual(form.$('input').val(), 'ABC', 'input should now be ABC'); form.destroy(); }); - QUnit.test('discard changes on a duplicated record', function (assert) { + QUnit.test('discard changes on a duplicated record', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2382,28 +2385,27 @@ QUnit.module('Views', { res_id: 1, viewOptions: {hasSidebar: true}, }); - testUtils.form.clickEdit(form); - testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); - testUtils.form.clickSave(form); + await testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + await testUtils.form.clickSave(form); - testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); - testUtils.dom.click(form.sidebar.$('a:contains(Duplicate)')); + await testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + await testUtils.dom.click(form.sidebar.$('a:contains(Duplicate)')); - assert.strictEqual(form.$('input[name=foo]').val(), 'tralala', - "input should contain ABC"); + assert.strictEqual(form.$('input[name=foo]').val(), 'tralala', 'input should contain ABC'); - testUtils.form.clickDiscard(form); + await testUtils.form.clickDiscard(form); assert.containsNone(document.body, '.modal', "there should not be a confirm modal"); form.destroy(); }); - QUnit.test('switching to another record from a dirty one', function (assert) { + QUnit.test("switching to another record from a dirty one", async function (assert) { assert.expect(11); var nbWrite = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2425,26 +2427,25 @@ QUnit.module('Views', { assert.strictEqual(form.$('.o_pager_limit').text(), '2', "pager limit should be 2"); // switch to edit mode - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('input[name=foo]').val(), 'yop', "input should contain yop"); // edit the foo field - testUtils.fields.editInput(form.$('input[name=foo]'), 'new value'); - assert.strictEqual(form.$('input[name=foo]').val(), 'new value', - "input should contain new value"); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'new value'); + assert.strictEqual(form.$('input').val(), 'new value', 'input should contain new value'); // click on the pager to switch to the next record and cancel the confirm request - testUtils.dom.click('.o_pager_next'); // click on next + await testUtils.dom.click('.o_pager_next'); // click on next assert.containsOnce(document.body, '.modal', "a confirm modal should be displayed"); - testUtils.dom.click('.modal-footer .btn-secondary'); // click on cancel + await testUtils.dom.click('.modal-footer .btn-secondary'); // click on cancel assert.strictEqual(form.$('input[name=foo]').val(), 'new value', "input should still contain new value"); assert.strictEqual(form.$('.o_pager_value').text(), '1', "pager value should still be 1"); // click on the pager to switch to the next record and confirm - testUtils.dom.click('.o_pager_next'); // click on next + await testUtils.dom.click('.o_pager_next'); // click on next assert.containsOnce(document.body, '.modal', "a confirm modal should be displayed"); - testUtils.dom.click('.modal-footer .btn-primary'); // click on confirm + await testUtils.dom.click('.modal-footer .btn-primary'); // click on confirm assert.strictEqual(form.$('input[name=foo]').val(), 'blip', "input should contain blip"); assert.strictEqual(form.$('.o_pager_value').text(), '2', "pager value should be 2"); @@ -2452,10 +2453,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('handling dirty state: switching to another record', function (assert) { + QUnit.test('handling dirty state: switching to another record', async function (assert) { assert.expect(12); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2472,18 +2473,18 @@ QUnit.module('Views', { assert.strictEqual(form.$('.o_pager_value').text(), '1', "pager value should be 1"); // switch to edit mode - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('input[name=foo]').val(), 'yop', "input should contain yop"); // edit the foo field - testUtils.fields.editInput(form.$('input[name=foo]'), 'new value'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'new value'); assert.strictEqual(form.$('input[name=foo]').val(), 'new value', "input should contain new value"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); // click on the pager to switch to the next record and cancel the confirm request - testUtils.dom.click('.o_pager_next'); // click on next + await testUtils.dom.click('.o_pager_next'); // click on next assert.containsNone(document.body, '.modal:visible', "no confirm modal should be displayed"); assert.strictEqual(form.$('.o_pager_value').text(), '2', "pager value should be 2"); @@ -2492,35 +2493,35 @@ QUnit.module('Views', { 'priority widget should have been rendered with correct value'); // edit the value in readonly - testUtils.dom.click(form.$('.o_priority .fa-star-o:first')); // click on the first star + await testUtils.dom.click(form.$('.o_priority .fa-star-o:first')); // click on the first star assert.containsOnce(form, '.o_priority .fa-star', 'priority widget should have been updated'); - testUtils.dom.click('.o_pager_next'); // click on next + await testUtils.dom.click('.o_pager_next'); // click on next assert.containsNone(document.body, '.modal:visible', "no confirm modal should be displayed"); assert.strictEqual(form.$('.o_pager_value').text(), '1', "pager value should be 1"); // switch to edit mode - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('input[name=foo]').val(), 'new value', "input should contain yop"); // edit the foo field - testUtils.fields.editInput(form.$('input[name=foo]'), 'wrong value'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'wrong value'); - testUtils.form.clickDiscard(form); + await testUtils.form.clickDiscard(form); assert.containsOnce(document.body, '.modal', "a confirm modal should be displayed"); - testUtils.dom.click('.modal-footer .btn-primary'); // click on confirm - testUtils.dom.click('.o_pager_next'); // click on next + await testUtils.dom.click('.modal-footer .btn-primary'); // click on confirm + await testUtils.dom.click('.o_pager_next'); // click on next assert.strictEqual(form.$('.o_pager_value').text(), '2', "pager value should be 2"); form.destroy(); }); - QUnit.test('restore local state when switching to another record', function (assert) { + QUnit.test('restore local state when switching to another record', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2542,23 +2543,23 @@ QUnit.module('Views', { }); // click on second page tab - testUtils.dom.click(form.$('.o_notebook .nav-link:eq(1)')); + await testUtils.dom.click(form.$('.o_notebook .nav-link:eq(1)')); assert.doesNotHaveClass(form.$('.o_notebook .nav-link:eq(0)'), 'active'); assert.hasClass(form.$('.o_notebook .nav-link:eq(1)'), 'active'); // click on the pager to switch to the next record - testUtils.dom.click('.o_pager_next'); + await testUtils.dom.click('.o_pager_next'); assert.doesNotHaveClass(form.$('.o_notebook .nav-link:eq(0)'), 'active'); assert.hasClass(form.$('.o_notebook .nav-link:eq(1)'), 'active'); form.destroy(); }); - QUnit.test('pager is hidden in create mode', function (assert) { + QUnit.test('pager is hidden in create mode', async function (assert) { assert.expect(7); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2578,10 +2579,10 @@ QUnit.module('Views', { assert.strictEqual(form.pager.$('.o_pager_limit').text(), "2", "current pager limit should be 1"); - testUtils.form.clickCreate(form); + await testUtils.form.clickCreate(form); assert.isNotVisible(form.pager); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.isVisible(form.pager); assert.strictEqual(form.pager.$('.o_pager_value').text(), "3", "current pager value should be 3"); @@ -2591,12 +2592,12 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('switching to another record, in readonly mode', function (assert) { + QUnit.test('switching to another record, in readonly mode', async function (assert) { assert.expect(5); var pushStateCount = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2615,7 +2616,7 @@ QUnit.module('Views', { assert.strictEqual(form.mode, 'readonly', 'form view should be in readonly mode'); assert.strictEqual(form.pager.$('.o_pager_value').text(), "1", 'pager value should be 1'); - testUtils.dom.click(form.pager.$('.o_pager_next')); + await testUtils.dom.click(form.pager.$('.o_pager_next')); assert.strictEqual(form.pager.$('.o_pager_value').text(), "2", 'pager value should be 2'); assert.strictEqual(form.mode, 'readonly', 'form view should be in readonly mode'); @@ -2624,10 +2625,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('modifiers are reevaluated when creating new record', function (assert) { + QUnit.test('modifiers are reevaluated when creating new record', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2643,7 +2644,7 @@ QUnit.module('Views', { assert.containsOnce(form, 'span.foo_field'); assert.isNotVisible(form.$('span.foo_field')); - testUtils.form.clickCreate(form); + await testUtils.form.clickCreate(form); assert.containsOnce(form, 'input.foo_field'); assert.isVisible(form.$('input.foo_field')); @@ -2651,14 +2652,14 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('empty readonly fields are visible on new records', function (assert) { + QUnit.test('empty readonly fields are visible on new records', async function (assert) { assert.expect(2); this.data.partner.fields.foo.readonly = true; this.data.partner.fields.foo.default = undefined; this.data.partner.records[0].foo = undefined; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2672,16 +2673,16 @@ QUnit.module('Views', { assert.containsOnce(form, '.o_field_empty'); - testUtils.form.clickCreate(form); + await testUtils.form.clickCreate(form); assert.containsNone(form, '.o_field_empty'); form.destroy(); }); - QUnit.test('all group children have correct layout classname', function (assert) { + QUnit.test('all group children have correct layout classname', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2703,10 +2704,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('deleting a record', function (assert) { + QUnit.test('deleting a record', async function (assert) { assert.expect(8); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2726,13 +2727,13 @@ QUnit.module('Views', { assert.ok(!$('.modal:visible').length, 'no confirm modal should be displayed'); // open sidebar and delete - testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); - testUtils.dom.click(form.sidebar.$('a:contains(Delete)')); + await testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + await testUtils.dom.click(form.sidebar.$('a:contains(Delete)')); assert.ok($('.modal').length, 'a confirm modal should be displayed'); // confirm the delete - testUtils.dom.click($('.modal-footer button.btn-primary')); + await testUtils.dom.click($('.modal-footer button.btn-primary')); assert.strictEqual(form.pager.$('.o_pager_value').text(), "1", 'pager value should be 1'); assert.strictEqual(form.pager.$('.o_pager_limit').text(), "2", 'pager limit should be 2'); @@ -2741,10 +2742,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('deleting the last record', function (assert) { + QUnit.test('deleting the last record', async function (assert) { assert.expect(6); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2762,27 +2763,27 @@ QUnit.module('Views', { }); // open sidebar, click on delete and confirm - testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); - testUtils.dom.click(form.sidebar.$('a:contains(Delete)')); + await testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + await testUtils.dom.click(form.sidebar.$('a:contains(Delete)')); - testUtils.mock.intercept(form, 'history_back', function () { + await testUtils.mock.intercept(form, 'history_back', function () { assert.step('history_back'); }); assert.strictEqual($('.modal').length, 1, 'a confirm modal should be displayed'); - testUtils.dom.click($('.modal-footer button.btn-primary')); + await testUtils.dom.click($('.modal-footer button.btn-primary')); assert.strictEqual($('.modal').length, 0, 'no confirm modal should be displayed'); assert.verifySteps(['read', 'unlink', 'history_back']); form.destroy(); }); - QUnit.test('empty required fields cannot be saved', function (assert) { + QUnit.test('empty required fields cannot be saved', async function (assert) { assert.expect(5); this.data.partner.fields.foo.required = true; delete this.data.partner.fields.foo.default; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2804,22 +2805,22 @@ QUnit.module('Views', { }, }); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.hasClass(form.$('label.o_form_label'),'o_field_invalid'); assert.hasClass(form.$('input[name=foo]'),'o_field_invalid'); - testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); assert.containsNone(form, '.o_field_invalid'); form.destroy(); }); - QUnit.test('changes in a readonly form view are saved directly', function (assert) { + QUnit.test('changes in a readonly form view are saved directly', async function (assert) { assert.expect(10); var nbWrite = 0; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2844,36 +2845,36 @@ QUnit.module('Views', { 'priority widget should have been rendered with correct value'); // edit the value in readonly - testUtils.dom.click(form.$('.o_priority .fa-star-o:first')); + await testUtils.dom.click(form.$('.o_priority .fa-star-o:first')); assert.strictEqual(nbWrite, 1, 'should have saved directly'); assert.containsOnce(form, '.o_priority .fa-star', 'priority widget should have been updated'); // switch to edit mode and edit the value again - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsN(form, '.o_priority .o_priority_star', 2, 'priority widget should have been correctly rendered'); assert.containsOnce(form, '.o_priority .fa-star', 'priority widget should have correct value'); - testUtils.dom.click(form.$('.o_priority .fa-star-o:first')); + await testUtils.dom.click(form.$('.o_priority .fa-star-o:first')); assert.strictEqual(nbWrite, 1, 'should not have saved directly'); assert.containsN(form, '.o_priority .fa-star', 2, 'priority widget should have been updated'); // save - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(nbWrite, 2, 'should not have saved directly'); assert.containsN(form, '.o_priority .fa-star', 2, 'priority widget should have correct value'); form.destroy(); }); - QUnit.test('display a dialog if onchange result is a warning', function (assert) { + QUnit.test('display a dialog if onchange result is a warning', async function (assert) { assert.expect(5); this.data.partner.onchanges = { foo: true }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2883,7 +2884,7 @@ QUnit.module('Views', { res_id: 2, mockRPC: function (route, args) { if (args.method === 'onchange') { - return $.when({ + return Promise.resolve({ value: { int_field: 10 }, warning: { title: "Warning", @@ -2905,23 +2906,23 @@ QUnit.module('Views', { }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('input[name=int_field]').val(), '9'); - testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); assert.strictEqual(form.$('input[name=int_field]').val(), '10'); form.destroy(); }); - QUnit.test('can create record even if onchange returns a warning', function (assert) { + QUnit.test('can create record even if onchange returns a warning', async function (assert) { assert.expect(2); this.data.partner.onchanges = { foo: true }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2930,7 +2931,7 @@ QUnit.module('Views', { '</form>', mockRPC: function (route, args) { if (args.method === 'onchange') { - return $.when({ + return Promise.resolve({ value: { int_field: 10 }, warning: { title: "Warning", @@ -2942,7 +2943,7 @@ QUnit.module('Views', { }, intercepts: { warning: function (event) { - assert.step('warning'); + assert.ok(true, 'should trigger a warning'); }, }, }); @@ -2952,12 +2953,12 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('do nothing if add a line in one2many result in a onchange with a warning', function (assert) { - assert.expect(2); + QUnit.test('do nothing if add a line in one2many result in a onchange with a warning', async function (assert) { + assert.expect(3); this.data.partner.onchanges = { foo: true }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -2971,7 +2972,7 @@ QUnit.module('Views', { res_id: 2, mockRPC: function (route, args) { if (args.method === 'onchange') { - return $.when({ + return Promise.resolve({ value: {}, warning: { title: "Warning", @@ -2989,19 +2990,20 @@ QUnit.module('Views', { }); // go to edit mode, click to add a record in the o2m - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.containsNone(form, 'tr.o_data_row', "should not have added a line"); + assert.verifySteps(["should have triggered a warning"]); form.destroy(); }); - QUnit.test('default_get, onchange which fails, should still work', function (assert) { + QUnit.test('default_get, onchange which fails, should still work', async function (assert) { assert.expect(1); this.data.partner.onchanges.foo = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3013,8 +3015,8 @@ QUnit.module('Views', { // we simulate a validation error. In the 'real' web client, // the server error will be used by the session to display // an error dialog. From the point of view of the basic - // model, the deferred is just rejected. - return $.Deferred().reject(); + // model, the promise is just rejected. + return Promise.reject(); } return this._super.apply(this, arguments); }, @@ -3034,7 +3036,7 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('attrs are properly transmitted to new records', function (assert) { + QUnit.test('attrs are properly transmitted to new records', async function (assert) { assert.expect(2); // this test checks that the fieldsInfo have been transmitted to the @@ -3046,7 +3048,7 @@ QUnit.module('Views', { string_false: "Test Environment", hover_false: "Switch to production environment" }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3062,7 +3064,7 @@ QUnit.module('Views', { assert.strictEqual(form.$('.o_stat_text.o_not_hover:contains(Production Environment)').length, 1, "button should contain correct string"); - testUtils.form.clickCreate(form); + await testUtils.form.clickCreate(form); assert.strictEqual(form.$('.o_stat_text.o_not_hover:contains(Test Environment)').length, 1, "button should contain correct string"); @@ -3070,10 +3072,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('button box is rendered in create mode', function (assert) { + QUnit.test('button box is rendered in create mode', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3092,21 +3094,21 @@ QUnit.module('Views', { "button box should be displayed in readonly"); // edit mode - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, '.oe_stat_button', "button box should be displayed in edit on an existing record"); // create mode (leave edition first!) - testUtils.form.clickDiscard(form); - testUtils.form.clickCreate(form); + await testUtils.form.clickDiscard(form); + await testUtils.form.clickCreate(form); assert.containsOnce(form, '.oe_stat_button', "button box should be displayed when creating a new record as well"); form.destroy(); }); - QUnit.test('properly apply onchange on one2many fields', function (assert) { + QUnit.test('properly apply onchange on one2many fields', async function (assert) { assert.expect(5); this.data.partner.records[0].p = [4]; @@ -3119,7 +3121,7 @@ QUnit.module('Views', { ]; }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3140,8 +3142,8 @@ QUnit.module('Views', { "the 'display_name' of the one2many record should be correct"); // switch to edit mode - testUtils.form.clickEdit(form); - testUtils.fields.editInput(form.$('input[name=foo]'), 'let us trigger an onchange'); + await testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'let us trigger an onchange'); var $o2m = form.$('.o_field_one2many'); assert.strictEqual($o2m.find('.o_data_row').length, 2, "there should be two linked record"); @@ -3153,7 +3155,7 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('update many2many value in one2many after onchange', function (assert) { + QUnit.test('update many2many value in one2many after onchange', async function (assert) { assert.expect(2); this.data.partner.records[1].p = [4]; @@ -3168,7 +3170,7 @@ QUnit.module('Views', { ]; }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3185,21 +3187,21 @@ QUnit.module('Views', { }); assert.strictEqual($('div[name="p"] .o_data_row td').text().trim(), "aaaNo records", "should have proper initial content"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); - testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'tralala'); assert.strictEqual($('div[name="p"] .o_data_row td').text().trim(), "goldNo records", "should have proper initial content"); form.destroy(); }); - QUnit.test('delete a line in a one2many while editing another line triggers a warning', function (assert) { + QUnit.test('delete a line in a one2many while editing another line triggers a warning', async function (assert) { assert.expect(3); this.data.partner.records[0].p = [1, 2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3213,15 +3215,15 @@ QUnit.module('Views', { res_id: 1, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_data_cell').first()); - testUtils.fields.editInput(form.$('input[name=display_name]'), ''); - testUtils.dom.click(form.$('.fa-trash-o').eq(1)); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_data_cell').first()); + await testUtils.fields.editInput(form.$('input[name=display_name]'), ''); + await testUtils.dom.click(form.$('.fa-trash-o').eq(1)); assert.strictEqual($('.modal').find('.modal-title').first().text(), "Warning", "Clicking out of a dirty line while editing should trigger a warning modal."); - testUtils.dom.click($('.modal').find('.btn-primary')); + await testUtils.dom.click($('.modal').find('.btn-primary')); assert.strictEqual(form.$('.o_data_cell').first().text(), "first record", "Value should have been reset to what it was before editing began."); @@ -3230,7 +3232,7 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('properly apply onchange on many2many fields', function (assert) { + QUnit.test('properly apply onchange on many2many fields', async function (assert) { assert.expect(14); this.data.partner.onchanges = { @@ -3242,7 +3244,7 @@ QUnit.module('Views', { ]; }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3274,8 +3276,8 @@ QUnit.module('Views', { "there should be no many2many record linked at first"); // switch to edit mode - testUtils.form.clickEdit(form); - testUtils.fields.editInput(form.$('input[name=foo]'), 'let us trigger an onchange'); + await testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'let us trigger an onchange'); var $m2m = form.$('.o_field_many2many'); assert.strictEqual($m2m.find('.o_data_row').length, 2, "there should be two linked records"); @@ -3284,14 +3286,14 @@ QUnit.module('Views', { assert.strictEqual($m2m.find('.o_data_row:nth(1) td:first').text(), 'silver', "the 'display_name' of the second m2m record should be correctly displayed"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.verifySteps(['read', 'onchange', 'read', 'write', 'read', 'read']); form.destroy(); }); - QUnit.test('display_name not sent for onchanges if not in view', function (assert) { + QUnit.test('display_name not sent for onchanges if not in view', async function (assert) { assert.expect(7); this.data.partner.records[0].timmy = [12]; @@ -3302,7 +3304,7 @@ QUnit.module('Views', { name: function () {}, }; var readInModal = false; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3367,17 +3369,17 @@ QUnit.module('Views', { }); // trigger the onchange - testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), "coucou"); + await testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), "coucou"); // open a subrecord and trigger an onchange readInModal = true; - testUtils.dom.click(form.$('.o_data_row .o_data_cell:first')); - testUtils.fields.editInput($('.modal .o_field_widget[name=name]'), "new name"); + await testUtils.dom.click(form.$('.o_data_row .o_data_cell:first')); + await testUtils.fields.editInput($('.modal .o_field_widget[name=name]'), "new name"); form.destroy(); }); - QUnit.test('onchanges on date(time) fields', function (assert) { + QUnit.test('onchanges on date(time) fields', async function (assert) { assert.expect(6); this.data.partner.onchanges = { @@ -3386,7 +3388,7 @@ QUnit.module('Views', { obj.datetime = '2021-12-12 10:55:05'; }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3410,7 +3412,7 @@ QUnit.module('Views', { assert.strictEqual(form.$('.o_field_widget[name=datetime]').text(), '12/12/2016 12:55:05', "the initial datetime should be correct"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_field_widget[name=date] input').val(), '01/25/2017', "the initial date should be correct in edit"); @@ -3418,7 +3420,7 @@ QUnit.module('Views', { '12/12/2016 12:55:05', "the initial datetime should be correct in edit"); // trigger the onchange - testUtils.fields.editInput(form.$('.o_field_widget[name="foo"]'), "coucou"); + await testUtils.fields.editInput(form.$('.o_field_widget[name="foo"]'), "coucou"); assert.strictEqual(form.$('.o_field_widget[name=date] input').val(), '12/12/2021', "the initial date should be correct in edit"); @@ -3428,7 +3430,7 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('onchanges are not sent for each keystrokes', function (assert) { + QUnit.test('onchanges are not sent for each keystrokes', async function (assert) { var done = assert.async(); assert.expect(5); @@ -3439,8 +3441,8 @@ QUnit.module('Views', { obj.int_field = obj.foo.length + 1000; }, }; - var def = $.Deferred(); - var form = createView({ + var def = testUtils.makeTestPromise(); + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3462,19 +3464,18 @@ QUnit.module('Views', { }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); testUtils.fields.editInput(form.$('input[name=foo]'), '1'); assert.strictEqual(onchangeNbr, 0, "no onchange has been called yet"); testUtils.fields.editInput(form.$('input[name=foo]'), '12'); assert.strictEqual(onchangeNbr, 0, "no onchange has been called yet"); - return waitForFinishedOnChange().then(function () { + return waitForFinishedOnChange().then(async function () { assert.strictEqual(onchangeNbr, 1, "one onchange has been called"); // add something in the input, then focus another input - testUtils.fields.editInput(form.$('input[name=foo]'), '123'); - form.$('input[name=foo]').change(); + await testUtils.fields.editAndTrigger(form.$('input[name=foo]'), '123', ['change']); assert.strictEqual(onchangeNbr, 2, "one onchange has been called immediately"); return waitForFinishedOnChange(); @@ -3487,13 +3488,13 @@ QUnit.module('Views', { function waitForFinishedOnChange() { return def.then(function () { - def = $.Deferred(); + def = testUtils.makeTestPromise(); return concurrency.delay(0); }); } }); - QUnit.test('onchanges are not sent for invalid values', function (assert) { + QUnit.test('onchanges are not sent for invalid values', async function (assert) { assert.expect(6); this.data.partner.onchanges = { @@ -3501,7 +3502,7 @@ QUnit.module('Views', { obj.foo = String(obj.int_field); }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3515,21 +3516,21 @@ QUnit.module('Views', { }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // edit int_field, and check that an onchange has been applied - testUtils.fields.editInput(form.$('input[name="int_field"]'), "123"); + await testUtils.fields.editInput(form.$('input[name="int_field"]'), "123"); assert.strictEqual(form.$('input[name="foo"]').val(), "123", "the onchange has been applied"); // enter an invalid value in a float, and check that no onchange has // been applied - testUtils.fields.editInput(form.$('input[name="int_field"]'), "123a"); + await testUtils.fields.editInput(form.$('input[name="int_field"]'), "123a"); assert.strictEqual(form.$('input[name="foo"]').val(), "123", "the onchange has not been applied"); // save, and check that the int_field input is marked as invalid - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.hasClass(form.$('input[name="int_field"]'),'o_field_invalid', "input int_field is marked as invalid"); @@ -3537,10 +3538,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('rpc complete after destroying parent', function (assert) { + QUnit.test('rpc complete after destroying parent', async function (assert) { // We just test that there is no crash in this situation assert.expect(0); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3555,20 +3556,20 @@ QUnit.module('Views', { } } }); - testUtils.dom.click(form.$('.o_form_button_update')); + await testUtils.dom.click(form.$('.o_form_button_update')); }); - QUnit.test('onchanges that complete after discarding', function (assert) { - assert.expect(4); + QUnit.test('onchanges that complete after discarding', async function (assert) { + assert.expect(5); - var def1 = $.Deferred(); + var def1 = testUtils.makeTestPromise(); this.data.partner.onchanges = { foo: function (obj) { obj.int_field = obj.foo.length + 1000; }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3591,31 +3592,33 @@ QUnit.module('Views', { // go into edit mode assert.strictEqual(form.$('span[name="foo"]').text(), "blip", "field foo should be displayed to initial value"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // edit a value - testUtils.fields.editInput(form.$('input[name=foo]'), '1234'); + await testUtils.fields.editInput(form.$('input[name=foo]'), '1234'); // discard changes - testUtils.form.clickDiscard(form); - testUtils.dom.click($('.modal-footer .btn-primary')); + await testUtils.form.clickDiscard(form); + await testUtils.dom.click($('.modal-footer .btn-primary')); assert.strictEqual(form.$('span[name="foo"]').text(), "blip", "field foo should still be displayed to initial value"); // complete the onchange def1.resolve(); + await testUtils.nextTick(); assert.strictEqual(form.$('span[name="foo"]').text(), "blip", "field foo should still be displayed to initial value"); + assert.verifySteps(['onchange is done']); form.destroy(); }); - QUnit.test('discarding before save returns', function (assert) { + QUnit.test('discarding before save returns', async function (assert) { assert.expect(4); - var def = $.Deferred(); + var def = testUtils.makeTestPromise(); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3635,10 +3638,10 @@ QUnit.module('Views', { }, }); - testUtils.fields.editInput(form.$('input[name=foo]'), '1234'); + await testUtils.fields.editInput(form.$('input[name=foo]'), '1234'); // save the value and discard directly - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.discardChanges(); // Simulate click on breadcrumb assert.strictEqual(form.$('.o_field_widget[name="foo"]').val(), "1234", @@ -3648,7 +3651,7 @@ QUnit.module('Views', { // complete the write def.resolve(); - + await testUtils.nextTick(); assert.strictEqual($('.modal').length, 0, "Confirm dialog should not be displayed"); assert.strictEqual(form.$('.o_field_widget[name="foo"]').text(), "1234", @@ -3657,7 +3660,7 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('unchanged relational data is sent for onchanges', function (assert) { + QUnit.test('unchanged relational data is sent for onchanges', async function (assert) { assert.expect(1); this.data.partner.records[1].p = [4]; @@ -3666,7 +3669,7 @@ QUnit.module('Views', { obj.int_field = obj.foo.length + 1000; }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3692,13 +3695,13 @@ QUnit.module('Views', { }, }); - testUtils.form.clickEdit(form); - testUtils.fields.editInput(form.$('input[name=foo]'), 'trigger an onchange'); + await testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'trigger an onchange'); form.destroy(); }); - QUnit.test('onchanges on unknown fields of o2m are ignored', function (assert) { + QUnit.test('onchanges on unknown fields of o2m are ignored', async function (assert) { // many2one fields need to be postprocessed (the onchange returns [id, // display_name]), but if we don't know the field, we can't know it's a // many2one, so it isn't ignored, its value is an array instead of a @@ -3709,7 +3712,7 @@ QUnit.module('Views', { this.data.partner.onchanges = { foo: function () {}, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3732,7 +3735,7 @@ QUnit.module('Views', { res_id: 2, mockRPC: function (route, args) { if (args.method === 'onchange') { - return $.when({ + return Promise.resolve({ value: { p: [ [5], @@ -3753,25 +3756,25 @@ QUnit.module('Views', { }, }); - testUtils.form.clickEdit(form); - testUtils.fields.editInput(form.$('input[name=foo]'), 'trigger an onchange'); + await testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'trigger an onchange'); assert.strictEqual(form.$('.o_data_row td:first').text(), 'foo changed', "onchange should have been correctly applied on field in o2m list"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('onchange value are not discarded on o2m edition', function (assert) { + QUnit.test('onchange value are not discarded on o2m edition', async function (assert) { assert.expect(4); this.data.partner.records[1].p = [4]; this.data.partner.onchanges = { foo: function () {}, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3794,7 +3797,7 @@ QUnit.module('Views', { res_id: 2, mockRPC: function (route, args) { if (args.method === 'onchange') { - return $.when({ + return Promise.resolve({ value: { p: [[5], [1, 4, {foo: 'foo changed'}]], }, @@ -3809,17 +3812,17 @@ QUnit.module('Views', { }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_data_row td:first').text(), 'My little Foo Value', "the initial value should be the default one"); - testUtils.fields.editInput(form.$('input[name=foo]'), 'trigger an onchange'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'trigger an onchange'); assert.strictEqual(form.$('.o_data_row td:first').text(), 'foo changed', "onchange should have been correctly applied on field in o2m list"); - testUtils.dom.click(form.$('.o_data_row')); + await testUtils.dom.click(form.$('.o_data_row')); assert.strictEqual($('.modal .modal-title').text().trim(), 'Open: one2many field', "the field string is displayed in the modal title"); assert.strictEqual($('.modal .o_field_widget').val(), 'foo changed', @@ -3828,7 +3831,7 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('args of onchanges in o2m fields are correct (inline edition)', function (assert) { + QUnit.test('args of onchanges in o2m fields are correct (inline edition)', async function (assert) { assert.expect(3); this.data.partner.records[1].p = [4]; @@ -3839,7 +3842,7 @@ QUnit.module('Views', { obj.foo = '[' + obj.rel_field.foo + '] ' + obj.int_field; }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3857,26 +3860,26 @@ QUnit.module('Views', { res_id: 2, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_data_row td:first').text(), 'My little Foo Value', "the initial value should be the default one"); - testUtils.dom.click(form.$('.o_data_row td:nth(1)')); - testUtils.fields.editInput(form.$('.o_data_row input:nth(1)'), 77); + await testUtils.dom.click(form.$('.o_data_row td:nth(1)')); + await testUtils.fields.editInput(form.$('.o_data_row input:nth(1)'), 77); assert.strictEqual(form.$('.o_data_row input:first').val(), '[blip] 77', "onchange should have been correctly applied"); // create a new o2m record - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual(form.$('.o_data_row input:first').val(), '[blip] 14', "onchange should have been correctly applied after default get"); form.destroy(); }); - QUnit.test('args of onchanges in o2m fields are correct (dialog edition)', function (assert) { + QUnit.test('args of onchanges in o2m fields are correct (dialog edition)', async function (assert) { assert.expect(6); this.data.partner.records[1].p = [4]; @@ -3887,7 +3890,7 @@ QUnit.module('Views', { obj.foo = '[' + obj.rel_field.foo + '] ' + obj.int_field; }, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3908,39 +3911,41 @@ QUnit.module('Views', { res_id: 2, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('.o_data_row td:first').text(), 'My little Foo Value', "the initial value should be the default one"); - testUtils.dom.click(form.$('.o_data_row td:first')); - testUtils.fields.editInput($('.modal input:nth(1)'), 77); + await testUtils.dom.click(form.$('.o_data_row td:first')); + await testUtils.nextTick(); + await testUtils.fields.editInput($('.modal input:nth(1)'), 77); assert.strictEqual($('.modal input:first').val(), '[blip] 77', "onchange should have been correctly applied"); - testUtils.dom.click($('.modal-footer .btn-primary')); + await testUtils.dom.click($('.modal-footer .btn-primary')); assert.strictEqual(form.$('.o_data_row td:first').text(), '[blip] 77', "onchange should have been correctly applied"); // create a new o2m record - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual($('.modal .modal-title').text().trim(), 'Create custom label', "the custom field label is applied in the modal title"); assert.strictEqual($('.modal input:first').val(), '[blip] 14', "onchange should have been correctly applied after default get"); - testUtils.dom.clickFirst($('.modal-footer .btn-primary')); + await testUtils.dom.clickFirst($('.modal-footer .btn-primary')); + await testUtils.nextTick(); assert.strictEqual(form.$('.o_data_row:nth(1) td:first').text(), '[blip] 14', "onchange should have been correctly applied after default get"); form.destroy(); }); - QUnit.test('context of onchanges contains the context of changed fields', function (assert) { + QUnit.test('context of onchanges contains the context of changed fields', async function (assert) { assert.expect(2); this.data.partner.onchanges = { foo: function () {}, }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3962,16 +3967,16 @@ QUnit.module('Views', { res_id: 2, }); - testUtils.form.clickEdit(form); - testUtils.fields.editInput(form.$('input[name=foo]'), 'coucou'); + await testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'coucou'); form.destroy(); }); - QUnit.test('navigation with tab key in form view', function (assert) { + QUnit.test('navigation with tab key in form view', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -3988,7 +3993,7 @@ QUnit.module('Views', { }); // go to edit mode - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // focus first input, trigger tab form.$('input[name="foo"]').focus(); @@ -4010,12 +4015,12 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('navigation with tab key in readonly form view', function (assert) { + QUnit.test('navigation with tab key in readonly form view', async function (assert) { assert.expect(3); this.data.partner.records[1].product_id = 37; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4055,12 +4060,12 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('skip invisible fields when navigating with TAB', function (assert) { + QUnit.test('skip invisible fields when navigating with TAB', async function (assert) { assert.expect(1); this.data.partner.records[0].bar = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4075,7 +4080,7 @@ QUnit.module('Views', { res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); form.$('input[name="foo"]').focus(); form.$('input[name="foo"]').trigger($.Event('keydown', {which: $.ui.keyCode.TAB})); assert.strictEqual(form.$('input[name="int_field"]')[0], document.activeElement, @@ -4084,10 +4089,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('clicking on a stat button with a context', function (assert) { + QUnit.test('clicking on a stat button with a context', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4114,15 +4119,15 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(form.$('.oe_stat_button')); + await testUtils.dom.click(form.$('.oe_stat_button')); form.destroy(); }); - QUnit.test('clicking on a stat button with no context', function (assert) { + QUnit.test('clicking on a stat button with no context', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4148,15 +4153,15 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(form.$('.oe_stat_button')); + await testUtils.dom.click(form.$('.oe_stat_button')); form.destroy(); }); - QUnit.test('diplay a stat button outside a buttonbox', function (assert) { + QUnit.test('diplay a stat button outside a buttonbox', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4180,10 +4185,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('diplay something else than a button in a buttonbox', function (assert) { + QUnit.test('diplay something else than a button in a buttonbox', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4208,10 +4213,41 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('display correctly buttonbox, in large size class', function (assert) { + QUnit.test('invisible fields are not considered as visible in a buttonbox', async function (assert) { + assert.expect(2); + + var form = await createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form>' + + '<div name="button_box" class="oe_button_box">' + + '<field name="foo" invisible="1"/>' + + '<field name="bar" invisible="1"/>' + + '<field name="int_field" invisible="1"/>' + + '<field name="qux" invisible="1"/>' + + '<field name="display_name" invisible="1"/>' + + '<field name="state" invisible="1"/>' + + '<field name="date" invisible="1"/>' + + '<field name="datetime" invisible="1"/>' + + '<button type="object" class="oe_stat_button" icon="fa-check-square"/>' + + '</div>' + + '</form>', + res_id: 2, + }); + + assert.strictEqual(form.$('.oe_button_box').children().length, 9, + "button box should contain nine children"); + assert.hasClass(form.$('.oe_button_box'), 'o_not_full', + "the buttonbox should not be full (only 1 visible child)"); + + form.destroy(); + }); + + QUnit.test('display correctly buttonbox, in large size class', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4237,12 +4273,12 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('one2many default value creation', function (assert) { + QUnit.test('one2many default value creation', async function (assert) { assert.expect(1); this.data.partner.records[0].product_ids = [37]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4259,7 +4295,7 @@ QUnit.module('Views', { '</form>', mockRPC: function (route, args) { if (args.method === 'default_get') { - return $.when({ + return Promise.resolve({ product_ids: [[0, 0, { name: 'xdroid', partner_type_id: 12, @@ -4274,14 +4310,14 @@ QUnit.module('Views', { return this._super.apply(this, arguments); }, }); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('many2manys inside one2manys are saved correctly', function (assert) { + QUnit.test('many2manys inside one2manys are saved correctly', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4306,19 +4342,19 @@ QUnit.module('Views', { }); // add a o2m subrecord with a m2m tag - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.many2one.clickOpenDropdown('timmy'); - testUtils.fields.many2one.clickHighlightedItem('timmy'); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.many2one.clickOpenDropdown('timmy'); + await testUtils.fields.many2one.clickHighlightedItem('timmy'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('one2manys (list editable) inside one2manys are saved correctly', function (assert) { + QUnit.test('one2manys (list editable) inside one2manys are saved correctly', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4353,10 +4389,10 @@ QUnit.module('Views', { }); // add a o2m subrecord - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.dom.click($('.modal-body .o_field_one2many .o_field_x2many_list_row_add a')); - testUtils.fields.editInput($('.modal-body input'), 'xtv'); - testUtils.dom.click($('.modal-footer button:first')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click($('.modal-body .o_field_one2many .o_field_x2many_list_row_add a')); + await testUtils.fields.editInput($('.modal-body input'), 'xtv'); + await testUtils.dom.click($('.modal-footer button:first')); assert.strictEqual($('.modal').length, 0, "dialog should be closed"); @@ -4364,16 +4400,16 @@ QUnit.module('Views', { assert.strictEqual(row.children()[0].textContent, '1 record', "the cell should contains the number of record: 1"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('*_view_ref in context are passed correctly', function (assert) { + QUnit.test('*_view_ref in context are passed correctly', async function (assert) { var done = assert.async(); assert.expect(3); - createAsyncView({ + createView({ View: FormView, model: 'partner', data: this.data, @@ -4401,15 +4437,15 @@ QUnit.module('Views', { } return this._super.apply(this, arguments); }, - }).then(function (form) { + }).then(async function (form) { // reload to check that the record's context hasn't been modified - form.reload(); + await form.reload(); form.destroy(); done(); }); }); - QUnit.test('readonly fields with modifiers may be saved', function (assert) { + QUnit.test('readonly fields with modifiers may be saved', async function (assert) { // the readonly property on the field description only applies on view, // this is not a DB constraint. It should be seen as a default value, // that may be overriden in views, for example with modifiers. So @@ -4417,7 +4453,7 @@ QUnit.module('Views', { assert.expect(3); this.data.partner.fields.foo.readonly = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4439,13 +4475,13 @@ QUnit.module('Views', { // bar being set to true, foo shouldn't be readonly and thus its value // could be saved, even if in its field description it is readonly - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.containsOnce(form, 'input[name="foo"]', "foo field should be editable"); - testUtils.fields.editInput(form.$('input[name="foo"]'), 'New foo value'); + await testUtils.fields.editInput(form.$('input[name="foo"]'), 'New foo value'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.strictEqual(form.$('.o_field_widget[name=foo]').text(), 'New foo value', "new value for foo field should have been saved"); @@ -4453,10 +4489,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('check if id and active_id are defined', function (assert) { + QUnit.test('check if id and active_id are defined', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4474,8 +4510,8 @@ QUnit.module('Views', { }, mockRPC: function (route, args) { if (args.method === 'default_get' && args.args[0][0] === 'trululu') { - assert.strictEqual(args.kwargs.context.current_id, false, - "current_id should be false"); + assert.strictEqual(args.kwargs.context.current_id, false, + "current_id should be false"); assert.strictEqual(args.kwargs.context.default_trululu, false, "default_trululu should be false"); } @@ -4483,14 +4519,14 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); form.destroy(); }); - QUnit.test('modifiers are considered on multiple <footer/> tags', function (assert) { + QUnit.test('modifiers are considered on multiple <footer/> tags', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4515,7 +4551,7 @@ QUnit.module('Views', { assert.deepEqual(getVisibleButtonTexts(), ["Hello", "World"], "only the first button section should be visible"); - testUtils.dom.click(form.$(".o_field_boolean input")); + await testUtils.dom.click(form.$(".o_field_boolean input")); assert.deepEqual(getVisibleButtonTexts(), ["Foo"], "only the second button section should be visible"); @@ -4530,10 +4566,10 @@ QUnit.module('Views', { } }); - QUnit.test('buttons in footer are moved to $buttons if necessary', function (assert) { + QUnit.test('buttons in footer are moved to $buttons if necessary', async function (assert) { assert.expect(4); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4551,7 +4587,7 @@ QUnit.module('Views', { assert.containsNone(form.$('.o_form_view'), 'button.infooter'); // check that this still works after a reload - testUtils.form.reload(form); + await testUtils.form.reload(form); assert.containsOnce(form.$('.o_control_panel'), 'button.infooter'); assert.containsNone(form.$('.o_form_view'), 'button.infooter'); @@ -4559,10 +4595,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('render stat button with string inline', function (assert) { + QUnit.test('render stat button with string inline', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', res_id: 1, @@ -4581,11 +4617,11 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('renderer waits for asynchronous fields rendering', function (assert) { + QUnit.test('renderer waits for asynchronous fields rendering', async function (assert) { assert.expect(1); var done = assert.async(); - testUtils.createAsyncView({ + testUtils.createView({ View: FormView, model: 'partner', data: this.data, @@ -4603,7 +4639,7 @@ QUnit.module('Views', { }); }); - QUnit.test('open one2many form containing one2many', function (assert) { + QUnit.test('open one2many form containing one2many', async function (assert) { assert.expect(9); this.data.partner.records[0].product_ids = [37]; @@ -4612,7 +4648,7 @@ QUnit.module('Views', { }; this.data.product.records[0].partner_type_ids = [12]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', res_id: 1, @@ -4652,7 +4688,7 @@ QUnit.module('Views', { var row = form.$('.o_field_one2many .o_list_view .o_data_row'); assert.strictEqual(row.children()[1].textContent, '1 record', "the cell should contains the number of record: 1"); - testUtils.dom.click(row); + await testUtils.dom.click(row); var modal_row = $('.modal-body .o_form_sheet .o_field_one2many .o_list_view .o_data_row'); assert.strictEqual(modal_row.children().length, 2, "the row should contains the 2 fields defined in the form view"); @@ -4663,10 +4699,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('in edit mode, first field is focused', function (assert) { + QUnit.test('in edit mode, first field is focused', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4676,7 +4712,7 @@ QUnit.module('Views', { '</form>', res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(document.activeElement, form.$('input[name="foo"]')[0], "foo field should have focus"); @@ -4686,10 +4722,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('autofocus fields are focused', function (assert) { + QUnit.test('autofocus fields are focused', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4699,17 +4735,17 @@ QUnit.module('Views', { '</form>', res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(document.activeElement, form.$('input[name="foo"]')[0], "foo field should have focus"); form.destroy(); }); - QUnit.test('in create mode, autofocus fields are focused', function (assert) { + QUnit.test('in create mode, autofocus fields are focused', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4724,9 +4760,9 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('create with false values', function (assert) { + QUnit.test('create with false values', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4742,15 +4778,15 @@ QUnit.module('Views', { }, }); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('autofocus first visible field', function (assert) { + QUnit.test('autofocus first visible field', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4765,10 +4801,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('no autofocus with disable_autofocus option [REQUIRE FOCUS]', function (assert) { + QUnit.test('no autofocus with disable_autofocus option [REQUIRE FOCUS]', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4783,7 +4819,7 @@ QUnit.module('Views', { assert.notStrictEqual(document.activeElement, form.$('input[name="int_field"]')[0], "int_field field should not have focus"); - form.update({}); + await form.update({}); assert.notStrictEqual(document.activeElement, form.$('input[name="int_field"]')[0], "int_field field should not have focus"); @@ -4791,7 +4827,7 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('open one2many form containing many2many_tags', function (assert) { + QUnit.test('open one2many form containing many2many_tags', async function (assert) { assert.expect(4); this.data.partner.records[0].product_ids = [37]; @@ -4800,7 +4836,7 @@ QUnit.module('Views', { }; this.data.product.records[0].partner_type_ids = [12, 14]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', res_id: 1, @@ -4833,21 +4869,21 @@ QUnit.module('Views', { }, }); var row = form.$('.o_field_one2many .o_list_view .o_data_row'); - testUtils.dom.click(row); + await testUtils.dom.click(row); assert.verifySteps(['read', 'read', 'read'], "there should be 3 read rpcs"); form.destroy(); }); - QUnit.test('onchanges are applied before checking if it can be saved', function (assert) { + QUnit.test('onchanges are applied before checking if it can be saved', async function (assert) { assert.expect(4); this.data.partner.onchanges.foo = function (obj) {}; this.data.partner.fields.foo.required = true; - var def = $.Deferred(); + var def = testUtils.makeTestPromise(); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4876,20 +4912,21 @@ QUnit.module('Views', { }, }); - testUtils.form.clickEdit(form); - testUtils.fields.editInput(form.$('input[name="foo"]'), ''); - testUtils.form.clickSave(form); + await testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name="foo"]'), ''); + await testUtils.form.clickSave(form); def.resolve(); + await testUtils.nextTick(); assert.verifySteps(['read', 'onchange', 'warning']); form.destroy(); }); - QUnit.test('display toolbar', function (assert) { + QUnit.test('display toolbar', async function (assert) { assert.expect(8); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -4915,7 +4952,7 @@ QUnit.module('Views', { "the active_id shoud be 1."); assert.deepEqual(args.context.active_ids, [1], "the active_ids should be an array with 1 inside."); - return $.when({}); + return Promise.resolve({}); } return this._super.apply(this, arguments); }, @@ -4932,19 +4969,19 @@ QUnit.module('Views', { var $customAction = $actionMenu.find('.dropdown-item:last'); assert.strictEqual($customAction.text().trim(), 'Action partner', "the custom action should have 'Action partner' as name"); - testUtils.mock.intercept(form, 'do_action', function (event) { + await testUtils.mock.intercept(form, 'do_action', function (event) { var context = event.data.action.context.__contexts[1]; assert.strictEqual(context.active_id, 1, "the active_id shoud be 1."); assert.deepEqual(context.active_ids, [1], "the active_ids should be an array with 1 inside."); }); - testUtils.dom.click($customAction); + await testUtils.dom.click($customAction); form.destroy(); }); - QUnit.test('check interactions between multiple FormViewDialogs', function (assert) { + QUnit.test('check interactions between multiple FormViewDialogs', async function (assert) { assert.expect(8); this.data.product.fields.product_ids = { @@ -4953,7 +4990,7 @@ QUnit.module('Views', { this.data.partner.records[0].product_id = 37; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', res_id: 1, @@ -4979,7 +5016,7 @@ QUnit.module('Views', { }, mockRPC: function (route, args) { if (route === '/web/dataset/call_kw/product/get_formview_id') { - return $.when(false); + return Promise.resolve(false); } else if (args.method === 'write') { assert.strictEqual(args.model, 'product', "should write on product model"); @@ -4990,9 +5027,9 @@ QUnit.module('Views', { }, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); // Open first dialog - testUtils.dom.click(form.$('.o_external_button')); + await testUtils.dom.click(form.$('.o_external_button')); assert.strictEqual($('.modal').length, 1, "One FormViewDialog should be opened"); var $firstModal = $('.modal'); @@ -5002,27 +5039,27 @@ QUnit.module('Views', { "display_name should be correctly displayed"); // Open second dialog - testUtils.dom.click($firstModal.find('.o_field_x2many_list_row_add a')); + await testUtils.dom.click($firstModal.find('.o_field_x2many_list_row_add a')); assert.strictEqual($('.modal').length, 2, "two FormViewDialogs should be opened"); var $secondModal = $('.modal:nth(1)'); // Add new value - testUtils.fields.editInput($secondModal.find('input'), 'xtv'); - testUtils.dom.click($secondModal.find('.modal-footer button:first')); + await testUtils.fields.editInput($secondModal.find('input'), 'xtv'); + await testUtils.dom.click($secondModal.find('.modal-footer button:first')); assert.strictEqual($('.modal').length, 1, "last opened dialog should be closed"); // Check that data in first dialog is correctly updated assert.strictEqual($firstModal.find('tr.o_data_row td').text(), 'xtv', "should have added a line with xtv as new record"); - testUtils.dom.click($firstModal.find('.modal-footer button:first')); + await testUtils.dom.click($firstModal.find('.modal-footer button:first')); form.destroy(); }); - QUnit.test('fields and record contexts are not mixed', function (assert) { + QUnit.test('fields and record contexts are not mixed', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5047,15 +5084,15 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(form.$('.o_field_widget[name=trululu] input')); + await testUtils.dom.click(form.$('.o_field_widget[name=trululu] input')); form.destroy(); }); - QUnit.test('do not activate an hidden tab when switching between records', function (assert) { + QUnit.test('do not activate an hidden tab when switching between records', async function (assert) { assert.expect(6); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5084,14 +5121,14 @@ QUnit.module('Views', { "first tab should be active"); // click on the pager to switch to the next record - testUtils.dom.click(form.pager.$('.o_pager_next')); + await testUtils.dom.click(form.pager.$('.o_pager_next')); assert.strictEqual(form.$('.o_notebook .nav-item:not(.o_invisible_modifier)').length, 1, "only the second tab should be visible"); assert.hasClass(form.$('.o_notebook .nav-item:not(.o_invisible_modifier) .nav-link'),'active', "the visible tab should be active"); // click on the pager to switch back to the previous record - testUtils.dom.click(form.pager.$('.o_pager_previous')); + await testUtils.dom.click(form.pager.$('.o_pager_previous')); assert.strictEqual(form.$('.o_notebook .nav-item:not(.o_invisible_modifier)').length, 2, "both tabs should be visible again"); assert.hasClass(form.$('.o_notebook .nav-link:nth(1)'),'active', @@ -5100,16 +5137,16 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('support anchor tags with action type', function (assert) { + QUnit.test('support anchor tags with action type', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, arch: '<form string="Partners">' + '<a type="action" name="42"><i class="fa fa-arrow-right"/> Click me !</a>' + - '</form>', + '</form>', res_id: 1, intercepts: { do_action: function (event) { @@ -5118,17 +5155,17 @@ QUnit.module('Views', { } } }); - testUtils.dom.click(form.$('a[type="action"]')); + await testUtils.dom.click(form.$('a[type="action"]')); form.destroy(); }); - QUnit.test('do not perform extra RPC to read invisible many2one fields', function (assert) { + QUnit.test('do not perform extra RPC to read invisible many2one fields', async function (assert) { assert.expect(2); this.data.partner.fields.trululu.default = 2; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5148,14 +5185,14 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('do not perform extra RPC to read invisible x2many fields', function (assert) { + QUnit.test('do not perform extra RPC to read invisible x2many fields', async function (assert) { assert.expect(2); this.data.partner.records[0].p = [2]; // one2many this.data.partner.records[0].product_ids = [37]; // one2many this.data.partner.records[0].timmy = [12]; // many2many - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5180,13 +5217,13 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('default_order on x2many embedded view', function (assert) { + QUnit.test('default_order on x2many embedded view', async function (assert) { assert.expect(11); this.data.partner.fields.display_name.sortable = true; this.data.partner.records[0].p = [1, 4]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5215,14 +5252,14 @@ QUnit.module('Views', { assert.ok(form.$('.o_field_one2many tbody tr:first td:contains(yop)').length, "record 1 should be first"); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual($('.modal').length, 1, "FormViewDialog should be opened"); - testUtils.fields.editInput($('.modal input[name="foo"]'), 'xop') - testUtils.dom.click($('.modal-footer button:eq(1)')); - testUtils.fields.editInput($('.modal input[name="foo"]'), 'zop') - testUtils.dom.click($('.modal-footer button:first')); + await testUtils.fields.editInput($('.modal input[name="foo"]'), 'xop'); + await testUtils.dom.click($('.modal-footer button:eq(1)')); + await testUtils.fields.editInput($('.modal input[name="foo"]'), 'zop'); + await testUtils.dom.click($('.modal-footer button:first')); // client-side sort assert.ok(form.$('.o_field_one2many tbody tr:eq(0) td:contains(zop)').length, @@ -5233,7 +5270,7 @@ QUnit.module('Views', { "record xop should be third"); // server-side sort - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.ok(form.$('.o_field_one2many tbody tr:eq(0) td:contains(zop)').length, "record zop should be first"); assert.ok(form.$('.o_field_one2many tbody tr:eq(1) td:contains(yop)').length, @@ -5242,10 +5279,10 @@ QUnit.module('Views', { "record xop should be third"); // client-side sort on edit - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_one2many tbody tr:eq(1) td:contains(yop)')); - testUtils.fields.editInput($('.modal input[name="foo"]'), 'zzz'); - testUtils.dom.click($('.modal-footer button:first')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_one2many tbody tr:eq(1) td:contains(yop)')); + await testUtils.fields.editInput($('.modal input[name="foo"]'), 'zzz'); + await testUtils.dom.click($('.modal-footer button:first')); assert.ok(form.$('.o_field_one2many tbody tr:eq(0) td:contains(zzz)').length, "record zzz should be first"); assert.ok(form.$('.o_field_one2many tbody tr:eq(1) td:contains(zop)').length, @@ -5256,10 +5293,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('action context is used when evaluating domains', function (assert) { + QUnit.test('action context is used when evaluating domains', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5280,16 +5317,16 @@ QUnit.module('Views', { return this._super.apply(this, arguments); }, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('div[name="trululu"] input')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('div[name="trululu"] input')); form.destroy(); }); - QUnit.test('form rendering with groups with col/colspan', function (assert) { + QUnit.test('form rendering with groups with col/colspan', async function (assert) { assert.expect(46); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5411,10 +5448,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('outer and inner groups string attribute', function (assert) { + QUnit.test('outer and inner groups string attribute', async function (assert) { assert.expect(5); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5451,10 +5488,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('form group with newline tag inside', function (assert) { + QUnit.test('form group with newline tag inside', async function (assert) { assert.expect(6); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5508,11 +5545,11 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('custom open record dialog title', function (assert) { + QUnit.test('custom open record dialog title', async function (assert) { assert.expect(1); this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5530,14 +5567,14 @@ QUnit.module('Views', { res_id: 1, }); - testUtils.dom.click(form.$('.o_data_row:first')); + await testUtils.dom.click(form.$('.o_data_row:first')); assert.strictEqual($('.modal .modal-title').first().text().trim(), 'Open: custom label', "modal should use the python field string as title"); form.destroy(); }); - QUnit.test('display translation alert', function (assert) { + QUnit.test('display translation alert', async function (assert) { assert.expect(2); this.data.partner.fields.foo.translate = true; @@ -5546,7 +5583,7 @@ QUnit.module('Views', { var multi_lang = _t.database.multi_lang; _t.database.multi_lang = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5561,15 +5598,15 @@ QUnit.module('Views', { res_id: 1, }); - testUtils.form.clickEdit(form); - testUtils.fields.editInput(form.$('input[name="foo"]'), "test"); - testUtils.form.clickSave(form); + await testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name="foo"]'), "test"); + await testUtils.form.clickSave(form); assert.containsOnce(form, '.o_form_view > .alert > div .oe_field_translate', "should have single translation alert"); - testUtils.form.clickEdit(form); - testUtils.fields.editInput(form.$('input[name="display_name"]'), "test2"); - testUtils.form.clickSave(form); + await testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name="display_name"]'), "test2"); + await testUtils.form.clickSave(form); assert.containsN(form, '.o_form_view > .alert > div .oe_field_translate', 2, "should have two translate fields in translation alert"); @@ -5578,7 +5615,7 @@ QUnit.module('Views', { _t.database.multi_lang = multi_lang; }); - QUnit.test('translation alerts are preserved on pager change', function (assert) { + QUnit.test('translation alerts are preserved on pager change', async function (assert) { assert.expect(5); this.data.partner.fields.foo.translate = true; @@ -5586,7 +5623,7 @@ QUnit.module('Views', { var multi_lang = _t.database.multi_lang; _t.database.multi_lang = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5602,27 +5639,27 @@ QUnit.module('Views', { res_id: 1, }); - testUtils.form.clickEdit(form); - testUtils.fields.editInput(form.$('input[name="foo"]'), "test"); - testUtils.form.clickSave(form); + await testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name="foo"]'), "test"); + await testUtils.form.clickSave(form); assert.containsOnce(form, '.o_form_view > .alert > div',"should have a translation alert"); // click on the pager to switch to the next record - form.pager.$('.o_pager_next').click(); + await testUtils.dom.click(form.pager.$('.o_pager_next')); assert.strictEqual(form.$('.o_form_view > .alert > div').length, 0, "should not have a translation alert"); // click on the pager to switch back to the previous record - form.pager.$('.o_pager_previous').click(); + await testUtils.dom.click(form.pager.$('.o_pager_previous')); assert.containsOnce(form, '.o_form_view > .alert > div',"should have a translation alert"); // remove translation alert by click X and check alert even after form reload - form.$('.o_form_view > .alert > .close').click(); + await testUtils.dom.click(form.$('.o_form_view > .alert > .close')); assert.strictEqual(form.$('.o_form_view > .alert > div').length, 0, "should not have a translation alert"); - form.reload(); + await form.reload(); assert.strictEqual(form.$('.o_form_view > .alert > div').length, 0, "should not have a translation alert after reload"); @@ -5630,7 +5667,7 @@ QUnit.module('Views', { _t.database.multi_lang = multi_lang; }); - QUnit.test('translation alerts preseved on reverse breadcrumb', function (assert) { + QUnit.test('translation alerts preseved on reverse breadcrumb', async function (assert) { assert.expect(2); this.data['ir.translation'] = { @@ -5678,23 +5715,21 @@ QUnit.module('Views', { flags: {'search_view': true, 'action_buttons': true}, }]; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: actions, archs: archs, data: this.data, }); - actionManager.doAction(1); - - actionManager.$('.o_form_button_edit').click(); + await actionManager.doAction(1); actionManager.$('input[name="foo"]').val("test").trigger("input"); - actionManager.$('.o_form_button_save').click(); + await testUtils.dom.click(actionManager.$('.o_form_button_save')); assert.strictEqual(actionManager.$('.o_form_view > .alert > div').length, 1, "should have a translation alert"); var currentController = actionManager.getCurrentController().widget; - actionManager.doAction(2, { + await actionManager.doAction(2, { on_reverse_breadcrumb: function () { if (!_.isEmpty(currentController.renderer.alertFields)) { currentController.renderer.displayTranslationAlert(); @@ -5703,7 +5738,7 @@ QUnit.module('Views', { }, }); - $('.o_control_panel .breadcrumb a:first').click(); + await testUtils.dom.click($('.o_control_panel .breadcrumb a:first')); assert.strictEqual(actionManager.$('.o_form_view > .alert > div').length, 1, "should have a translation alert"); @@ -5711,7 +5746,7 @@ QUnit.module('Views', { _t.database.multi_lang = multi_lang; }); - QUnit.test('translate event correctly handled with multiple controllers', function (assert) { + QUnit.test('translate event correctly handled with multiple controllers', async function (assert) { assert.expect(3); this.data.product.fields.name.translate = true; @@ -5721,7 +5756,7 @@ QUnit.module('Views', { var multi_lang = _t.database.multi_lang; _t.database.multi_lang = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5745,23 +5780,23 @@ QUnit.module('Views', { res_id: 1, mockRPC: function (route, args) { if (route === '/web/dataset/call_kw/product/get_formview_id') { - return $.when(false); + return Promise.resolve(false); } else if (route === "/web/dataset/call_button" && args.method === 'translate_fields') { assert.deepEqual(args.args, ["product",37,"name",{}], 'should call "call_button" route'); nbTranslateCalls++; - return $.when(); + return Promise.resolve(); } return this._super.apply(this, arguments); }, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('[name="product_id"] .o_external_button')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('[name="product_id"] .o_external_button')); assert.strictEqual($('.modal-body .o_field_translate').length, 1, "there should be a translate button in the modal"); - testUtils.dom.click($('.modal-body .o_field_translate')); + await testUtils.dom.click($('.modal-body .o_field_translate')); assert.strictEqual(nbTranslateCalls, 1, "should call_button translate once"); @@ -5769,12 +5804,12 @@ QUnit.module('Views', { _t.database.multi_lang = multi_lang; }); - QUnit.test('buttons are disabled until status bar action is resolved', function (assert) { + QUnit.test('buttons are disabled until status bar action is resolved', async function (assert) { assert.expect(9); - var def = $.Deferred(); + var def = testUtils.makeTestPromise(); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5811,9 +5846,9 @@ QUnit.module('Views', { assert.strictEqual(form.$('.oe_button_box button:not(:disabled)').length, 1, "stat buttons should be enabled"); - testUtils.dom.clickFirst(form.$('.o_form_statusbar button')); + await testUtils.dom.clickFirst(form.$('.o_form_statusbar button')); - // The unresolved deferred lets us check the state of the buttons + // The unresolved promise lets us check the state of the buttons assert.strictEqual(form.$buttons.find('button:disabled').length, 4, "control panel buttons should be disabled"); assert.containsN(form, '.o_form_statusbar button:disabled', 2, @@ -5822,7 +5857,7 @@ QUnit.module('Views', { "stat buttons should be disabled"); def.resolve(); - + await testUtils.nextTick(); assert.strictEqual(form.$buttons.find('button:not(:disabled)').length, 4, "control panel buttons should be enabled"); assert.strictEqual(form.$('.o_form_statusbar button:not(:disabled)').length, 2, @@ -5833,12 +5868,12 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('buttons are disabled until button box action is resolved', function (assert) { + QUnit.test('buttons are disabled until button box action is resolved', async function (assert) { assert.expect(9); - var def = $.Deferred(); + var def = testUtils.makeTestPromise(); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5875,9 +5910,9 @@ QUnit.module('Views', { assert.strictEqual(form.$('.oe_button_box button:not(:disabled)').length, 1, "stat buttons should be enabled"); - testUtils.dom.click(form.$('.oe_button_box button')); + await testUtils.dom.click(form.$('.oe_button_box button')); - // The unresolved deferred lets us check the state of the buttons + // The unresolved promise lets us check the state of the buttons assert.strictEqual(form.$buttons.find('button:disabled').length, 4, "control panel buttons should be disabled"); assert.containsN(form, '.o_form_statusbar button:disabled', 2, @@ -5886,7 +5921,7 @@ QUnit.module('Views', { "stat buttons should be disabled"); def.resolve(); - + await testUtils.nextTick(); assert.strictEqual(form.$buttons.find('button:not(:disabled)').length, 4, "control panel buttons should be enabled"); assert.strictEqual(form.$('.o_form_statusbar button:not(:disabled)').length, 2, @@ -5897,10 +5932,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('buttons with "confirm" attribute save before calling the method', function (assert) { + QUnit.test('buttons with "confirm" attribute save before calling the method', async function (assert) { assert.expect(9); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5920,36 +5955,36 @@ QUnit.module('Views', { intercepts: { execute_action: function (event) { assert.step('execute_action'); + event.data.on_success(); }, }, }); // click on button, and cancel in confirm dialog - testUtils.dom.click(form.$('.o_statusbar_buttons button')); + await testUtils.dom.click(form.$('.o_statusbar_buttons button')); assert.ok(form.$('.o_statusbar_buttons button').prop('disabled'), 'button should be disabled'); - testUtils.dom.click($('.modal-footer button.btn-secondary')); + await testUtils.dom.click($('.modal-footer button.btn-secondary')); assert.ok(!form.$('.o_statusbar_buttons button').prop('disabled'), 'button should no longer be disabled'); assert.verifySteps(['default_get']); // click on button, and click on ok in confirm dialog - testUtils.dom.click(form.$('.o_statusbar_buttons button')); - assert.verifySteps(['default_get']); - testUtils.dom.click($('.modal-footer button.btn-primary')); - - assert.verifySteps(['default_get', 'create', 'read', 'execute_action']); + await testUtils.dom.click(form.$('.o_statusbar_buttons button')); + assert.verifySteps([]); + await testUtils.dom.click($('.modal-footer button.btn-primary')); + assert.verifySteps(['create', 'read', 'execute_action']); form.destroy(); }); - QUnit.test('buttons are disabled until action is resolved (in dialogs)', function (assert) { + QUnit.test('buttons are disabled until action is resolved (in dialogs)', async function (assert) { assert.expect(3); - var def = $.Deferred(); + var def = testUtils.makeTestPromise(); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -5982,7 +6017,7 @@ QUnit.module('Views', { }, mockRPC: function (route, args) { if (args.method === 'get_formview_id') { - return $.when(false); + return Promise.resolve(false); } return this._super.apply(this, arguments); }, @@ -5991,30 +6026,30 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(form.$('.o_external_button')); + await testUtils.dom.click(form.$('.o_external_button')); assert.notOk($('.modal .oe_button_box button').attr('disabled'), "stat buttons should be enabled"); - testUtils.dom.click($('.modal .oe_button_box button')); + await testUtils.dom.click($('.modal .oe_button_box button')); assert.ok($('.modal .oe_button_box button').attr('disabled'), "stat buttons should be disabled"); def.resolve(); - + await testUtils.nextTick(); assert.notOk($('.modal .oe_button_box button').attr('disabled'), "stat buttons should be enabled"); form.destroy(); }); - QUnit.test('multiple clicks on save should reload only once', function (assert) { + QUnit.test('multiple clicks on save should reload only once', async function (assert) { assert.expect(4); - var def = $.Deferred(); + var def = testUtils.makeTestPromise(); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6039,13 +6074,13 @@ QUnit.module('Views', { }, }); - testUtils.form.clickEdit(form); - testUtils.fields.editInput(form.$('input[name="foo"]'), "test"); - testUtils.form.clickSave(form); - testUtils.form.clickSave(form); + await testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name="foo"]'), "test"); + await testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); def.resolve(); - + await testUtils.nextTick(); assert.verifySteps([ 'read', // initial read to render the view 'write', // write on save @@ -6055,10 +6090,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('form view is not broken if save operation fails', function (assert) { + QUnit.test('form view is not broken if save operation fails', async function (assert) { assert.expect(5); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6073,19 +6108,19 @@ QUnit.module('Views', { mockRPC: function (route, args) { assert.step(args.method); if (args.method === 'write' && args.args[1].foo === 'incorrect value') { - return $.Deferred().reject(); + return Promise.reject(); } return this._super.apply(this, arguments); }, }); - testUtils.form.clickEdit(form); - testUtils.fields.editInput(form.$('input[name="foo"]'), "incorrect value"); - testUtils.form.clickSave(form); + await testUtils.form.clickEdit(form); + await testUtils.fields.editInput(form.$('input[name="foo"]'), "incorrect value"); + await testUtils.form.clickSave(form); - testUtils.fields.editInput(form.$('input[name="foo"]'), "correct value"); + await testUtils.fields.editInput(form.$('input[name="foo"]'), "correct value"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.verifySteps([ 'read', // initial read to render the view @@ -6097,10 +6132,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('support password attribute', function (assert) { + QUnit.test('support password attribute', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6112,7 +6147,7 @@ QUnit.module('Views', { assert.strictEqual(form.$('span[name="foo"]').text(), '***', "password should be displayed with stars"); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.strictEqual(form.$('input[name="foo"]').val(), 'yop', "input value should be the password"); assert.strictEqual(form.$('input[name="foo"]').prop('type'), 'password', @@ -6120,10 +6155,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('support autocomplete attribute', function (assert) { + QUnit.test('support autocomplete attribute', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6133,16 +6168,16 @@ QUnit.module('Views', { res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.hasAttrValue(form.$('input[name="display_name"]'), 'autocomplete', 'coucou', "attribute autocomplete should be set"); form.destroy(); }); - QUnit.test('context is correctly passed after save & new in FormViewDialog', function (assert) { + QUnit.test('context is correctly passed after save & new in FormViewDialog', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', res_id: 4, @@ -6174,27 +6209,30 @@ QUnit.module('Views', { return this._super.apply(this, arguments); }, }); - - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.nextTick(); assert.strictEqual($('.modal').length, 1, "One FormViewDialog should be opened"); // set a value on the m2o - testUtils.fields.many2one.clickOpenDropdown('partner_type_id'); - testUtils.fields.many2one.clickHighlightedItem('partner_type_id'); + await testUtils.fields.many2one.clickOpenDropdown('partner_type_id'); + await testUtils.fields.many2one.clickHighlightedItem('partner_type_id'); - testUtils.dom.click($('.modal-footer button:eq(1)')); - testUtils.dom.click($('.modal .o_field_many2one input')); - testUtils.dom.click($('.modal-footer button:first')); + await testUtils.dom.click($('.modal-footer button:eq(1)')); + await testUtils.nextTick(); + await testUtils.dom.click($('.modal .o_field_many2one input')); + await testUtils.fields.many2one.clickHighlightedItem('partner_type_id'); + await testUtils.dom.click($('.modal-footer button:first')); + await testUtils.nextTick(); form.destroy(); }); - QUnit.test('render domain field widget without model', function (assert) { + QUnit.test('render domain field widget without model', async function (assert) { assert.expect(3); this.data.partner.fields.model_name = { string: "Model name", type: "char" }; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6209,11 +6247,11 @@ QUnit.module('Views', { assert.strictEqual(args.model, 'test', "should search_count on test"); if (!args.kwargs.domain) { - return $.Deferred().reject({ + return Promise.reject({message:{ code: 200, data: {}, message: "MockServer._getRecords: given domain has to be an array.", - }, $.Event()); + }, event: $.Event()}); } } return this._super.apply(this, arguments); @@ -6222,13 +6260,13 @@ QUnit.module('Views', { assert.strictEqual(form.$('.o_field_widget[name="display_name"]').text(), "Select a model to add a filter.", "should contain an error message saying the model is missing"); - testUtils.fields.editInput(form.$('input[name="model_name"]'), "test"); + await testUtils.fields.editInput(form.$('input[name="model_name"]'), "test"); assert.notStrictEqual(form.$('.o_field_widget[name="display_name"]').text(), "Select a model to add a filter.", "should not contain an error message anymore"); form.destroy(); }); - QUnit.test('readonly fields are not sent when saving', function (assert) { + QUnit.test('readonly fields are not sent when saving', async function (assert) { assert.expect(6); // define an onchange on display_name to check that the value of readonly @@ -6239,7 +6277,7 @@ QUnit.module('Views', { }; var checkOnchange = false; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6274,31 +6312,33 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.nextTick(); assert.strictEqual($('.modal input.o_field_widget[name=foo]').length, 1, - 'foo should be editable'); + 'foo should be editable'); checkOnchange = true; - testUtils.fields.editInput($('.modal .o_field_widget[name=foo]'), 'foo value'); - testUtils.fields.editInput($('.modal .o_field_widget[name=display_name]'), 'readonly'); + await testUtils.fields.editInput($('.modal .o_field_widget[name=foo]'), 'foo value'); + await testUtils.fields.editInput($('.modal .o_field_widget[name=display_name]'), 'readonly'); assert.strictEqual($('.modal span.o_field_widget[name=foo]').length, 1, - 'foo should be readonly'); - testUtils.dom.clickFirst($('.modal-footer .btn-primary')); + 'foo should be readonly'); + await testUtils.dom.clickFirst($('.modal-footer .btn-primary')); + await testUtils.nextTick(); checkOnchange = false; - testUtils.dom.click(form.$('.o_data_row')); + await testUtils.dom.click(form.$('.o_data_row')); assert.strictEqual($('.modal .o_field_widget[name=foo]').text(), 'foo value', - "the edited value should have been kept"); - testUtils.dom.clickFirst($('.modal-footer .btn-primary')); - - testUtils.form.clickSave(form); // save the record + "the edited value should have been kept"); + await testUtils.dom.clickFirst($('.modal-footer .btn-primary')); + await testUtils.nextTick(); + await testUtils.form.clickSave(form); // save the record form.destroy(); }); - QUnit.test('id is False in evalContext for new records', function (assert) { + QUnit.test('id is False in evalContext for new records', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6311,8 +6351,8 @@ QUnit.module('Views', { assert.hasClass(form.$('.o_field_widget[name=foo]'),'o_readonly_modifier', "foo should be readonly in 'Create' mode"); - testUtils.form.clickSave(form); - testUtils.form.clickEdit(form); + await testUtils.form.clickSave(form); + await testUtils.form.clickEdit(form); assert.doesNotHaveClass(form.$('.o_field_widget[name=foo]'), 'o_readonly_modifier', "foo should not be readonly anymore"); @@ -6320,11 +6360,11 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('delete a duplicated record', function (assert) { + QUnit.test('delete a duplicated record', async function (assert) { assert.expect(5); var newRecordID; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6350,20 +6390,20 @@ QUnit.module('Views', { }); // duplicate record 1 - testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); - testUtils.dom.click(form.sidebar.$('a:contains(Duplicate)')); + await testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + await testUtils.dom.click(form.sidebar.$('a:contains(Duplicate)')); assert.containsOnce(form, '.o_form_editable', "form should be in edit mode"); assert.strictEqual(form.$('.o_field_widget').val(), 'first record (copy)', "duplicated record should have correct name"); - testUtils.form.clickSave(form); // save duplicated record + await testUtils.form.clickSave(form); // save duplicated record // delete duplicated record - testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); - testUtils.dom.click(form.sidebar.$('a:contains(Delete)')); + await testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + await testUtils.dom.click(form.sidebar.$('a:contains(Delete)')); assert.strictEqual($('.modal').length, 1, "should have opened a confirm dialog"); - testUtils.dom.click($('.modal-footer .btn-primary')); + await testUtils.dom.click($('.modal-footer .btn-primary')); assert.strictEqual(form.$('.o_field_widget').text(), 'first record', "should have come back to previous record"); @@ -6371,13 +6411,13 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('display tooltips for buttons', function (assert) { + QUnit.test('display tooltips for buttons', async function (assert) { assert.expect(2); var initialDebugMode = config.debug; config.debug = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6409,7 +6449,7 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('reload event is handled only once', function (assert) { + QUnit.test('reload event is handled only once', async function (assert) { // In this test, several form controllers are nested (two of them are // opened in dialogs). When the users clicks on save in the last // opened dialog, a 'reload' event is triggered up to reload the (direct) @@ -6422,7 +6462,7 @@ QUnit.module('Views', { '<field name="display_name"/>' + '<field name="trululu"/>' + '</form>'; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6434,7 +6474,7 @@ QUnit.module('Views', { mockRPC: function (route, args) { assert.step(args.method); if (args.method === 'get_formview_id') { - return $.when(false); + return Promise.resolve(false); } return this._super.apply(this, arguments); }, @@ -6443,11 +6483,11 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(form.$('.o_external_button')); - testUtils.dom.click($('.modal .o_external_button')); + await testUtils.dom.click(form.$('.o_external_button')); + await testUtils.dom.click($('.modal .o_external_button')); - testUtils.fields.editInput($('.modal:nth(1) .o_field_widget[name=display_name]'), 'new name'); - testUtils.dom.click($('.modal:nth(1) footer .btn-primary').first()); + await testUtils.fields.editInput($('.modal:nth(1) .o_field_widget[name=display_name]'), 'new name'); + await testUtils.dom.click($('.modal:nth(1) footer .btn-primary').first()); assert.strictEqual($('.modal .o_field_widget[name=trululu] input').val(), 'new name', "record should have been reloaded"); @@ -6466,12 +6506,12 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('process the context for inline subview', function (assert) { + QUnit.test('process the context for inline subview', async function (assert) { assert.expect(1); this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6493,12 +6533,12 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('process the context for subview not inline', function (assert) { + QUnit.test('process the context for subview not inline', async function (assert) { assert.expect(1); this.data.partner.records[0].p = [2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6521,12 +6561,12 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('can toggle column in x2many in sub form view', function (assert) { + QUnit.test('can toggle column in x2many in sub form view', async function (assert) { assert.expect(2); this.data.partner.records[2].p = [1,2]; this.data.partner.fields.foo.sortable = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6536,7 +6576,7 @@ QUnit.module('Views', { res_id: 1, mockRPC: function (route, args) { if (route === '/web/dataset/call_kw/partner/get_formview_id') { - return $.when(false); + return Promise.resolve(false); } return this._super.apply(this, arguments); }, @@ -6551,20 +6591,20 @@ QUnit.module('Views', { }, viewOptions: {mode: 'edit'}, }); - testUtils.dom.click(form.$('.o_external_button')); + await testUtils.dom.click(form.$('.o_external_button')); assert.strictEqual($('.modal-body .o_form_view .o_list_view .o_data_cell').text(), "yopblip", "table has some initial order"); - testUtils.dom.click($('.modal-body .o_form_view .o_list_view th')); + await testUtils.dom.click($('.modal-body .o_form_view .o_list_view th')); assert.strictEqual($('.modal-body .o_form_view .o_list_view .o_data_cell').text(), "blipyop", "table is now sorted"); form.destroy(); }); - QUnit.test('rainbowman attributes correctly passed on button click', function (assert) { + QUnit.test('rainbowman attributes correctly passed on button click', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6578,14 +6618,14 @@ QUnit.module('Views', { var effectDescription = pyUtils.py_eval(event.data.action_data.effect); assert.deepEqual(effectDescription, {message: 'Congrats!'}, "should have correct effect description"); } - } + }, }); - testUtils.dom.click(form.$('.o_form_statusbar .btn-secondary')); + await testUtils.dom.click(form.$('.o_form_statusbar .btn-secondary')); form.destroy(); }); - QUnit.test('basic support for widgets', function (assert) { + QUnit.test('basic support for widgets', async function (assert) { assert.expect(1); var MyWidget = Widget.extend({ @@ -6598,7 +6638,7 @@ QUnit.module('Views', { }); widgetRegistry.add('test', MyWidget); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6616,10 +6656,10 @@ QUnit.module('Views', { delete widgetRegistry.map.test; }); - QUnit.test('support header button as widgets on form statusbar', function (assert) { + QUnit.test('support header button as widgets on form statusbar', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6638,7 +6678,7 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('basic support for widgets', function (assert) { + QUnit.test('basic support for widgets', async function (assert) { assert.expect(1); var MyWidget = Widget.extend({ @@ -6654,7 +6694,7 @@ QUnit.module('Views', { }); widgetRegistry.add('test', MyWidget); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6664,7 +6704,7 @@ QUnit.module('Views', { '</form>', }); - testUtils.fields.editInput(form.$('input[name="foo"]'), "I am alive"); + await testUtils.fields.editInput(form.$('input[name="foo"]'), "I am alive"); assert.strictEqual(form.$('.o_widget').text(), 'I am alive!', "widget should have been updated"); @@ -6673,10 +6713,10 @@ QUnit.module('Views', { }); - QUnit.test('bounce edit button in readonly mode', function (assert) { + QUnit.test('bounce edit button in readonly mode', async function (assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6694,24 +6734,24 @@ QUnit.module('Views', { }); // in readonly - testUtils.dom.click(form.$('[name="display_name"]')); + await testUtils.dom.click(form.$('[name="display_name"]')); assert.verifySteps(['bounce']); // in edit - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('[name="display_name"]')); - assert.verifySteps(['bounce']); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('[name="display_name"]')); + assert.verifySteps([]); form.destroy(); }); - QUnit.test('proper stringification in debug mode tooltip', function (assert) { + QUnit.test('proper stringification in debug mode tooltip', async function (assert) { assert.expect(4); var initialDebugMode = config.debug; config.debug = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6740,14 +6780,14 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('autoresize of text fields is done when switching to edit mode', function (assert) { + QUnit.test('autoresize of text fields is done when switching to edit mode', async function (assert) { assert.expect(4); this.data.partner.fields.text_field = { string: 'Text field', type: 'text' }; this.data.partner.fields.text_field.default = "some\n\nmulti\n\nline\n\ntext\n"; this.data.partner.records[0].text_field = "a\nb\nc\nd\ne\nf"; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6761,7 +6801,7 @@ QUnit.module('Views', { }); // switch to edit mode to ensure that autoresize is correctly done - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); var height = form.$('.o_field_widget[name=text_field]').height(); // focus the field to manually trigger autoresize form.$('.o_field_widget[name=text_field]').trigger('focus'); @@ -6772,8 +6812,8 @@ QUnit.module('Views', { assert.ok(height > 80, "textarea should have an height of at least 80px"); // save and create a new record to ensure that autoresize is correctly done - testUtils.form.clickSave(form); - testUtils.form.clickCreate(form); + await testUtils.form.clickSave(form); + await testUtils.form.clickCreate(form); height = form.$('.o_field_widget[name=text_field]').height(); // focus the field to manually trigger autoresize form.$('.o_field_widget[name=text_field]').trigger('focus'); @@ -6784,11 +6824,11 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('check if the view destroys all widgets and instances', function (assert) { + QUnit.test('check if the view destroys all widgets and instances', async function (assert) { assert.expect(1); var instanceNumber = 0; - testUtils.mock.patch(mixins.ParentedMixin, { + await testUtils.mock.patch(mixins.ParentedMixin, { init: function () { instanceNumber++; return this._super.apply(this, arguments); @@ -6844,13 +6884,13 @@ QUnit.module('Views', { res_id: 1, }; - var form = createView(params); + var form = await createView(params); form.destroy(); var initialInstanceNumber = instanceNumber; instanceNumber = 0; - form = createView(params); + form = await createView(params); // call destroy function of controller to ensure that it correctly destroys everything form.__destroy(); @@ -6861,13 +6901,13 @@ QUnit.module('Views', { form.destroy(); - testUtils.mock.unpatch(mixins.ParentedMixin); + await testUtils.mock.unpatch(mixins.ParentedMixin); }); - QUnit.test('do not change pager when discarding current record', function (assert) { + QUnit.test('do not change pager when discarding current record', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6884,8 +6924,8 @@ QUnit.module('Views', { assert.strictEqual(form.pager.$('.o_pager_counter').text().trim(), '2 / 2', 'pager should indicate that we are on second record'); - testUtils.form.clickEdit(form); - testUtils.form.clickDiscard(form); + await testUtils.form.clickEdit(form); + await testUtils.form.clickDiscard(form); assert.strictEqual(form.pager.$('.o_pager_counter').text().trim(), '2 / 2', 'pager should not have changed'); @@ -6893,14 +6933,14 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('edition in form view on a "noCache" model', function (assert) { - assert.expect(4); + QUnit.test('edition in form view on a "noCache" model', async function (assert) { + assert.expect(5); - testUtils.mock.patch(BasicModel, { + await testUtils.mock.patch(BasicModel, { noCacheModels: BasicModel.prototype.noCacheModels.concat(['partner']), }); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6922,23 +6962,25 @@ QUnit.module('Views', { }); core.bus.on('clear_cache', form, assert.step.bind(assert, 'clear_cache')); - testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'new value'); - testUtils.form.clickSave(form); + await testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'new value'); + await testUtils.form.clickSave(form); assert.verifySteps(['write', 'clear_cache']); form.destroy(); - testUtils.mock.unpatch(BasicModel); + await testUtils.mock.unpatch(BasicModel); + + assert.verifySteps(['clear_cache']); // triggered by the test environment on destroy }); - QUnit.test('creation in form view on a "noCache" model', function (assert) { - assert.expect(4); + QUnit.test('creation in form view on a "noCache" model', async function (assert) { + assert.expect(5); - testUtils.mock.patch(BasicModel, { + await testUtils.mock.patch(BasicModel, { noCacheModels: BasicModel.prototype.noCacheModels.concat(['partner']), }); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6956,23 +6998,25 @@ QUnit.module('Views', { }); core.bus.on('clear_cache', form, assert.step.bind(assert, 'clear_cache')); - testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'value'); - testUtils.form.clickSave(form); + await testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'value'); + await testUtils.form.clickSave(form); assert.verifySteps(['create', 'clear_cache']); form.destroy(); - testUtils.mock.unpatch(BasicModel); + await testUtils.mock.unpatch(BasicModel); + + assert.verifySteps(['clear_cache']); // triggered by the test environment on destroy }); - QUnit.test('deletion in form view on a "noCache" model', function (assert) { - assert.expect(4); + QUnit.test('deletion in form view on a "noCache" model', async function (assert) { + assert.expect(5); - testUtils.mock.patch(BasicModel, { + await testUtils.mock.patch(BasicModel, { noCacheModels: BasicModel.prototype.noCacheModels.concat(['partner']), }); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -6994,17 +7038,19 @@ QUnit.module('Views', { }); core.bus.on('clear_cache', form, assert.step.bind(assert, 'clear_cache')); - testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); - testUtils.dom.click(form.sidebar.$('a:contains(Delete)')); - testUtils.dom.click($('.modal-footer .btn-primary')); + await testUtils.dom.click(form.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + await testUtils.dom.click(form.sidebar.$('a:contains(Delete)')); + await testUtils.dom.click($('.modal-footer .btn-primary')); assert.verifySteps(['unlink', 'clear_cache']); form.destroy(); - testUtils.mock.unpatch(BasicModel); + await testUtils.mock.unpatch(BasicModel); + + assert.verifySteps(['clear_cache']); // triggered by the test environment on destroy }); - QUnit.test('reload currencies when writing on records of model res.currency', function (assert) { + QUnit.test('reload currencies when writing on records of model res.currency', async function (assert) { assert.expect(5); this.data['res.currency'] = { @@ -7012,7 +7058,7 @@ QUnit.module('Views', { records: [{id: 1, display_name: "some currency"}], }; - var form = createView({ + var form = await createView({ View: FormView, model: 'res.currency', data: this.data, @@ -7032,8 +7078,8 @@ QUnit.module('Views', { }, }); - testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'new value'); - testUtils.form.clickSave(form); + await testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'new value'); + await testUtils.form.clickSave(form); assert.verifySteps([ 'read', @@ -7045,8 +7091,7 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('a popup window should automatically close after a do_action event', function (assert) { - + QUnit.test('a popup window should automatically close after a do_action event', async function (assert) { // Having clicked on a one2many in a form view and clicked on a many2one // field in the resulting popup window that popup window should automatically close. @@ -7055,7 +7100,7 @@ QUnit.module('Views', { this.data.partner.records[0].product_ids = [37]; this.data.product.records[0].partner_type_id = 12; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7068,7 +7113,7 @@ QUnit.module('Views', { res_id: 1, mockRPC: function (route, args) { if (args.method === 'get_formview_action' && args.model === 'partner_type') { - return $.when(); + return Promise.resolve(); } return this._super(route, args); }, @@ -7079,16 +7124,16 @@ QUnit.module('Views', { }, }); // Open one2many - testUtils.dom.click(form.$('.o_data_row')); + await testUtils.dom.click(form.$('.o_data_row')); assert.strictEqual($('.modal-content').length, 1, "a popup window should have opened"); // Click on many2one and trigger do_action - testUtils.dom.click($('.modal-content a[name="partner_type_id"]')); + await testUtils.dom.click($('.modal-content a[name="partner_type_id"]')); assert.strictEqual($('.modal-content').length, 0, "the popup window should have closed"); form.destroy(); }); - QUnit.test('all popup windows should automatically close after a do_action event', function (assert) { + QUnit.test('all popup windows should automatically close after a do_action event', async function (assert) { // Having clicked successively on two different one2many in form views // and clicked on a many2one in the last popup window all popup @@ -7100,7 +7145,7 @@ QUnit.module('Views', { this.data.partner.records[1].product_ids = [37]; this.data.product.records[0].partner_type_id = 12; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7118,7 +7163,7 @@ QUnit.module('Views', { res_id: 1, mockRPC: function (route, args) { if (args.method === 'get_formview_action' && args.model === 'partner_type') { - return $.when(); + return Promise.resolve(); } return this._super(route, args); }, @@ -7129,21 +7174,21 @@ QUnit.module('Views', { }, }); // Open two one2manys - testUtils.dom.click(form.$('.o_data_row')); - testUtils.dom.click($('.modal-content .o_data_row')); + await testUtils.dom.click(form.$('.o_data_row')); + await testUtils.dom.click($('.modal-content .o_data_row')); assert.strictEqual($('.modal-content').length, 2, "Two popup windows should have opened."); // Click on many2one and trigger do_action - testUtils.dom.click($('.modal-content a[name="partner_type_id"]')); + await testUtils.dom.click($('.modal-content a[name="partner_type_id"]')); assert.strictEqual($('.modal-content').length, 0, "All popup windows should have closed."); form.destroy(); }); - QUnit.test('keep editing after call_button fail', function (assert) { + QUnit.test('keep editing after call_button fail', async function (assert) { assert.expect(4); var values; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7175,21 +7220,20 @@ QUnit.module('Views', { }); // add a row and partially fill it - form.$('.o_field_x2many_list_row_add a').click(); - form.$('input[name=display_name]').val('abc').trigger('input'); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('input[name=display_name]'), 'abc'); // click button which will trigger_up 'execute_action' (this will save) values = { display_name: 'abc', product_id: false, }; - form.$('button.p').click(); - + await testUtils.dom.click(form.$('button.p')); // edit the new row again and set a many2one value - form.$('.o_form_view .o_field_one2many .o_data_row .o_data_cell').click(); - var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); - form.$('.o_field_many2one input').click(); - $dropdown.find('li:first()').click(); + await testUtils.dom.clickLast(form.$('.o_form_view .o_field_one2many .o_data_row .o_data_cell')); + await testUtils.nextTick(); + await testUtils.fields.many2one.clickOpenDropdown('product_id'); + await testUtils.fields.many2one.clickHighlightedItem('product_id'); assert.strictEqual(form.$('.o_field_many2one input').val(), 'xphone', "value of the m2o should have been correctly updated"); @@ -7197,15 +7241,15 @@ QUnit.module('Views', { values = { product_id: 37, }; - form.$buttons.find('.o_form_button_save').click(); + await testUtils.form.clickSave(form); form.destroy(); }); - QUnit.test('asynchronous rendering of a widget tag', function (assert) { + QUnit.test('asynchronous rendering of a widget tag', async function (assert) { assert.expect(1); - var def1 = $.Deferred(); + var def1 = testUtils.makeTestPromise(); var MyWidget = Widget.extend({ willStart: function() { @@ -7215,7 +7259,7 @@ QUnit.module('Views', { widgetRegistry.add('test', MyWidget); - createAsyncView({ + createView({ View: FormView, model: 'partner', data: this.data, @@ -7230,9 +7274,10 @@ QUnit.module('Views', { }); def1.resolve(); + await testUtils.nextTick(); }); - QUnit.test('no deadlock when saving with uncommitted changes', function (assert) { + QUnit.test('no deadlock when saving with uncommitted changes', async function (assert) { // Before saving a record, all field widgets are asked to commit their changes (new values // that they wouldn't have sent to the model yet). This test is added alongside a bug fix // ensuring that we don't end up in a deadlock when a widget actually has some changes to @@ -7245,7 +7290,7 @@ QUnit.module('Views', { // on 'Save'. assert.expect(6); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7254,16 +7299,18 @@ QUnit.module('Views', { assert.step(args.method); return this._super.apply(this, arguments); }, - // we set a fieldDebounce to precisely mock the behavior of the webclient: changes are - // not sent to the model at keystrokes, but when the input is left + // we set a fieldDebounce to precisely mock the behavior of the webclient: changes are + // not sent to the model at keystrokes, but when the input is left fieldDebounce: 5000, }); - testUtils.fields.editInput(form.$('input[name=foo]'), 'some foo value'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'some foo value'); // manually save the record, to prevent the field widget to notify the model of its new // value before being requested to form.saveRecord(); + await testUtils.nextTick(); + assert.containsOnce(form, '.o_form_readonly', "form view should be in readonly"); assert.strictEqual(form.$('.o_form_view').text().trim(), 'some foo value', "foo field should have correct value"); @@ -7272,7 +7319,7 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('save record with onchange on one2many with required field', function (assert) { + QUnit.test('save record with onchange on one2many with required field', async function (assert) { // in this test, we have a one2many with a required field, whose value is // set by an onchange on another field ; we manually set the value of that // first field, and directly click on Save (before the onchange RPC returns @@ -7287,7 +7334,7 @@ QUnit.module('Views', { }; var onchangeDef; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7302,7 +7349,7 @@ QUnit.module('Views', { mockRPC: function (route, args) { var result = this._super.apply(this, arguments); if (args.method === 'onchange') { - return $.when(onchangeDef).then(_.constant(result)); + return Promise.resolve(onchangeDef).then(_.constant(result)); } if (args.method === 'create') { assert.step('create'); @@ -7313,28 +7360,29 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); assert.strictEqual(form.$('.o_field_widget[name=display_name]').val(), '', "display_name should be the empty string by default"); assert.strictEqual(form.$('.o_field_widget[name=foo]').val(), '', "foo should be the empty string by default"); - onchangeDef = $.Deferred(); // delay the onchange + onchangeDef = testUtils.makeTestPromise(); // delay the onchange - testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'some value'); + await testUtils.fields.editInput(form.$('.o_field_widget[name=display_name]'), 'some value'); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.step('resolve'); onchangeDef.resolve(); + await testUtils.nextTick(); assert.verifySteps(['resolve', 'create']); form.destroy(); }); - QUnit.test('call canBeRemoved while saving', function (assert) { + QUnit.test('call canBeRemoved while saving', async function (assert) { assert.expect(10); this.data.partner.onchanges = { @@ -7344,8 +7392,8 @@ QUnit.module('Views', { }; var onchangeDef; - var createDef = $.Deferred(); - var form = createView({ + var createDef = testUtils.makeTestPromise(); + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7353,41 +7401,43 @@ QUnit.module('Views', { mockRPC: function (route, args) { var result = this._super.apply(this, arguments); if (args.method === 'onchange') { - return $.when(onchangeDef).then(_.constant(result)); + return Promise.resolve(onchangeDef).then(_.constant(result)); } if (args.method === 'create') { - return $.when(createDef).then(_.constant(result)); + return Promise.resolve(createDef).then(_.constant(result)); } return result; }, }); // edit foo to trigger a delayed onchange - onchangeDef = $.Deferred(); - testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), 'trigger onchange'); + onchangeDef = testUtils.makeTestPromise(); + await testUtils.fields.editInput(form.$('.o_field_widget[name=foo]'), 'trigger onchange'); assert.strictEqual(form.$('.o_field_widget[name=display_name]').val(), 'default'); // save (will wait for the onchange to return), and will be delayed as well - testUtils.dom.click(form.$buttons.find('.o_form_button_save')); + await testUtils.dom.click(form.$buttons.find('.o_form_button_save')); assert.hasClass(form.$('.o_form_view'), 'o_form_editable'); assert.strictEqual(form.$('.o_field_widget[name=display_name]').val(), 'default'); // simulate a click on the breadcrumbs to leave the form view form.canBeRemoved(); + await testUtils.nextTick(); assert.hasClass(form.$('.o_form_view'), 'o_form_editable'); assert.strictEqual(form.$('.o_field_widget[name=display_name]').val(), 'default'); // unlock the onchange onchangeDef.resolve(); - + await testUtils.nextTick(); assert.hasClass(form.$('.o_form_view'), 'o_form_editable'); assert.strictEqual(form.$('.o_field_widget[name=display_name]').val(), 'changed'); // unlock the create createDef.resolve(); + await testUtils.nextTick(); assert.hasClass(form.$('.o_form_view'), 'o_form_readonly'); assert.strictEqual(form.$('.o_field_widget[name=display_name]').text(), 'changed'); @@ -7397,7 +7447,7 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('domain returned by onchange is cleared on discard', function (assert) { + QUnit.test('domain returned by onchange is cleared on discard', async function (assert) { assert.expect(4); this.data.partner.onchanges = { @@ -7406,7 +7456,7 @@ QUnit.module('Views', { var domain = ['id', '=', 1]; var expectedDomain = domain; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7414,7 +7464,7 @@ QUnit.module('Views', { mockRPC: function (route, args) { if (args.method === 'onchange' && args.args[0][0] === 1) { // onchange returns a domain only on record 1 - return $.when({ + return Promise.resolve({ domain: { trululu: domain, }, @@ -7435,30 +7485,30 @@ QUnit.module('Views', { assert.strictEqual(form.$('input[name=foo]').val(), 'yop', "should be on record 1"); // change foo to trigger the onchange - testUtils.fields.editInput(form.$('input[name=foo]'), 'new value'); + await testUtils.fields.editInput(form.$('input[name=foo]'), 'new value'); // open many2one dropdown to check if the domain is applied - testUtils.fields.many2one.clickOpenDropdown('trululu'); + await testUtils.fields.many2one.clickOpenDropdown('trululu'); // switch to another record (should ask to discard changes, and reset the domain) - testUtils.dom.click(form.pager.$('.o_pager_next')); + await testUtils.dom.click(form.pager.$('.o_pager_next')); // discard changes by clicking on confirm in the dialog - testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); + await testUtils.dom.click($('.modal .modal-footer .btn-primary:first')); assert.strictEqual(form.$('input[name=foo]').val(), 'blip', "should be on record 2"); // open many2one dropdown to check if the domain is applied expectedDomain = []; - testUtils.fields.many2one.clickOpenDropdown('trululu'); + await testUtils.fields.many2one.clickOpenDropdown('trululu'); form.destroy(); }); - QUnit.test('discard after a failed save', function (assert) { + QUnit.test('discard after a failed save', async function (assert) { assert.expect(2); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ data: this.data, archs: { 'partner,false,form': '<form>' + @@ -7472,13 +7522,13 @@ QUnit.module('Views', { actions: this.actions, }); - actionManager.doAction(1); + await actionManager.doAction(1); - testUtils.dom.click('.o_control_panel .o-kanban-button-new'); + await testUtils.dom.click('.o_control_panel .o-kanban-button-new'); //cannot save because there is a required field - testUtils.dom.click('.o_control_panel .o_form_button_save'); - testUtils.dom.click('.o_control_panel .o_form_button_cancel'); + await testUtils.dom.click('.o_control_panel .o_form_button_save'); + await testUtils.dom.click('.o_control_panel .o_form_button_cancel'); assert.containsNone(actionManager, '.o_form_view'); assert.containsOnce(actionManager, '.o_kanban_view'); @@ -7486,10 +7536,10 @@ QUnit.module('Views', { actionManager.destroy(); }); - QUnit.test("one2many create record dialog shouldn't have a 'remove' button", function (assert) { + QUnit.test("one2many create record dialog shouldn't have a 'remove' button", async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7510,8 +7560,8 @@ QUnit.module('Views', { res_id: 1, }); - testUtils.form.clickCreate(form); - form.$('.o-kanban-button-new').click(); + await testUtils.form.clickCreate(form); + await testUtils.dom.click(form.$('.o-kanban-button-new')); assert.containsOnce(document.body, '.modal'); assert.strictEqual($('.modal .modal-footer .o_btn_remove').length, 0, @@ -7522,10 +7572,10 @@ QUnit.module('Views', { QUnit.module('FormViewTABMainButtons'); - QUnit.test('using tab in an empty required string field should not move to the next field',function(assert) { + QUnit.test('using tab in an empty required string field should not move to the next field', async function(assert) { assert.expect(3); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7539,7 +7589,7 @@ QUnit.module('Views', { '</form>', }); - testUtils.dom.click(form.$('input[name=display_name]')); + await testUtils.dom.click(form.$('input[name=display_name]')); assert.strictEqual(form.$('input[name="display_name"]')[0], document.activeElement, "display_name should be focused"); form.$('input[name="display_name"]').trigger($.Event('keydown', {which: $.ui.keyCode.TAB})); @@ -7550,10 +7600,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('using tab in an empty required date field should not move to the next field',function(assert) { + QUnit.test('using tab in an empty required date field should not move to the next field', async function(assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7567,7 +7617,7 @@ QUnit.module('Views', { '</form>', }); - testUtils.dom.click(form.$('input[name=date]')); + await testUtils.dom.click(form.$('input[name=date]')); assert.strictEqual(form.$('input[name="date"]')[0], document.activeElement, "display_name should be focused"); form.$('input[name="date"]').trigger($.Event('keydown', {which: $.ui.keyCode.TAB})); @@ -7577,10 +7627,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('Edit button get the focus when pressing TAB from form', function (assert) { + QUnit.test('Edit button get the focus when pressing TAB from form', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7593,7 +7643,7 @@ QUnit.module('Views', { }); // in edit - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); form.$('input[name="display_name"]').focus().trigger($.Event('keydown', {which: $.ui.keyCode.TAB})); assert.strictEqual(form.$buttons.find('.btn-primary:visible')[0], document.activeElement, "the first primary button (save) should be focused"); @@ -7602,9 +7652,9 @@ QUnit.module('Views', { QUnit.module('FormViewTABFormButtons'); - QUnit.test('In Edition mode, after navigating to the last field, the default button when pressing TAB is SAVE', function (assert) { + QUnit.test('In Edition mode, after navigating to the last field, the default button when pressing TAB is SAVE', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7634,10 +7684,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('In READ mode, the default button with focus is the first primary button of the form', function (assert) { + QUnit.test('In READ mode, the default button with focus is the first primary button of the form', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7662,10 +7712,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('In READ mode, the default button when pressing TAB is EDIT when there is no primary button on the form', function (assert) { + QUnit.test('In READ mode, the default button when pressing TAB is EDIT when there is no primary button on the form', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7690,10 +7740,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('In Edition mode, when an attribute is dynamically required (and not required), TAB should navigate to the next field', function (assert) { + QUnit.test('In Edition mode, when an attribute is dynamically required (and not required), TAB should navigate to the next field', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7718,10 +7768,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('In Edition mode, when an attribute is dynamically required, TAB should stop on the field if it is required', function (assert) { + QUnit.test('In Edition mode, when an attribute is dynamically required, TAB should stop on the field if it is required', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7739,7 +7789,7 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(form.$('div[name="bar"]>input')); + await testUtils.dom.click(form.$('div[name="bar"]>input')); form.$('input[name="foo"]').focus(); $(document.activeElement).trigger($.Event('keydown', {which: $.ui.keyCode.TAB})); @@ -7747,10 +7797,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('display tooltips for save and discard buttons', function (assert) { + QUnit.test('display tooltips for save and discard buttons', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7762,12 +7812,13 @@ QUnit.module('Views', { form.$buttons.find('.o_form_buttons_edit').tooltip('show',false); assert.strictEqual($('.tooltip .oe_tooltip_string').length, 1, "should have rendered a tooltip"); + await testUtils.nextTick(); form.destroy(); }); - QUnit.test('if the focus is on the save button, hitting ENTER should save', function (assert) { + QUnit.test('if the focus is on the save button, hitting ENTER should save', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7786,14 +7837,15 @@ QUnit.module('Views', { }); form.$buttons.find('.o_form_button_save') - .focus() - .trigger($.Event('keydown', {which: $.ui.keyCode.ENTER})); + .focus() + .trigger($.Event('keydown', {which: $.ui.keyCode.ENTER})); + await testUtils.nextTick(); form.destroy(); }); - QUnit.test('if the focus is on the discard button, hitting ENTER should save', function (assert) { + QUnit.test('if the focus is on the discard button, hitting ENTER should save', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7812,14 +7864,15 @@ QUnit.module('Views', { }); form.$buttons.find('.o_form_button_cancel') - .focus() - .trigger($.Event('keydown', {which: $.ui.keyCode.ENTER})); + .focus() + .trigger($.Event('keydown', {which: $.ui.keyCode.ENTER})); + await testUtils.nextTick(); form.destroy(); }); - QUnit.test('if the focus is on the save button, hitting ESCAPE should discard', function (assert) { + QUnit.test('if the focus is on the save button, hitting ESCAPE should discard', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7838,14 +7891,15 @@ QUnit.module('Views', { }); form.$buttons.find('.o_form_button_save') - .focus() - .trigger($.Event('keydown', {which: $.ui.keyCode.ESCAPE})); + .focus() + .trigger($.Event('keydown', {which: $.ui.keyCode.ESCAPE})); + await testUtils.nextTick(); form.destroy(); }); - QUnit.test('if the focus is on the discard button, hitting ESCAPE should discard', function (assert) { + QUnit.test('if the focus is on the discard button, hitting ESCAPE should discard', async function (assert) { assert.expect(1); - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -7864,18 +7918,19 @@ QUnit.module('Views', { }); form.$buttons.find('.o_form_button_cancel') - .focus() - .trigger($.Event('keydown', {which: $.ui.keyCode.ESCAPE})); + .focus() + .trigger($.Event('keydown', {which: $.ui.keyCode.ESCAPE})); + await testUtils.nextTick(); form.destroy(); }); - QUnit.test('if the focus is on the save button, hitting TAB should not move to the next button', function (assert) { + QUnit.test('if the focus is on the save button, hitting TAB should not move to the next button', async function (assert) { assert.expect(1); /* this test has only one purpose: to say that it is normal that the focus stays within a button primary even after the TAB key has been pressed. It is not possible here to execute the default action of the TAB on a button : https://stackoverflow.com/questions/32428993/why-doesnt-simulating-a-tab-keypress-move-focus-to-the-next-input-field so writing a test that will always succeed is not useful. - */ + */ assert.ok("Behavior can't be tested"); }); }); diff --git a/addons/web/static/tests/views/graph_tests.js b/addons/web/static/tests/views/graph_tests.js index d9cec90bc4e..cae561e4889 100644 --- a/addons/web/static/tests/views/graph_tests.js +++ b/addons/web/static/tests/views/graph_tests.js @@ -59,10 +59,10 @@ QUnit.module('Views', { QUnit.module('GraphView'); - QUnit.test('simple graph rendering', function (assert) { + QUnit.test('simple graph rendering', async function (assert) { assert.expect(4); - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -92,10 +92,10 @@ QUnit.module('Views', { }); }); - QUnit.test('default type attribute', function (assert) { + QUnit.test('default type attribute', async function (assert) { assert.expect(1); - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -107,10 +107,10 @@ QUnit.module('Views', { graph.destroy(); }); - QUnit.test('title attribute', function (assert) { + QUnit.test('title attribute', async function (assert) { assert.expect(1); - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -123,10 +123,10 @@ QUnit.module('Views', { graph.destroy(); }); - QUnit.test('switching mode', function (assert) { + QUnit.test('switching mode', async function (assert) { assert.expect(6); - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -139,7 +139,7 @@ QUnit.module('Views', { 'bar type button should not be active'); assert.hasClass(graph.$buttons.find('button[data-mode="line"]'),'active', 'line type button should be active'); - testUtils.dom.click(graph.$buttons.find('button[data-mode="bar"]')); + await testUtils.dom.click(graph.$buttons.find('button[data-mode="bar"]')); assert.strictEqual(graph.renderer.state.mode, "bar", "should be in bar chart mode by default"); assert.doesNotHaveClass(graph.$buttons.find('button[data-mode="line"]'), 'active', 'line type button should not be active'); @@ -148,13 +148,13 @@ QUnit.module('Views', { graph.destroy(); }); - QUnit.test('displaying line chart with only 1 data point', function (assert) { + QUnit.test('displaying line chart with only 1 data point', async function (assert) { assert.expect(1); // this test makes sure the line chart does not crash when only one data // point is displayed. var done = assert.async(); this.data.foo.records = this.data.foo.records.slice(0,1); - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -169,12 +169,12 @@ QUnit.module('Views', { }); }); - QUnit.test('displaying line chart data with multiple groupbys', function (assert) { + QUnit.test('displaying line chart data with multiple groupbys', async function (assert) { // this test makes sure the line chart shows all data labels (X axis) when // it is grouped by several fields assert.expect(3); - var graph = createView({ + var graph = await createView({ View: GraphView, model: 'foo', data: this.data, @@ -192,10 +192,10 @@ QUnit.module('Views', { graph.destroy(); }); - QUnit.test('displaying bar chart data with multiple groupbys', function (assert) { + QUnit.test('displaying bar chart data with multiple groupbys', async function (assert) { assert.expect(3); - var graph = createView({ + var graph = await createView({ View: GraphView, model: 'foo', data: this.data, @@ -213,13 +213,13 @@ QUnit.module('Views', { graph.destroy(); }); - QUnit.test('switching measures', function (assert) { + QUnit.test('switching measures', async function (assert) { var done = assert.async(); assert.expect(4); var rpcCount = 0; - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -249,11 +249,11 @@ QUnit.module('Views', { }); }); - QUnit.test('no content helper (bar chart)', function (assert) { + QUnit.test('no content helper (bar chart)', async function (assert) { assert.expect(2); this.data.foo.records = []; - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -268,11 +268,11 @@ QUnit.module('Views', { graph.destroy(); }); - QUnit.test('no content helper (pie chart)', function (assert) { + QUnit.test('no content helper (pie chart)', async function (assert) { assert.expect(2); this.data.foo.records = [] - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -287,10 +287,10 @@ QUnit.module('Views', { graph.destroy(); }); - QUnit.test('render pie chart in comparison mode', function (assert) { + QUnit.test('render pie chart in comparison mode', async function (assert) { assert.expect(2); - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -316,11 +316,10 @@ QUnit.module('Views', { graph.destroy(); }); - QUnit.test('no content helper after update', function (assert) { - var done = assert.async(); + QUnit.test('no content helper after update', async function (assert) { assert.expect(4); - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -328,27 +327,26 @@ QUnit.module('Views', { '<field name="product_id"/>' + '</graph>', }); - return concurrency.delay(0).then(function () { - assert.ok(graph.$('div.o_graph_svg_container svg.nvd3-svg').length, - "should contain a div with a svg element"); - assert.notOk(graph.$('div.o_view_nocontent').length, - "should not display the no content helper"); - testUtils.graph.reload(graph, {domain: [['product_id', '=', 4]]}); - - assert.notOk(graph.$('div.o_graph_svg_container svg.nvd3-svg').length, - "should not contain a div with a svg element"); - assert.ok(graph.$('div.o_view_nocontent').length, - "should display the no content helper"); - graph.destroy(); - done(); - }); + + await testUtils.nextTick(); + assert.ok(graph.$('div.o_graph_svg_container svg.nvd3-svg').length, + "should contain a div with a svg element"); + assert.notOk(graph.$('div.o_view_nocontent').length, + "should not display the no content helper"); + testUtils.graph.reload(graph, {domain: [['product_id', '=', 4]]}); + await testUtils.nextTick(); + assert.notOk(graph.$('div.o_graph_svg_container svg.nvd3-svg').length, + "should not contain a div with a svg element"); + assert.ok(graph.$('div.o_view_nocontent').length, + "should display the no content helper"); + graph.destroy(); }); - QUnit.test('can reload with other group by', function (assert) { + QUnit.test('can reload with other group by', async function (assert) { var done = assert.async(); assert.expect(4); - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -375,11 +373,11 @@ QUnit.module('Views', { }); }); - QUnit.test('getOwnedQueryParams correctly returns mode, measure, groupbys and interval mapping', function (assert) { + QUnit.test('getOwnedQueryParams correctly returns mode, measure, groupbys and interval mapping', async function (assert) { var done = assert.async(); assert.expect(4); - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -442,14 +440,14 @@ QUnit.module('Views', { }); }); - QUnit.test('correctly uses graph_ keys from the context', function (assert) { + QUnit.test('correctly uses graph_ keys from the context', async function (assert) { var done = assert.async(); assert.expect(6); var lastOne = _.last(this.data.foo.records); lastOne.color_id = 14; - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -485,14 +483,14 @@ QUnit.module('Views', { }); }); - QUnit.test('correctly use group_by key from the context', function (assert) { + QUnit.test('correctly use group_by key from the context', async function (assert) { var done = assert.async(); assert.expect(2); var lastOne = _.last(this.data.foo.records); lastOne.color_id = 14; - var graph = createView({ + var graph = await createView({ View: GraphView, model: 'foo', data: this.data, @@ -515,14 +513,14 @@ QUnit.module('Views', { }); }); - QUnit.test('correctly uses graph_ keys from the context (at reload)', function (assert) { + QUnit.test('correctly uses graph_ keys from the context (at reload)', async function (assert) { var done = assert.async(); assert.expect(8); var lastOne = _.last(this.data.foo.records); lastOne.color_id = 14; - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -564,10 +562,10 @@ QUnit.module('Views', { }); }); - QUnit.test('reload graph with correct fields', function (assert) { + QUnit.test('reload graph with correct fields', async function (assert) { assert.expect(2); - var graph = createView({ + var graph = await createView({ View: GraphView, model: 'foo', data: this.data, @@ -584,16 +582,16 @@ QUnit.module('Views', { }, }); - testUtils.graph.reload(graph, {groupBy: []}); + await testUtils.graph.reload(graph, {groupBy: []}); graph.destroy(); }); - QUnit.test('initial groupby is kept when reloading', function (assert) { + QUnit.test('initial groupby is kept when reloading', async function (assert) { var done = assert.async(); assert.expect(4); - var graph = createView({ + var graph = await createView({ View: GraphView, model: 'foo', data: this.data, @@ -626,10 +624,10 @@ QUnit.module('Views', { }); }); - QUnit.test('use a many2one as a measure should work (without groupBy)', function (assert) { + QUnit.test('use a many2one as a measure should work (without groupBy)', async function (assert) { assert.expect(3); - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -651,10 +649,10 @@ QUnit.module('Views', { }); }); - QUnit.test('use a many2one as a measure should work (with groupBy)', function (assert) { + QUnit.test('use a many2one as a measure should work (with groupBy)', async function (assert) { assert.expect(4); - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -679,10 +677,10 @@ QUnit.module('Views', { }); }); - QUnit.test('use a many2one as a measure and as a groupby should work', function (assert) { + QUnit.test('use a many2one as a measure and as a groupby should work', async function (assert) { assert.expect(2); - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -693,27 +691,24 @@ QUnit.module('Views', { additionalMeasures: ['product_id'], }, }); - var done = assert.async(); - return concurrency.delay(0).then(function () { - // need to set the measure this way because it cannot be set in the - // arch. - testUtils.dom.click(graph.$buttons.find('.dropdown-toggle:contains(Measures)')); - testUtils.dom.click(graph.$buttons.find('.dropdown-item[data-field="product_id"]')); + await testUtils.nextTick(); + // need to set the measure this way because it cannot be set in the + // arch. + await testUtils.dom.click(graph.$buttons.find('.dropdown-toggle:contains(Measures)')); + await testUtils.dom.click(graph.$buttons.find('.dropdown-item[data-field="product_id"]')); - assert.strictEqual(graph.model.chart.data[0].value, 1, - "should have first datapoint with value 1"); - assert.strictEqual(graph.model.chart.data[1].value, 1, - "should have second datapoint with value 1"); + assert.strictEqual(graph.model.chart.data[0].value, 1, + "should have first datapoint with value 1"); + assert.strictEqual(graph.model.chart.data[1].value, 1, + "should have second datapoint with value 1"); - graph.destroy(); - done(); - }); + graph.destroy(); }); - QUnit.test('not use a many2one as a measure by default', function (assert) { + QUnit.test('not use a many2one as a measure by default', async function (assert) { assert.expect(1); - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -730,10 +725,10 @@ QUnit.module('Views', { }); }); - QUnit.test('use a many2one as a measure if set as additional fields', function (assert) { + QUnit.test('use a many2one as a measure if set as additional fields', async function (assert) { assert.expect(1); - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -753,10 +748,10 @@ QUnit.module('Views', { }); }); - QUnit.test('graph view crash when moving from search view using Down key', function (assert) { + QUnit.test('graph view crash when moving from search view using Down key', async function (assert) { assert.expect(1); - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: this.data, @@ -769,13 +764,13 @@ QUnit.module('Views', { graph.destroy(); }); - QUnit.test('graph measures should be alphabetically sorted', function (assert) { + QUnit.test('graph measures should be alphabetically sorted', async function (assert) { assert.expect(2); var data = this.data; data.foo.fields.bouh = {string: "bouh", type: "integer"}; - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", data: data, @@ -793,10 +788,10 @@ QUnit.module('Views', { graph.destroy(); }); - QUnit.test('Undefined should appear in bar, pie graph but not in line graph', function (assert) { + QUnit.test('Undefined should appear in bar, pie graph but not in line graph', async function (assert) { assert.expect(4); - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", groupBy:['date'], @@ -809,17 +804,17 @@ QUnit.module('Views', { assert.strictEqual(graph.$("svg.nvd3-svg .nv-x:contains('Undefined')").length, 0); assert.strictEqual(graph.$("svg.nvd3-svg:contains('January')").length, 1); - testUtils.dom.click(graph.$buttons.find('.o_graph_button[data-mode=bar]')); + await testUtils.dom.click(graph.$buttons.find('.o_graph_button[data-mode=bar]')); assert.strictEqual(graph.$("svg.nvd3-svg .nv-x:contains('Undefined')").length, 1); assert.strictEqual(graph.$("svg.nvd3-svg:contains('January')").length, 1); graph.destroy(); }); - QUnit.test('Undefined should appear in bar, pie graph but not in line graph with multiple groupbys', function (assert) { + QUnit.test('Undefined should appear in bar, pie graph but not in line graph with multiple groupbys', async function (assert) { assert.expect(6); - var graph = createView({ + var graph = await createView({ View: GraphView, model: "foo", groupBy:['date', 'color_id'], @@ -832,12 +827,12 @@ QUnit.module('Views', { assert.strictEqual(graph.$("svg.nvd3-svg .nv-x:contains('Undefined')").length, 0); assert.strictEqual(graph.$("svg.nvd3-svg:contains('January')").length, 1); - graph.$buttons.find('.o_graph_button[data-mode=bar]').click(); + await testUtils.dom.click(graph.$buttons.find('.o_graph_button[data-mode=bar]')); assert.strictEqual(graph.$("svg.nvd3-svg .nv-x:contains('Undefined')").length, 1); assert.strictEqual(graph.$("svg.nvd3-svg:contains('January')").length, 1); // Undefined should not appear after switching back to line chart - graph.$buttons.find('.o_graph_button[data-mode=line]').click(); + await testUtils.dom.click(graph.$buttons.find('.o_graph_button[data-mode=line]')); assert.strictEqual(graph.$("svg.nvd3-svg .nv-x:contains('Undefined')").length, 0); assert.strictEqual(graph.$("svg.nvd3-svg:contains('January')").length, 1); graph.destroy(); diff --git a/addons/web/static/tests/views/kanban_mobile_tests.js b/addons/web/static/tests/views/kanban_mobile_tests.js index cc8354432e3..52bfa60a180 100644 --- a/addons/web/static/tests/views/kanban_mobile_tests.js +++ b/addons/web/static/tests/views/kanban_mobile_tests.js @@ -55,10 +55,10 @@ QUnit.module('Views', { QUnit.module('KanbanView Mobile'); - QUnit.test('mobile grouped rendering', function (assert) { + QUnit.test('mobile grouped rendering', async function (assert) { assert.expect(13); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -83,13 +83,13 @@ QUnit.module('Views', { "there is no records in next tab. Records will be loaded when it will be opened"); // quick create in first column - testUtils.dom.click(kanban.$buttons.find('.o-kanban-button-new')); - assert.hasClass(kanban.$('.o_kanban_group:nth(0) > div:nth(1)'), 'o_kanban_quick_create', + await testUtils.dom.click(kanban.$buttons.find('.o-kanban-button-new')); + assert.hasClass(kanban.$('.o_kanban_group:nth(0) > div:nth(1)'),'o_kanban_quick_create', "clicking on create should open the quick_create in the first column"); // move to second column - kanban.$('.o_kanban_mobile_tab:nth(1)').trigger('click'); - assert.hasClass(kanban.$('.o_kanban_mobile_tab:nth(1)'), 'o_current', + await testUtils.dom.click(kanban.$('.o_kanban_mobile_tab:nth(1)')); + assert.hasClass(kanban.$('.o_kanban_mobile_tab:nth(1)'),'o_current', "second tab is now active with class 'o_current'"); assert.hasClass(kanban.$('.o_kanban_group:nth(1)'),'o_current', "second column is now active with class 'o_current'"); @@ -97,8 +97,8 @@ QUnit.module('Views', { "the 2 records of the second group have now been loaded"); // quick create in second column - testUtils.dom.click(kanban.$buttons.find('.o-kanban-button-new')); - assert.hasClass(kanban.$('.o_kanban_group:nth(1) > div:nth(1)'), 'o_kanban_quick_create', + await testUtils.dom.click(kanban.$buttons.find('.o-kanban-button-new')); + assert.hasClass(kanban.$('.o_kanban_group:nth(1) > div:nth(1)'),'o_kanban_quick_create', "clicking on create should open the quick_create in the second column"); // kanban column should match kanban mobile tabs @@ -118,10 +118,11 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('mobile grouped with undefined column', function (assert) { + + QUnit.test('mobile grouped with undefined column', async function (assert) { assert.expect(5); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -155,10 +156,11 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('mobile grouped on many2one rendering', function (assert) { + + QUnit.test('mobile grouped on many2one rendering', async function (assert) { assert.expect(5); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -193,10 +195,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('kanban with searchpanel: there should be no searchpanel in mobile', function (assert) { + QUnit.test('kanban with searchpanel: there should be no searchpanel in mobile', async function (assert) { assert.expect(3); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, diff --git a/addons/web/static/tests/views/kanban_model_tests.js b/addons/web/static/tests/views/kanban_model_tests.js index 20eb2918d04..29dd60c3d15 100644 --- a/addons/web/static/tests/views/kanban_model_tests.js +++ b/addons/web/static/tests/views/kanban_model_tests.js @@ -57,12 +57,13 @@ QUnit.module('Views', { openGroupByDefault: true, viewType: 'kanban', }; - } + }, }, function () { QUnit.module('KanbanModel'); - QUnit.test('load grouped + add a new group', function (assert) { + QUnit.test('load grouped + add a new group', async function (assert) { + var done = assert.async(); assert.expect(22); var calledRoutes = {}; @@ -84,7 +85,7 @@ QUnit.module('Views', { fieldNames: ['foo'], }); - model.load(params).then(function (resultID) { + model.load(params).then(async function (resultID) { // various checks on the load result var state = model.get(resultID); assert.ok(_.isEqual(state.groupedBy, ['product_id']), 'should be grouped by "product_id"'); @@ -102,7 +103,7 @@ QUnit.module('Views', { assert.strictEqual(xphoneGroup.limit, 40, 'limit in a group should be 40'); // add a new group - model.createGroup('xpod', resultID); + await model.createGroup('xpod', resultID); state = model.get(resultID); assert.strictEqual(state.data.length, 3, 'should now have 3 groups'); assert.strictEqual(state.count, 2, 'there are still 2 records'); @@ -123,10 +124,12 @@ QUnit.module('Views', { assert.strictEqual(nbSearchRead, 2, 'should have done 2 search_read'); assert.strictEqual(nbNameCreate, 1, 'should have done 1 name_create'); model.destroy(); + done(); }); }); - QUnit.test('archive/restore a column', function (assert) { + QUnit.test('archive/restore a column', async function (assert) { + var done = assert.async(); assert.expect(4); var model = createModel({ @@ -139,7 +142,7 @@ QUnit.module('Views', { fieldNames: ['foo'], }); - model.load(params).then(function (resultID) { + model.load(params).then(async function (resultID) { var state = model.get(resultID); var xphoneGroup = _.findWhere(state.data, {res_id: 37}); var xpadGroup = _.findWhere(state.data, {res_id: 41}); @@ -148,17 +151,19 @@ QUnit.module('Views', { // archive the column 'xphone' var recordIDs = _.pluck(xphoneGroup.data, 'id'); - model.toggleActive(recordIDs, false, xphoneGroup.id); + await model.toggleActive(recordIDs, false, xphoneGroup.id); state = model.get(resultID); xphoneGroup = _.findWhere(state.data, {res_id: 37}); assert.strictEqual(xphoneGroup.count, 0, 'xphone group has no record anymore'); xpadGroup = _.findWhere(state.data, {res_id: 41}); assert.strictEqual(xpadGroup.count, 1, 'xpad group still has one record'); model.destroy(); + done(); }); }); - QUnit.test('kanban model does not allow nested groups', function (assert) { + QUnit.test('kanban model does not allow nested groups', async function (assert) { + var done = assert.async(); assert.expect(2); var model = createModel({ @@ -185,10 +190,11 @@ QUnit.module('Views', { "the second level of groupBy should have been removed"); model.destroy(); + done(); }); }); - QUnit.test('resequence columns and records', function (assert) { + QUnit.test('resequence columns and records', async function (assert) { var done = assert.async(); assert.expect(8); @@ -252,7 +258,8 @@ QUnit.module('Views', { }); }); - QUnit.test('add record to group', function (assert) { + QUnit.test('add record to group', async function (assert) { + var done = assert.async(); assert.expect(8); var self = this; @@ -289,9 +296,11 @@ QUnit.module('Views', { assert.strictEqual(state.data[0].data[0].data.foo, 'new record', "new record should have been fetched"); }); - }); + }).then(function() { + model.destroy(); + done(); + }) - model.destroy(); }); }); diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js index bb2231a025d..956578f2bf8 100644 --- a/addons/web/static/tests/views/kanban_tests.js +++ b/addons/web/static/tests/views/kanban_tests.js @@ -11,6 +11,8 @@ var testUtils = require('web.test_utils'); var Widget = require('web.Widget'); var widgetRegistry = require('web.widget_registry'); +var makeTestPromise = testUtils.makeTestPromise; +var nextTick = testUtils.nextTick; var createView = testUtils.createView; QUnit.module('Views', { @@ -69,10 +71,10 @@ QUnit.module('Views', { QUnit.module('KanbanView'); - QUnit.test('basic ungrouped rendering', function (assert) { + QUnit.test('basic ungrouped rendering', async function (assert) { assert.expect(5); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -92,10 +94,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('basic grouped rendering', function (assert) { + QUnit.test('basic grouped rendering', async function (assert) { assert.expect(13); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -131,20 +133,21 @@ QUnit.module('Views', { // the next line makes sure that reload works properly. It looks useless, // but it actually test that a grouped local record can be reloaded without // changing its result. - testUtils.kanban.reload(kanban); + await kanban.reload(kanban); assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 3); kanban.destroy(); }); - QUnit.test('basic grouped rendering with active field (archivable by default)', function (assert) { + QUnit.test('basic grouped rendering with active field (archivable by default)', async function (assert) { + // var done = assert.async(); assert.expect(9); // add active field on partner model and make all records active this.data.partner.fields.active = {string: 'Active', type: 'char', default: true}; var envIDs = [1, 2, 3, 4]; // the ids that should be in the environment during this test - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -166,28 +169,28 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_group:last .o_kanban_record', 3); testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); - testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_archive_records')); + await testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_archive_records')); assert.containsOnce(document.body, '.modal', "a confirm modal should be displayed"); - testUtils.modal.clickButton('Cancel'); + await testUtils.modal.clickButton('Cancel'); assert.containsN(kanban, '.o_kanban_group:last .o_kanban_record', 3, "still last column should contain 3 records"); testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); - testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_archive_records')); + await testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_archive_records')); assert.ok($('.modal').length, 'a confirm modal should be displayed'); - testUtils.modal.clickButton('Ok'); + await testUtils.modal.clickButton('Ok'); assert.containsNone(kanban, '.o_kanban_group:last .o_kanban_record', "last column should not contain any records"); envIDs = [4]; assert.deepEqual(kanban.exportState().resIds, envIDs); kanban.destroy(); }); - QUnit.test('basic grouped rendering with active field and archive enabled (archivable true)', function (assert) { + QUnit.test('basic grouped rendering with active field and archive enabled (archivable true)', async function (assert) { assert.expect(7); // add active field on partner model and make all records active this.data.partner.fields.active = {string: 'Active', type: 'char', default: true}; var envIDs = [1, 2, 3, 4]; // the ids that should be in the environment during this test - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -209,26 +212,26 @@ QUnit.module('Views', { "last column should contain 3 records"); envIDs = [4]; testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); - testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_archive_records')); + await testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_archive_records')); assert.ok($('.modal').length, 'a confirm modal should be displayed'); - testUtils.modal.clickButton('Cancel'); // Click on 'Cancel' + await testUtils.modal.clickButton('Cancel'); // Click on 'Cancel' assert.containsN(kanban, '.o_kanban_group:last .o_kanban_record', 3, "still last column should contain 3 records"); testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); - testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_archive_records')); + await testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_archive_records')); assert.ok($('.modal').length, 'a confirm modal should be displayed'); - testUtils.modal.clickButton('Ok'); // Click on 'Ok' + await testUtils.modal.clickButton('Ok'); // Click on 'Ok' assert.containsNone(kanban, '.o_kanban_group:last .o_kanban_record', "last column should not contain any records"); kanban.destroy(); }); - QUnit.test('basic grouped rendering with active field and hidden archive buttons (archivable false)', function (assert) { + QUnit.test('basic grouped rendering with active field and hidden archive buttons (archivable false)', async function (assert) { assert.expect(2); // add active field on partner model and make all records active this.data.partner.fields.active = {string: 'Active', type: 'char', default: true}; var envIDs = [1, 2, 3, 4]; // the ids that should be in the environment during this test - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -251,10 +254,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('context can be used in kanban template', function (assert) { + QUnit.test('context can be used in kanban template', async function (assert) { assert.expect(2); - var form = createView({ + var form = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -281,10 +284,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('pager should be hidden in grouped mode', function (assert) { + QUnit.test('pager should be hidden in grouped mode', async function (assert) { assert.expect(1); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -295,17 +298,17 @@ QUnit.module('Views', { '</t></templates></kanban>', groupBy: ['bar'], }); - kanban.renderPager(); + await kanban.renderPager(); assert.isNotVisible(kanban.pager.$el, "pager should be hidden in grouped kanban"); kanban.destroy(); }); - QUnit.test('pager, ungrouped, with default limit', function (assert) { + QUnit.test('pager, ungrouped, with default limit', async function (assert) { assert.expect(3); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -325,10 +328,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('pager, ungrouped, with limit given in options', function (assert) { + QUnit.test('pager, ungrouped, with limit given in options', async function (assert) { assert.expect(3); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -350,11 +353,11 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('pager, ungrouped, with limit set on arch and given in options', function (assert) { + QUnit.test('pager, ungrouped, with limit set on arch and given in options', async function (assert) { assert.expect(3); // the limit given in the arch should take the priority over the one given in options - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -376,10 +379,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('create in grouped on m2o', function (assert) { + QUnit.test('create in grouped on m2o', async function (assert) { assert.expect(5); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -399,7 +402,7 @@ QUnit.module('Views', { assert.hasClass(kanban.$('.o_kanban_view > div:last'),'o_column_quick_create', "column quick create should be enabled when grouped by a many2one field)"); - testUtils.kanban.clickCreate(kanban); // Click on 'Create' + await testUtils.kanban.clickCreate(kanban); // Click on 'Create' assert.hasClass(kanban.$('.o_kanban_group:first() > div:nth(1)'),'o_kanban_quick_create', "clicking on create should open the quick_create in the first column"); @@ -408,10 +411,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('create in grouped on char', function (assert) { + QUnit.test('create in grouped on char', async function (assert) { assert.expect(4); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -433,10 +436,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record without quick_create_view', function (assert) { + QUnit.test('quick create record without quick_create_view', async function (assert) { assert.expect(16); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -460,7 +463,7 @@ QUnit.module('Views', { "first column should contain one record"); // click on 'Create' -> should open the quick create in the first column - testUtils.kanban.clickCreate(kanban); + await testUtils.kanban.clickCreate(kanban); var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create'); assert.strictEqual($quickCreate.length, 1, @@ -469,14 +472,14 @@ QUnit.module('Views', { "should have rendered an XXS form view"); assert.strictEqual($quickCreate.find('input').length, 1, "should have only one input"); - assert.hasClass($quickCreate.find('input'),'o_required_modifier', + assert.hasClass($quickCreate.find('input'), 'o_required_modifier', "the field should be required"); assert.strictEqual($quickCreate.find('input[placeholder=Title]').length, 1, "input placeholder should be 'Title'"); // fill the quick create and validate - testUtils.fields.editInput($quickCreate.find('input'), 'new partner'); - testUtils.dom.click($quickCreate.find('button.o_kanban_add')); + await testUtils.fields.editInput($quickCreate.find('input'), 'new partner'); + await testUtils.dom.click($quickCreate.find('button.o_kanban_add')); assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column should contain two records"); @@ -494,10 +497,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record with quick_create_view', function (assert) { + QUnit.test('quick create record with quick_create_view', async function (assert) { assert.expect(19); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -532,7 +535,7 @@ QUnit.module('Views', { "first column should contain one record"); // click on 'Create' -> should open the quick create in the first column - testUtils.kanban.clickCreate(kanban); + await testUtils.kanban.clickCreate(kanban); var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create'); assert.strictEqual($quickCreate.length, 1, @@ -546,10 +549,10 @@ QUnit.module('Views', { "should have rendered three widgets"); // fill the quick create and validate - testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=foo]'), 'new partner'); - testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=int_field]'), '4'); - testUtils.dom.click($quickCreate.find('.o_field_widget[name=state] .o_priority_star:first')); - testUtils.dom.click($quickCreate.find('button.o_kanban_add')); + await testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=foo]'), 'new partner'); + await testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=int_field]'), '4'); + await testUtils.dom.click($quickCreate.find('.o_field_widget[name=state] .o_priority_star:first')); + await testUtils.dom.click($quickCreate.find('button.o_kanban_add')); assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column should contain two records"); @@ -569,10 +572,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record in grouped on m2o (no quick_create_view)', function (assert) { + QUnit.test('quick create record in grouped on m2o (no quick_create_view)', async function (assert) { assert.expect(12); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -604,10 +607,10 @@ QUnit.module('Views', { "first column should contain two records"); // click on 'Create', fill the quick create and validate - testUtils.kanban.clickCreate(kanban); + await testUtils.kanban.clickCreate(kanban); var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create'); - testUtils.fields.editInput($quickCreate.find('input'), 'new partner'); - testUtils.dom.click($quickCreate.find('button.o_kanban_add')); + await testUtils.fields.editInput($quickCreate.find('input'), 'new partner'); + await testUtils.dom.click($quickCreate.find('button.o_kanban_add')); assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 3, "first column should contain three records"); @@ -625,10 +628,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record in grouped on m2o (with quick_create_view)', function (assert) { + QUnit.test('quick create record in grouped on m2o (with quick_create_view)', async function (assert) { assert.expect(14); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -669,12 +672,12 @@ QUnit.module('Views', { "first column should contain two records"); // click on 'Create', fill the quick create and validate - testUtils.kanban.clickCreate(kanban); + await testUtils.kanban.clickCreate(kanban); var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create'); - testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=foo]'), 'new partner'); - testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=int_field]'), '4'); - testUtils.dom.click($quickCreate.find('.o_field_widget[name=state] .o_priority_star:first')); - testUtils.dom.click($quickCreate.find('button.o_kanban_add')); + await testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=foo]'), 'new partner'); + await testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=int_field]'), '4'); + await testUtils.dom.click($quickCreate.find('.o_field_widget[name=state] .o_priority_star:first')); + await testUtils.dom.click($quickCreate.find('button.o_kanban_add')); assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 3, "first column should contain three records"); @@ -694,7 +697,7 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record with default values and onchanges', function (assert) { + QUnit.test('quick create record with default values and onchanges', async function (assert) { assert.expect(11); this.data.partner.fields.int_field.default = 4; @@ -706,7 +709,7 @@ QUnit.module('Views', { }, }; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -729,16 +732,17 @@ QUnit.module('Views', { }); // click on 'Create' -> should open the quick create in the first column - testUtils.kanban.clickCreate(kanban); + await testUtils.kanban.clickCreate(kanban); var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create'); + var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create'); assert.strictEqual($quickCreate.length, 1, "should have a quick create element in the first column"); assert.strictEqual($quickCreate.find('.o_field_widget[name=int_field]').val(), '4', "default value should be set"); // fill the 'foo' field -> should trigger the onchange - testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=foo]'), 'new partner'); + await testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=foo]'), 'new partner'); assert.strictEqual($quickCreate.find('.o_field_widget[name=int_field]').val(), '8', "onchange should have been triggered"); @@ -756,10 +760,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record with quick_create_view: modifiers', function (assert) { + QUnit.test('quick create record with quick_create_view: modifiers', async function (assert) { assert.expect(3); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -778,7 +782,7 @@ QUnit.module('Views', { }); // create a new record - testUtils.dom.click(kanban.$('.o_kanban_group:first .o_kanban_quick_add')); + await testUtils.dom.click(kanban.$('.o_kanban_group:first .o_kanban_quick_add')); var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create'); assert.hasClass($quickCreate.find('.o_field_widget[name=foo]'),'o_required_modifier', @@ -787,7 +791,7 @@ QUnit.module('Views', { "int_field should be invisible"); // fill 'foo' field - testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=foo]'), 'new partner'); + await testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=foo]'), 'new partner'); assert.doesNotHaveClass($quickCreate.find('.o_field_widget[name=int_field]'), 'o_invisible_modifier', "int_field should now be visible"); @@ -795,7 +799,7 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record and change state in grouped mode', function (assert) { + QUnit.test('quick create record and change state in grouped mode', async function (assert) { assert.expect(1); this.data.partner.fields.kanban_state = { @@ -804,7 +808,7 @@ QUnit.module('Views', { selection: [["normal", "Grey"], ["done", "Green"], ["blocked", "Red"]], }; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -820,24 +824,24 @@ QUnit.module('Views', { }); // Quick create kanban record - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); var $quickAdd = kanban.$('.o_kanban_quick_create'); $quickAdd.find('.o_input').val('Test'); - testUtils.dom.click($quickAdd.find('.o_kanban_add')); + await testUtils.dom.click($quickAdd.find('.o_kanban_add')); // Select state in kanban - testUtils.dom.click(kanban.$('.o_status').first()); - testUtils.dom.click(kanban.$('.o_selection .dropdown-item:first')); + await testUtils.dom.click(kanban.$('.o_status').first()); + await testUtils.dom.click(kanban.$('.o_selection .dropdown-item:first')); assert.hasClass(kanban.$('.o_status').first(),'o_status_green', "Kanban state should be done (Green)"); kanban.destroy(); }); - QUnit.test('quick create record: cancel and validate without using the buttons', function (assert) { + QUnit.test('quick create record: cancel and validate without using the buttons', async function (assert) { assert.expect(8); var nbRecords = 4; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -852,7 +856,7 @@ QUnit.module('Views', { assert.strictEqual(kanban.exportState().resIds.length, nbRecords); // click to add an element and cancel the quick creation by pressing ESC - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); var $quickCreate = kanban.$('.o_kanban_quick_create'); assert.strictEqual($quickCreate.length, 1, "should have a quick create element"); @@ -865,18 +869,18 @@ QUnit.module('Views', { "should have destroyed the quick create element"); // click to add and element and click outside, should cancel the quick creation - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); - testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first')); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + await testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first')); assert.containsNone(kanban, '.o_kanban_quick_create', "the quick create should be destroyed when the user clicks outside"); // click to really add an element - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); $quickCreate = kanban.$('.o_kanban_quick_create'); - testUtils.fields.editInput($quickCreate.find('input'), 'new partner'); + await testUtils.fields.editInput($quickCreate.find('input'), 'new partner'); // clicking outside should no longer destroy the quick create as it is dirty - testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first')); + await testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first')); assert.containsOnce(kanban, '.o_kanban_quick_create', "the quick create should not have been destroyed"); @@ -887,6 +891,7 @@ QUnit.module('Views', { which: $.ui.keyCode.ENTER, })); + await nextTick(); assert.strictEqual(this.data.partner.records.length, 5, "should have created a partner"); assert.strictEqual(_.last(this.data.partner.records).name, "new partner", @@ -896,7 +901,7 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record: validate with ENTER', function (assert) { + QUnit.test('quick create record: validate with ENTER', async function (assert) { // in this test, we accurately mock the behavior of the webclient by specifying a // fieldDebounce > 0, meaning that the changes in an InputField aren't notified to the model // on 'input' events, but they wait for the 'change' event (or a call to 'commitChanges', @@ -906,7 +911,7 @@ QUnit.module('Views', { // directly assert.expect(3); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -929,8 +934,8 @@ QUnit.module('Views', { "should have 4 records at the beginning"); // add an element and confirm by pressing ENTER - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); - testUtils.kanban.quickCreate(kanban, 'new partner', 'foo'); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + await testUtils.kanban.quickCreate(kanban, 'new partner', 'foo'); // triggers a navigation event, leading to the 'commitChanges' and record creation assert.containsN(kanban, '.o_kanban_record', 5, @@ -941,11 +946,11 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record: prevent multiple adds with ENTER', function (assert) { + QUnit.test('quick create record: prevent multiple adds with ENTER', async function (assert) { assert.expect(9); - var def; - var kanban = createView({ + var prom = makeTestPromise(); + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -969,7 +974,9 @@ QUnit.module('Views', { var result = this._super.apply(this, arguments); if (args.method === 'create') { assert.step('create'); - return $.when(def).then(_.constant(result)); + return prom.then(function () { + return result; + }); } return result; }, @@ -979,8 +986,7 @@ QUnit.module('Views', { "should have 4 records at the beginning"); // add an element and press ENTER twice - def = $.Deferred(); - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); var enterEvent = { keyCode: $.ui.keyCode.ENTER, which: $.ui.keyCode.ENTER, @@ -995,10 +1001,11 @@ QUnit.module('Views', { "should not have created the record yet"); assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), 'new partner', "quick create should not be empty yet"); - assert.hasClass(kanban.$('.o_kanban_quick_create'),'o_disabled', + assert.hasClass(kanban.$('.o_kanban_quick_create'), 'o_disabled', "quick create should be disabled"); - def.resolve(); + prom.resolve(); + await nextTick(); assert.containsN(kanban, '.o_kanban_record', 5, "should have created a new record"); @@ -1012,11 +1019,11 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record: prevent multiple adds with Add clicked', function (assert) { + QUnit.test('quick create record: prevent multiple adds with Add clicked', async function (assert) { assert.expect(9); - var def; - var kanban = createView({ + var prom = makeTestPromise(); + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1036,7 +1043,9 @@ QUnit.module('Views', { var result = this._super.apply(this, arguments); if (args.method === 'create') { assert.step('create'); - return $.when(def).then(_.constant(result)); + return prom.then(function () { + return result; + }); } return result; }, @@ -1046,11 +1055,10 @@ QUnit.module('Views', { "should have 4 records at the beginning"); // add an element and click 'Add' twice - def = $.Deferred(); - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); - testUtils.fields.editInput(kanban.$('.o_kanban_quick_create').find('input[name=foo]'), 'new partner'); - testUtils.dom.click(kanban.$('.o_kanban_quick_create').find('.o_kanban_add')); - testUtils.dom.click(kanban.$('.o_kanban_quick_create').find('.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create').find('input[name=foo]'), 'new partner'); + await testUtils.dom.click(kanban.$('.o_kanban_quick_create').find('.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_kanban_quick_create').find('.o_kanban_add')); assert.containsN(kanban, '.o_kanban_record', 4, "should not have created the record yet"); @@ -1059,8 +1067,9 @@ QUnit.module('Views', { assert.hasClass(kanban.$('.o_kanban_quick_create'),'o_disabled', "quick create should be disabled"); - def.resolve(); + prom.resolve(); + await nextTick(); assert.containsN(kanban, '.o_kanban_record', 5, "should have created a new record"); assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), '', @@ -1073,7 +1082,7 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record: prevent multiple adds with ENTER, with onchange', function (assert) { + QUnit.test('quick create record: prevent multiple adds with ENTER, with onchange', async function (assert) { assert.expect(13); this.data.partner.onchanges = { @@ -1081,8 +1090,9 @@ QUnit.module('Views', { obj.int_field += (obj.foo ? 3 : 0); }, }; - var def; - var kanban = createView({ + var shouldDelayOnchange = false; + var prom = makeTestPromise(); + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1102,7 +1112,11 @@ QUnit.module('Views', { var result = this._super.apply(this, arguments); if (args.method === 'onchange') { assert.step('onchange'); - return $.when(def).then(_.constant(result)); + if (shouldDelayOnchange) { + return Promise.resolve(prom).then(function () { + return result + }); + } } if (args.method === 'create') { assert.step('create'); @@ -1123,8 +1137,8 @@ QUnit.module('Views', { "should have 4 records at the beginning"); // add an element and press ENTER twice - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); - def = $.Deferred(); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + shouldDelayOnchange = true; var enterEvent = { keyCode: $.ui.keyCode.ENTER, which: $.ui.keyCode.ENTER, @@ -1143,8 +1157,9 @@ QUnit.module('Views', { assert.hasClass(kanban.$('.o_kanban_quick_create'),'o_disabled', "quick create should be disabled"); - def.resolve(); + prom.resolve(); + await nextTick(); assert.containsN(kanban, '.o_kanban_record', 5, "should have created a new record"); assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), '', @@ -1162,7 +1177,7 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record: click Add to create, with delayed onchange', function (assert) { + QUnit.test('quick create record: click Add to create, with delayed onchange', async function (assert) { assert.expect(13); this.data.partner.onchanges = { @@ -1170,8 +1185,9 @@ QUnit.module('Views', { obj.int_field += (obj.foo ? 3 : 0); }, }; - var def; - var kanban = createView({ + var shouldDelayOnchange = false; + var prom = makeTestPromise(); + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1191,7 +1207,11 @@ QUnit.module('Views', { var result = this._super.apply(this, arguments); if (args.method === 'onchange') { assert.step('onchange'); - return $.when(def).then(_.constant(result)); + if (shouldDelayOnchange) { + return Promise.resolve(prom).then(function () { + return result + }); + } } if (args.method === 'create') { assert.step('create'); @@ -1208,10 +1228,10 @@ QUnit.module('Views', { "should have 4 records at the beginning"); // add an element and click 'add' - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); - def = $.Deferred(); - testUtils.fields.editInput(kanban.$('.o_kanban_quick_create').find('input[name=foo]'), 'new partner'); - testUtils.dom.click(kanban.$('.o_kanban_quick_create').find('.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + shouldDelayOnchange = true; + await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create').find('input[name=foo]'), 'new partner'); + await testUtils.dom.click(kanban.$('.o_kanban_quick_create').find('.o_kanban_add')); assert.containsN(kanban, '.o_kanban_record', 4, "should not have created the record yet"); @@ -1220,8 +1240,9 @@ QUnit.module('Views', { assert.hasClass(kanban.$('.o_kanban_quick_create'),'o_disabled', "quick create should be disabled"); - def.resolve(); // the onchange returns + prom.resolve(); // the onchange returns + await nextTick(); assert.containsN(kanban, '.o_kanban_record', 5, "should have created a new record"); assert.strictEqual(kanban.$('.o_kanban_quick_create input[name=foo]').val(), '', @@ -1239,10 +1260,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create when first column is folded', function (assert) { + QUnit.test('quick create when first column is folded', async function (assert) { assert.expect(6); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1259,13 +1280,13 @@ QUnit.module('Views', { // fold the first column testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:first')); - testUtils.dom.click(kanban.$('.o_kanban_group:first .o_kanban_toggle_fold')); + await testUtils.dom.click(kanban.$('.o_kanban_group:first .o_kanban_toggle_fold')); assert.hasClass(kanban.$('.o_kanban_group:first'),'o_column_folded', "first column should be folded"); // click on 'Create' to open the quick create in the first column - testUtils.kanban.clickCreate(kanban); + await testUtils.kanban.clickCreate(kanban); assert.doesNotHaveClass(kanban.$('.o_kanban_group:first'), 'o_column_folded', "first column should no longer be folded"); @@ -1275,7 +1296,7 @@ QUnit.module('Views', { // fold again the first column testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:first')); - testUtils.dom.click(kanban.$('.o_kanban_group:first .o_kanban_toggle_fold')); + await testUtils.dom.click(kanban.$('.o_kanban_group:first .o_kanban_toggle_fold')); assert.hasClass(kanban.$('.o_kanban_group:first'),'o_column_folded', "first column should be folded"); @@ -1285,10 +1306,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record: cancel when not dirty', function (assert) { + QUnit.test('quick create record: cancel when not dirty', async function (assert) { assert.expect(9); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1304,22 +1325,22 @@ QUnit.module('Views', { "first column should contain one record"); // click to add an element - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); assert.containsOnce(kanban, '.o_kanban_quick_create', "should have open the quick create widget"); // click again to add an element -> should have kept the quick create open - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); assert.containsOnce(kanban, '.o_kanban_quick_create', "should have kept the quick create open"); // click outside: should remove the quick create - testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first')); + await testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first')); assert.containsNone(kanban, '.o_kanban_quick_create', "the quick create should not have been destroyed"); // click to reopen the quick create - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); assert.containsOnce(kanban, '.o_kanban_quick_create', "should have open the quick create widget"); @@ -1332,13 +1353,13 @@ QUnit.module('Views', { "quick create widget should have been removed"); // click to reopen the quick create - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); assert.containsOnce(kanban, '.o_kanban_quick_create', "should have open the quick create widget"); // click on 'Discard': should remove the quick create - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); - testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first')); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + await testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first')); assert.containsNone(kanban, '.o_kanban_quick_create', "the quick create should be destroyed when the user clicks outside"); @@ -1348,10 +1369,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record: cancel when dirty', function (assert) { + QUnit.test('quick create record: cancel when dirty', async function (assert) { assert.expect(7); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1367,15 +1388,15 @@ QUnit.module('Views', { "first column should contain one record"); // click to add an element and edit it - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); assert.containsOnce(kanban, '.o_kanban_quick_create', "should have open the quick create widget"); var $quickCreate = kanban.$('.o_kanban_quick_create'); - testUtils.fields.editInput($quickCreate.find('input'), 'some value'); + await testUtils.fields.editInput($quickCreate.find('input'), 'some value'); // click outside: should not remove the quick create - testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first')); + await testUtils.dom.click(kanban.$('.o_kanban_group .o_kanban_record:first')); assert.containsOnce(kanban, '.o_kanban_quick_create', "the quick create should not have been destroyed"); @@ -1388,15 +1409,15 @@ QUnit.module('Views', { "quick create widget should have been removed"); // click to reopen quick create and edit it - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); assert.containsOnce(kanban, '.o_kanban_quick_create', "should have open the quick create widget"); $quickCreate = kanban.$('.o_kanban_quick_create'); - testUtils.fields.editInput($quickCreate.find('input'), 'some value'); + await testUtils.fields.editInput($quickCreate.find('input'), 'some value'); // click on 'Discard': should remove the quick create - testUtils.dom.click(kanban.$('.o_kanban_quick_create .o_kanban_cancel')); + await testUtils.dom.click(kanban.$('.o_kanban_quick_create .o_kanban_cancel')); assert.containsNone(kanban, '.o_kanban_quick_create', "the quick create should be destroyed when the user clicks outside"); @@ -1406,11 +1427,11 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record and edit in grouped mode', function (assert) { + QUnit.test('quick create record and edit in grouped mode', async function (assert) { assert.expect(6); var newRecordID; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1444,10 +1465,10 @@ QUnit.module('Views', { // click to add and edit an element var $quickCreate = kanban.$('.o_kanban_quick_create'); - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); $quickCreate = kanban.$('.o_kanban_quick_create'); - testUtils.fields.editInput($quickCreate.find('input'), 'new partner'); - testUtils.dom.click($quickCreate.find('button.o_kanban_edit')); + await testUtils.fields.editInput($quickCreate.find('input'), 'new partner'); + await testUtils.dom.click($quickCreate.find('button.o_kanban_edit')); assert.strictEqual(this.data.partner.records.length, 5, "should have created a partner"); @@ -1459,10 +1480,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create several records in a row', function (assert) { + QUnit.test('quick create several records in a row', async function (assert) { assert.expect(6); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1478,12 +1499,12 @@ QUnit.module('Views', { "first column should contain one record"); // click to add an element, fill the input and press ENTER - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); assert.containsOnce(kanban, '.o_kanban_quick_create', "the quick create should be open"); - testUtils.kanban.quickCreate(kanban, 'new partner 1'); + await testUtils.kanban.quickCreate(kanban, 'new partner 1'); assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column should now contain two records"); @@ -1491,7 +1512,7 @@ QUnit.module('Views', { "the quick create should still be open"); // create a second element in a row - testUtils.kanban.quickCreate(kanban, 'new partner 2'); + await testUtils.kanban.quickCreate(kanban, 'new partner 2'); assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 3, "first column should now contain three records"); @@ -1501,11 +1522,11 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create is disabled until record is created and read', function (assert) { + QUnit.test('quick create is disabled until record is created and read', async function (assert) { assert.expect(6); - var def; - var kanban = createView({ + var prom = makeTestPromise(); + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1518,7 +1539,7 @@ QUnit.module('Views', { mockRPC: function (route, args) { var result = this._super.apply(this, arguments); if (args.method === 'read') { - return $.when(def).then(_.constant(result)); + return prom.then(_.constant(result)); } return result; }, @@ -1528,22 +1549,21 @@ QUnit.module('Views', { "first column should contain one record"); // click to add a record, and add two in a row (first one will be delayed) - testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); + await testUtils.dom.click(kanban.$('.o_kanban_header .o_kanban_quick_add i').first()); assert.containsOnce(kanban, '.o_kanban_quick_create', "the quick create should be open"); - def = $.Deferred(); - - testUtils.kanban.quickCreate(kanban, 'new partner 1'); + await testUtils.kanban.quickCreate(kanban, 'new partner 1'); assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column should still contain one record"); assert.containsOnce(kanban, '.o_kanban_quick_create.o_disabled', "quick create should be disabled"); - def.resolve(); + prom.resolve(); + await nextTick(); assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column should now contain two records"); assert.strictEqual(kanban.$('.o_kanban_quick_create:not(.o_disabled)').length, 1, @@ -1552,10 +1572,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record fail in grouped by many2one', function (assert) { + QUnit.test('quick create record fail in grouped by many2one', async function (assert) { assert.expect(8); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1574,11 +1594,14 @@ QUnit.module('Views', { groupBy: ['product_id'], mockRPC: function (route, args) { if (args.method === 'name_create') { - return $.Deferred().reject({ - code: 200, - data: {}, - message: "Odoo server error", - }, $.Event()); + return Promise.reject({ + message: { + code: 200, + data: {}, + message: "Odoo server error", + }, + event: $.Event() + }); } return this._super.apply(this, arguments); }, @@ -1587,11 +1610,11 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "there should be 2 records in first column"); - testUtils.kanban.clickCreate(kanban); // Click on 'Create' + await testUtils.kanban.clickCreate(kanban); // Click on 'Create' assert.hasClass(kanban.$('.o_kanban_group:first() > div:nth(1)'),'o_kanban_quick_create', "clicking on create should open the quick_create in the first column"); - testUtils.kanban.quickCreate(kanban, 'test'); + await testUtils.kanban.quickCreate(kanban, 'test'); assert.strictEqual($('.modal .o_form_view.o_form_editable').length, 1, "a form view dialog should have been opened (in edit)"); @@ -1599,8 +1622,8 @@ QUnit.module('Views', { "the correct product_id should already be set"); // specify a name and save - testUtils.fields.editInput($('.modal input[name=foo]'), 'test'); - testUtils.modal.clickButton('Save'); + await testUtils.fields.editInput($('.modal input[name=foo]'), 'test'); + await testUtils.modal.clickButton('Save'); assert.strictEqual($('.modal').length, 0, "the modal should be closed"); assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 3, @@ -1614,10 +1637,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record is re-enabled after discard on failure', function (assert) { + QUnit.test('quick create record is re-enabled after discard on failure', async function (assert) { assert.expect(4); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1636,26 +1659,29 @@ QUnit.module('Views', { groupBy: ['product_id'], mockRPC: function (route, args) { if (args.method === 'name_create') { - return $.Deferred().reject({ - code: 200, - data: {}, - message: "Odoo server error", - }, $.Event()); + return Promise.reject({ + message: { + code: 200, + data: {}, + message: "Odoo server error", + }, + event: $.Event() + }); } return this._super.apply(this, arguments); } }); - testUtils.kanban.clickCreate(kanban); + await testUtils.kanban.clickCreate(kanban); assert.containsOnce(kanban, '.o_kanban_quick_create', "should have a quick create widget"); - testUtils.kanban.quickCreate(kanban, 'test'); + await testUtils.kanban.quickCreate(kanban, 'test'); assert.strictEqual($('.modal .o_form_view.o_form_editable').length, 1, "a form view dialog should have been opened (in edit)"); - testUtils.modal.clickButton('Discard'); + await testUtils.modal.clickButton('Discard'); assert.strictEqual($('.modal').length, 0, "the modal should be closed"); assert.strictEqual(kanban.$('.o_kanban_quick_create:not(.o_disabled)').length, 1, @@ -1664,10 +1690,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record fails in grouped by char', function (assert) { + QUnit.test('quick create record fails in grouped by char', async function (assert) { assert.expect(7); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1683,11 +1709,14 @@ QUnit.module('Views', { }, mockRPC: function (route, args) { if (args.method === 'name_create') { - return $.Deferred().reject({ - code: 200, - data: {}, - message: "Odoo server error", - }, $.Event()); + return Promise.reject({ + message: { + code: 200, + data: {}, + message: "Odoo server error", + }, + event: $.Event() + }); } if (args.method === 'create') { assert.deepEqual(args.args[0], {foo: 'yop'}, @@ -1703,15 +1732,15 @@ QUnit.module('Views', { assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "there should be 1 record in first column"); - testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i')); - testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'test'); - testUtils.dom.click(kanban.$('.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i')); + await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'test'); + await testUtils.dom.click(kanban.$('.o_kanban_add')); assert.strictEqual($('.modal .o_form_view.o_form_editable').length, 1, "a form view dialog should have been opened (in edit)"); assert.strictEqual($('.modal .o_field_widget[name=foo]').val(), 'yop', "the correct default value for foo should already be set"); - testUtils.modal.clickButton('Save'); + await testUtils.modal.clickButton('Save'); assert.strictEqual($('.modal').length, 0, "the modal should be closed"); assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, @@ -1720,10 +1749,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record fails in grouped by selection', function (assert) { + QUnit.test('quick create record fails in grouped by selection', async function (assert) { assert.expect(7); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1739,11 +1768,14 @@ QUnit.module('Views', { }, mockRPC: function (route, args) { if (args.method === 'name_create') { - return $.Deferred().reject({ - code: 200, - data: {}, - message: "Odoo server error", - }, $.Event()); + return Promise.reject({ + message: { + code: 200, + data: {}, + message: "Odoo server error", + }, + event: $.Event() + }); } if (args.method === 'create') { assert.deepEqual(args.args[0], {state: 'abc'}, @@ -1759,16 +1791,16 @@ QUnit.module('Views', { assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "there should be 1 record in first column"); - testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i')); - testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'test'); - testUtils.dom.click(kanban.$('.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i')); + await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'test'); + await testUtils.dom.click(kanban.$('.o_kanban_add')); assert.strictEqual($('.modal .o_form_view.o_form_editable').length, 1, "a form view dialog should have been opened (in edit)"); assert.strictEqual($('.modal .o_field_widget[name=state]').val(), '"abc"', "the correct default value for state should already be set"); - testUtils.modal.clickButton('Save'); + await testUtils.modal.clickButton('Save'); assert.strictEqual($('.modal').length, 0, "the modal should be closed"); assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, @@ -1777,10 +1809,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record in empty grouped kanban', function (assert) { + QUnit.test('quick create record in empty grouped kanban', async function (assert) { assert.expect(3); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1800,7 +1832,7 @@ QUnit.module('Views', { {__domain: [['product_id', '=', 3]], product_id_count: 0}, {__domain: [['product_id', '=', 5]], product_id_count: 0}, ]; - return $.when(result); + return Promise.resolve(result); } return this._super.apply(this, arguments); }, @@ -1811,7 +1843,7 @@ QUnit.module('Views', { assert.containsNone(kanban, '.o_kanban_record', "both columns should be empty"); - testUtils.kanban.clickCreate(kanban); + await testUtils.kanban.clickCreate(kanban); assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_quick_create', "should have opened the quick create in the first column"); @@ -1819,10 +1851,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record in grouped on date(time) field', function (assert) { + QUnit.test('quick create record in grouped on date(time) field', async function (assert) { assert.expect(6); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1846,27 +1878,27 @@ QUnit.module('Views', { "quick create should be disabled when grouped on a date field"); // clicking on CREATE in control panel should not open a quick create - testUtils.kanban.clickCreate(kanban); + await testUtils.kanban.clickCreate(kanban); assert.containsNone(kanban, '.o_kanban_quick_create', "should not have opened the quick create widget"); - kanban.reload({groupBy: ['datetime']}); + await kanban.reload({groupBy: ['datetime']}); assert.containsNone(kanban, '.o_kanban_header .o_kanban_quick_add i', "quick create should be disabled when grouped on a datetime field"); // clicking on CREATE in control panel should not open a quick create - testUtils.kanban.clickCreate(kanban); + await testUtils.kanban.clickCreate(kanban); assert.containsNone(kanban, '.o_kanban_quick_create', "should not have opened the quick create widget"); kanban.destroy(); }); - QUnit.test('quick create record feature is properly enabled/disabled at reload', function (assert) { + QUnit.test('quick create record feature is properly enabled/disabled at reload', async function (assert) { assert.expect(3); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1881,12 +1913,12 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_header .o_kanban_quick_add i', 3, "quick create should be enabled when grouped on a char field"); - kanban.reload({groupBy: ['date']}); + await kanban.reload({groupBy: ['date']}); assert.containsNone(kanban, '.o_kanban_header .o_kanban_quick_add i', "quick create should now be disabled (grouped on date field)"); - kanban.reload({groupBy: ['bar']}); + await kanban.reload({groupBy: ['bar']}); assert.containsN(kanban, '.o_kanban_header .o_kanban_quick_add i', 2, "quick create should be enabled again (grouped on boolean field)"); @@ -1894,10 +1926,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record in grouped by char field', function (assert) { + QUnit.test('quick create record in grouped by char field', async function (assert) { assert.expect(4); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1921,9 +1953,9 @@ QUnit.module('Views', { assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column should contain 1 record"); - testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i')); - testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'new record'); - testUtils.dom.click(kanban.$('.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i')); + await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'new record'); + await testUtils.dom.click(kanban.$('.o_kanban_add')); assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column should now contain 2 records"); @@ -1931,10 +1963,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record in grouped by boolean field', function (assert) { + QUnit.test('quick create record in grouped by boolean field', async function (assert) { assert.expect(4); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1958,9 +1990,9 @@ QUnit.module('Views', { assert.strictEqual(kanban.$('.o_kanban_group:nth(1) .o_kanban_record').length, 3, "second column (true) should contain 3 records"); - testUtils.dom.click(kanban.$('.o_kanban_header:nth(1) .o_kanban_quick_add i')); - testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'new record'); - testUtils.dom.click(kanban.$('.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_kanban_header:nth(1) .o_kanban_quick_add i')); + await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'new record'); + await testUtils.dom.click(kanban.$('.o_kanban_add')); assert.strictEqual(kanban.$('.o_kanban_group:nth(1) .o_kanban_record').length, 4, "second column (true) should now contain 4 records"); @@ -1968,10 +2000,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record in grouped on selection field', function (assert) { + QUnit.test('quick create record in grouped on selection field', async function (assert) { assert.expect(4); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1995,9 +2027,9 @@ QUnit.module('Views', { assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column (abc) should contain 1 record"); - testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i')); - testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'new record'); - testUtils.dom.click(kanban.$('.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i')); + await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'new record'); + await testUtils.dom.click(kanban.$('.o_kanban_add')); assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column (abc) should contain 2 records"); @@ -2005,10 +2037,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record in grouped by char field (within quick_create_view)', function (assert) { + QUnit.test('quick create record in grouped by char field (within quick_create_view)', async function (assert) { assert.expect(6); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2039,10 +2071,10 @@ QUnit.module('Views', { assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column should contain 1 record"); - testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i')); + await testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i')); assert.strictEqual(kanban.$('.o_kanban_quick_create input').val(), 'yop', "should have set the correct foo value by default"); - testUtils.dom.click(kanban.$('.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_kanban_add')); assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column should now contain 2 records"); @@ -2050,10 +2082,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record in grouped by boolean field (within quick_create_view)', function (assert) { + QUnit.test('quick create record in grouped by boolean field (within quick_create_view)', async function (assert) { assert.expect(6); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2084,10 +2116,10 @@ QUnit.module('Views', { assert.strictEqual(kanban.$('.o_kanban_group:nth(1) .o_kanban_record').length, 3, "second column (true) should contain 3 records"); - testUtils.dom.click(kanban.$('.o_kanban_header:nth(1) .o_kanban_quick_add i')); + await testUtils.dom.click(kanban.$('.o_kanban_header:nth(1) .o_kanban_quick_add i')); assert.ok(kanban.$('.o_kanban_quick_create .o_field_boolean input').is(':checked'), "should have set the correct bar value by default"); - testUtils.dom.click(kanban.$('.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_kanban_add')); assert.strictEqual(kanban.$('.o_kanban_group:nth(1) .o_kanban_record').length, 4, "second column (true) should now contain 4 records"); @@ -2095,10 +2127,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record in grouped by selection field (within quick_create_view)', function (assert) { + QUnit.test('quick create record in grouped by selection field (within quick_create_view)', async function (assert) { assert.expect(6); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2129,10 +2161,10 @@ QUnit.module('Views', { assert.containsOnce(kanban, '.o_kanban_group:first .o_kanban_record', "first column (abc) should contain 1 record"); - testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i')); + await testUtils.dom.click(kanban.$('.o_kanban_header:first .o_kanban_quick_add i')); assert.strictEqual(kanban.$('.o_kanban_quick_create select').val(), '"abc"', "should have set the correct state value by default"); - testUtils.dom.click(kanban.$('.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_kanban_add')); assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 2, "first column (abc) should now contain 2 records"); @@ -2140,11 +2172,11 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create record while adding a new column', function (assert) { + QUnit.test('quick create record while adding a new column', async function (assert) { assert.expect(10); - var def = $.Deferred(); - var kanban = createView({ + var def = testUtils.makeTestPromise(); + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2170,17 +2202,17 @@ QUnit.module('Views', { assert.containsOnce(kanban, '.o_column_quick_create'); assert.isNotVisible(kanban.$('.o_column_quick_create input')); - testUtils.dom.click(kanban.$('.o_quick_create_folded')); + await testUtils.dom.click(kanban.$('.o_quick_create_folded')); assert.isVisible(kanban.$('.o_column_quick_create input')); - testUtils.fields.editInput(kanban.$('.o_column_quick_create input'), 'new column'); - testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); + await testUtils.fields.editInput(kanban.$('.o_column_quick_create input'), 'new column'); + await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); assert.containsN(kanban, '.o_kanban_group', 2); // click to add a new record - testUtils.dom.click(kanban.$buttons.find('.o-kanban-button-new')); + await testUtils.dom.click(kanban.$buttons.find('.o-kanban-button-new')); // should wait for the column to be created (and view to be re-rendered // before opening the quick create @@ -2188,20 +2220,20 @@ QUnit.module('Views', { // unlock column creation def.resolve(); - + await testUtils.nextTick(); assert.containsN(kanban, '.o_kanban_group', 3); assert.containsOnce(kanban, '.o_kanban_quick_create'); // quick create record in first column - testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'new record'); - testUtils.dom.click(kanban.$('.o_kanban_quick_create .o_kanban_add')); + await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'new record'); + await testUtils.dom.click(kanban.$('.o_kanban_quick_create .o_kanban_add')); assert.containsN(kanban, '.o_kanban_group:first .o_kanban_record', 3); kanban.destroy(); }); - QUnit.test('many2many_tags in kanban views', function (assert) { + QUnit.test('many2many_tags in kanban views', async function (assert) { assert.expect(12); this.data.partner.records[0].category_ids = [6, 7]; @@ -2212,7 +2244,7 @@ QUnit.module('Views', { color: 0, }); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2254,10 +2286,8 @@ QUnit.module('Views', { 'there should be only one tag in second record'); // Write on the record using the priority widget to trigger a re-render in readonly - testUtils.dom.click(kanban.$('.o_field_widget.o_priority a.o_priority_star.fa-star-o').first()); + await testUtils.dom.click(kanban.$('.o_field_widget.o_priority a.o_priority_star.fa-star-o').first()); assert.verifySteps([ - '/web/dataset/search_read', - '/web/dataset/call_kw/category/read', '/web/dataset/call_kw/partner/write', '/web/dataset/call_kw/partner/read', '/web/dataset/call_kw/category/read' @@ -2266,19 +2296,19 @@ QUnit.module('Views', { 'first record should still contain only 2 tags'); // click on a tag (should trigger switch_view) - testUtils.dom.click(kanban.$('.o_tag:contains(gold):first')); + await testUtils.dom.click(kanban.$('.o_tag:contains(gold):first')); kanban.destroy(); }); - QUnit.test('Do not open record when clicking on `a` with `href`', function (assert) { + QUnit.test('Do not open record when clicking on `a` with `href`', async function (assert) { assert.expect(5); this.data.partner.records = [ { id: 1, foo: 'yop' }, ]; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2331,13 +2361,13 @@ QUnit.module('Views', { ev.preventDefault(); }); - testUtils.dom.click($testLink); + await testUtils.dom.click($testLink); $(document.body).off('click.o_test'); kanban.destroy(); }); - QUnit.test('o2m loaded in only one batch', function (assert) { + QUnit.test('o2m loaded in only one batch', async function (assert) { assert.expect(9); this.data.subtask = { @@ -2357,7 +2387,7 @@ QUnit.module('Views', { this.data.partner.records[0].subtask_ids = [1]; this.data.partner.records[1].subtask_ids = [2]; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2376,7 +2406,7 @@ QUnit.module('Views', { }, }); - kanban.reload(); + await kanban.reload(); assert.verifySteps([ 'read_group', '/web/dataset/search_read', @@ -2390,10 +2420,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('m2m loaded in only one batch', function (assert) { + QUnit.test('m2m loaded in only one batch', async function (assert) { assert.expect(9); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2412,7 +2442,7 @@ QUnit.module('Views', { }, }); - testUtils.kanban.reload(kanban); + await kanban.reload(kanban); assert.verifySteps([ 'read_group', '/web/dataset/search_read', @@ -2426,7 +2456,7 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('fetch reference in only one batch', function (assert) { + QUnit.test('fetch reference in only one batch', async function (assert) { assert.expect(9); this.data.partner.records[0].ref_product = 'product,3'; @@ -2436,7 +2466,7 @@ QUnit.module('Views', { type: 'reference', }; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2455,7 +2485,7 @@ QUnit.module('Views', { }, }); - kanban.reload(); + await kanban.reload(); assert.verifySteps([ 'read_group', '/web/dataset/search_read', @@ -2469,12 +2499,12 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('wait x2manys batch fetches to re-render', function (assert) { - assert.expect(4); + QUnit.test('wait x2manys batch fetches to re-render', async function (assert) { + assert.expect(7); var done = assert.async(); - var def; - var kanban = createView({ + var def = Promise.resolve(); + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2490,7 +2520,7 @@ QUnit.module('Views', { mockRPC: function (route, args) { var result = this._super(route, args); if (args.method === 'read') { - return $.when(def).then(function() { + return def.then(function() { return result; }); } @@ -2498,13 +2528,17 @@ QUnit.module('Views', { }, }); - def = $.Deferred(); + def = testUtils.makeTestPromise(); assert.containsN(kanban, '.o_tag', 2); + assert.containsN(kanban, '.o_kanban_group', 2); kanban.update({groupBy: ['state']}); + def.then(async function () { + assert.containsN(kanban, '.o_kanban_group', 2); + await testUtils.nextTick(); + assert.containsN(kanban, '.o_kanban_group', 3); - def.resolve().then(function () { assert.containsN(kanban, '.o_tag', 2, - 'Should display 2 tags after update'); + 'Should display 2 tags after update'); assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_tag').text(), 'gold', 'First category should be \'gold\''); assert.strictEqual(kanban.$('.o_kanban_group:eq(2) .o_tag').text(), @@ -2512,20 +2546,22 @@ QUnit.module('Views', { kanban.destroy(); done(); }); + await testUtils.nextTick(); + def.resolve(); }); - QUnit.test('can drag and drop a record from one column to the next', function (assert) { + QUnit.test('can drag and drop a record from one column to the next', async function (assert) { assert.expect(9); // @todo: remove this resequenceDef whenever the jquery upgrade branch // is merged. This is currently necessary to simulate the reality: we // need the click handlers to be executed after the end of the drag and // drop operation, not before. - var resequenceDef = $.Deferred(); + var resequenceDef = testUtils.makeTestPromise(); var envIDs = [1, 3, 2, 4]; // the ids that should be in the environment during this test this.data.partner.fields.sequence = {type: 'number', string: "Sequence"}; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2554,9 +2590,10 @@ QUnit.module('Views', { var $record = kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record:first'); var $group = kanban.$('.o_kanban_group:nth-child(2)'); envIDs = [3, 2, 4, 1]; // first record of first column moved to the bottom of second column - testUtils.dom.dragAndDrop($record, $group, {withTrailingClick: true}); + await testUtils.dom.dragAndDrop($record, $group, {withTrailingClick: true}); resequenceDef.resolve(); + await testUtils.nextTick(); assert.containsOnce(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record'); assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 3); assert.containsN(kanban, '.thisiseditable', 4); @@ -2566,10 +2603,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('drag and drop a record, grouped by selection', function (assert) { + QUnit.test('drag and drop a record, grouped by selection', async function (assert) { assert.expect(6); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2584,7 +2621,7 @@ QUnit.module('Views', { mockRPC: function (route, args) { if (route === '/web/dataset/resequence') { assert.ok(true, "should call resequence"); - return $.when(true); + return Promise.resolve(true); } if (args.model === 'partner' && args.method === 'write') { assert.deepEqual(args.args[1], {state: 'def'}); @@ -2597,18 +2634,19 @@ QUnit.module('Views', { var $record = kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record:first'); var $group = kanban.$('.o_kanban_group:nth-child(2)'); - testUtils.dom.dragAndDrop($record, $group); + await testUtils.dom.dragAndDrop($record, $group); + await nextTick(); // wait for resequence after drag and drop assert.containsNone(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record'); assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 2); kanban.destroy(); }); - QUnit.test('prevent drag and drop of record if grouped by readonly', function (assert) { + QUnit.test('prevent drag and drop of record if grouped by readonly', async function (assert) { assert.expect(12); this.data.partner.fields.foo.readonly = true; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2622,7 +2660,7 @@ QUnit.module('Views', { '</kanban>', mockRPC: function (route, args) { if (route === '/web/dataset/resequence') { - return $.when(); + return Promise.resolve(); } if (args.model === 'partner' && args.method === 'write') { throw new Error('should not be draggable'); @@ -2631,27 +2669,29 @@ QUnit.module('Views', { }, }); // simulate an update coming from the searchview, with another groupby given - kanban.update({groupBy: ['state']}); + await kanban.update({groupBy: ['state']}); assert.containsOnce(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record'); assert.containsOnce(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record'); // drag&drop a record in another column var $record = kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record:first'); var $group = kanban.$('.o_kanban_group:nth-child(2)'); - testUtils.dom.dragAndDrop($record, $group); + await testUtils.dom.dragAndDrop($record, $group); + await nextTick(); // wait for resequence after drag and drop // should not be draggable assert.containsOnce(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record'); assert.containsOnce(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record'); // simulate an update coming from the searchview, with another groupby given - kanban.update({groupBy: ['foo']}); + await kanban.update({groupBy: ['foo']}); assert.containsOnce(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record'); assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 2); // drag&drop a record in another column $record = kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record:first'); $group = kanban.$('.o_kanban_group:nth-child(2)'); - testUtils.dom.dragAndDrop($record, $group); + await testUtils.dom.dragAndDrop($record, $group); + await nextTick(); // wait for resequence after drag and drop // should not be draggable assert.containsOnce(kanban, '.o_kanban_group:nth-child(1) .o_kanban_record'); assert.containsN(kanban, '.o_kanban_group:nth-child(2) .o_kanban_record', 2); @@ -2661,7 +2701,7 @@ QUnit.module('Views', { var $record2 = kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record:eq(1)'); assert.strictEqual($record1.text(), "blipDEF", "first record should be DEF"); assert.strictEqual($record2.text(), "blipGHI", "second record should be GHI"); - testUtils.dom.dragAndDrop($record2, $record1, {position: 'top'}); + await testUtils.dom.dragAndDrop($record2, $record1, {position: 'top'}); // should still be able to resequence assert.strictEqual(kanban.$('.o_kanban_group:nth-child(2) .o_kanban_record:eq(0)').text(), "blipGHI", "records should have been resequenced"); @@ -2671,7 +2711,7 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('prevent drag and drop if grouped by date/datetime field', function (assert) { + QUnit.test('prevent drag and drop if grouped by date/datetime field', async function (assert) { assert.expect(5); this.data.partner.records[0].date = '2017-01-08'; @@ -2679,7 +2719,7 @@ QUnit.module('Views', { this.data.partner.records[2].date = '2017-02-08'; this.data.partner.records[3].date = '2017-02-10'; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2700,7 +2740,7 @@ QUnit.module('Views', { // drag&drop a record in another column var $record = kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record:first'); var $group = kanban.$('.o_kanban_group:nth-child(2)'); - testUtils.dragAndDrop($record, $group); + await testUtils.dragAndDrop($record, $group); // should not drag&drop record assert.strictEqual(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record').length , 2, @@ -2710,13 +2750,13 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('kanban view with default_group_by', function (assert) { + QUnit.test('kanban view with default_group_by', async function (assert) { assert.expect(7); this.data.partner.records.product_id = 1; this.data.product.records.push({id: 1, display_name: "third product"}); var readGroupCount = 0; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2746,19 +2786,19 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_group', 2, "should have " + 2 + " columns"); // simulate an update coming from the searchview, with another groupby given - kanban.update({groupBy: ['product_id']}); + await kanban.update({groupBy: ['product_id']}); assert.containsN(kanban, '.o_kanban_group', 2, "should now have " + 3 + " columns"); // simulate an update coming from the searchview, removing the previously set groupby - kanban.update({groupBy: []}); + await kanban.update({groupBy: []}); assert.containsN(kanban, '.o_kanban_group', 2, "should have " + 2 + " columns again"); kanban.destroy(); }); - QUnit.test('kanban view with create=False', function (assert) { + QUnit.test('kanban view with create=False', async function (assert) { assert.expect(1); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2773,10 +2813,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('clicking on a link triggers correct event', function (assert) { + QUnit.test('clicking on a link triggers correct event', async function (assert) { assert.expect(1); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2793,15 +2833,15 @@ QUnit.module('Views', { model: 'partner', }); }); - testUtils.dom.click(kanban.$('a').first()); + await testUtils.dom.click(kanban.$('a').first()); kanban.destroy(); }); - QUnit.test('environment is updated when (un)folding groups', function (assert) { + QUnit.test('environment is updated when (un)folding groups', async function (assert) { assert.expect(3); var envIDs = [1, 3, 2, 4]; // the ids that should be in the environment during this test - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2819,24 +2859,24 @@ QUnit.module('Views', { // fold the second group and check that the res_ids it contains are no // longer in the environment envIDs = [1, 3]; - testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); - testUtils.dom.click(kanban.$('.o_kanban_group:last .o_kanban_toggle_fold')); + await testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); + await testUtils.dom.click(kanban.$('.o_kanban_group:last .o_kanban_toggle_fold')); assert.deepEqual(kanban.exportState().resIds, envIDs); // re-open the second group and check that the res_ids it contains are // back in the environment envIDs = [1, 3, 2, 4]; - testUtils.dom.click(kanban.$('.o_kanban_group:last')); + await testUtils.dom.click(kanban.$('.o_kanban_group:last')); assert.deepEqual(kanban.exportState().resIds, envIDs); kanban.destroy(); }); - QUnit.test('create a column in grouped on m2o', function (assert) { + QUnit.test('create a column in grouped on m2o', async function (assert) { assert.expect(14); var nbRPCs = 0; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2855,7 +2895,7 @@ QUnit.module('Views', { //Create column will call resequence to set column order if (route === '/web/dataset/resequence') { assert.ok(true, "should call resequence"); - return $.when(true); + return Promise.resolve(true); } return this._super(route, args); }, @@ -2864,25 +2904,25 @@ QUnit.module('Views', { assert.notOk(kanban.$('.o_column_quick_create input').is(':visible'), "the input should not be visible"); - testUtils.dom.click(kanban.$('.o_quick_create_folded')); + await testUtils.dom.click(kanban.$('.o_quick_create_folded')); assert.ok(kanban.$('.o_column_quick_create input').is(':visible'), "the input should be visible"); // discard the column creation and click it again - kanban.$('.o_column_quick_create input').trigger($.Event('keydown', { + await kanban.$('.o_column_quick_create input').trigger($.Event('keydown', { keyCode: $.ui.keyCode.ESCAPE, which: $.ui.keyCode.ESCAPE, })); assert.notOk(kanban.$('.o_column_quick_create input').is(':visible'), "the input should not be visible after discard"); - testUtils.dom.click(kanban.$('.o_quick_create_folded')); + await testUtils.dom.click(kanban.$('.o_quick_create_folded')); assert.ok(kanban.$('.o_column_quick_create input').is(':visible'), "the input should be visible"); - kanban.$('.o_column_quick_create input').val('new value'); - testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); + await kanban.$('.o_column_quick_create input').val('new value').trigger('input'); + await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); assert.strictEqual(kanban.$('.o_kanban_group:last span:contains(new value)').length, 1, "the last column should be the newly created one"); @@ -2893,22 +2933,22 @@ QUnit.module('Views', { // fold and unfold the created column, and check that no RPC is done (as there is no record) nbRPCs = 0; - testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); - testUtils.dom.click(kanban.$('.o_kanban_group:last .o_kanban_toggle_fold')); + await testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); + await testUtils.dom.click(kanban.$('.o_kanban_group:last .o_kanban_toggle_fold')); assert.hasClass(kanban.$('.o_kanban_group:last'),'o_column_folded', 'the created column should now be folded'); - testUtils.dom.click(kanban.$('.o_kanban_group:last')); + await testUtils.dom.click(kanban.$('.o_kanban_group:last')); assert.doesNotHaveClass(kanban.$('.o_kanban_group:last'), 'o_column_folded'); assert.strictEqual(nbRPCs, 0, 'no rpc should have been done when folding/unfolding'); // quick create a record - testUtils.kanban.clickCreate(kanban); + await testUtils.kanban.clickCreate(kanban); assert.hasClass(kanban.$('.o_kanban_group:first() > div:nth(1)'),'o_kanban_quick_create', "clicking on create should open the quick_create in the first column"); kanban.destroy(); }); - QUnit.test('auto fold group when reach the limit', function (assert) { + QUnit.test('auto fold group when reach the limit', async function (assert) { assert.expect(9); var data = this.data; @@ -2924,7 +2964,7 @@ QUnit.module('Views', { }); } - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: data, @@ -2965,10 +3005,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('hide and display help message (ESC) in kanban quick create', function (assert) { + QUnit.test('hide and display help message (ESC) in kanban quick create', async function (assert) { assert.expect(2); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -2981,20 +3021,20 @@ QUnit.module('Views', { groupBy: ['product_id'], }); - testUtils.dom.click(kanban.$('.o_quick_create_folded')); + await testUtils.dom.click(kanban.$('.o_quick_create_folded')); assert.ok(kanban.$('.o_discard_msg').is(':visible'), 'the ESC to discard message is visible'); // click outside the column (to lose focus) - testUtils.dom.clickFirst(kanban.$('.o_kanban_header')); + await testUtils.dom.clickFirst(kanban.$('.o_kanban_header')); assert.notOk(kanban.$('.o_discard_msg').is(':visible'), 'the ESC to discard message is no longer visible'); kanban.destroy(); }); - QUnit.test('delete a column in grouped on m2o', function (assert) { - assert.expect(36); + QUnit.test('delete a column in grouped on m2o', async function (assert) { + assert.expect(37); testUtils.mock.patch(KanbanRenderer, { _renderGrouped: function () { @@ -3008,7 +3048,7 @@ QUnit.module('Views', { var resequencedIDs; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3024,7 +3064,7 @@ QUnit.module('Views', { resequencedIDs = args.ids; assert.strictEqual(_.reject(args.ids, _.isNumber).length, 0, "column resequenced should be existing records with IDs"); - return $.when(true); + return Promise.resolve(true); } if (args.method) { assert.step(args.method); @@ -3056,15 +3096,15 @@ QUnit.module('Views', { // delete second column (first cancel the confirm request, then confirm) testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); - testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_delete')); + await testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_delete')); assert.ok($('.modal').length, 'a confirm modal should be displayed'); - testUtils.modal.clickButton('Cancel'); // click on cancel + await testUtils.modal.clickButton('Cancel'); // click on cancel assert.strictEqual(kanban.$('.o_kanban_group:last').data('id'), 5, 'column [5, "xmo"] should still be there'); testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); - testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_delete')); + await testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_delete')); assert.ok($('.modal').length, 'a confirm modal should be displayed'); - testUtils.modal.clickButton('Ok'); // click on confirm + await testUtils.modal.clickButton('Ok'); // click on confirm assert.strictEqual(kanban.$('.o_kanban_group:last').data('id'), 3, 'last column should now be [3, "hello"]'); assert.containsN(kanban, '.o_kanban_group', 2, "should still have two columns"); @@ -3084,37 +3124,39 @@ QUnit.module('Views', { "the old widgets should have been correctly deleted"); // test column drag and drop having an 'Undefined' column - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( kanban.$('.o_kanban_header_title:first'), kanban.$('.o_kanban_header_title:last'), {position: 'right'} ); assert.strictEqual(resequencedIDs, undefined, "resequencing require at least 2 not Undefined columns"); - testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded')); + await testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded')); kanban.$('.o_column_quick_create input').val('once third column'); - testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); var newColumnID = kanban.$('.o_kanban_group:last').data('id'); - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( kanban.$('.o_kanban_header_title:first'), kanban.$('.o_kanban_header_title:last'), {position: 'right'} ); assert.deepEqual([3, newColumnID], resequencedIDs, "moving the Undefined column should not affect order of other columns") - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( kanban.$('.o_kanban_header_title:first'), kanban.$('.o_kanban_header_title:nth(1)'), {position: 'right'} ); + await nextTick(); // wait for resequence after drag and drop assert.deepEqual([newColumnID, 3], resequencedIDs, "moved column should be resequenced accordingly") + assert.verifySteps(['name_create', 'read', 'read', 'read']); kanban.destroy(); testUtils.mock.unpatch(KanbanRenderer); }); - QUnit.test('create a column, delete it and create another one', function (assert) { + QUnit.test('create a column, delete it and create another one', async function (assert) { assert.expect(5); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3129,21 +3171,21 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_group', 2, "should have two columns"); - testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded')); + await testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded')); kanban.$('.o_column_quick_create input').val('new column 1'); - testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); assert.containsN(kanban, '.o_kanban_group', 3, "should have two columns"); testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:last')); - testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_delete')); - testUtils.modal.clickButton('Ok'); + await testUtils.dom.click(kanban.$('.o_kanban_group:last .o_column_delete')); + await testUtils.modal.clickButton('Ok'); assert.containsN(kanban, '.o_kanban_group', 2, "should have twos columns"); - testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded')); + await testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded')); kanban.$('.o_column_quick_create input').val('new column 2'); - testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); assert.containsN(kanban, '.o_kanban_group', 3, "should have three columns"); assert.strictEqual(kanban.$('.o_kanban_group:last span:contains(new column 2)').length, 1, @@ -3151,11 +3193,11 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('edit a column in grouped on m2o', function (assert) { + QUnit.test('edit a column in grouped on m2o', async function (assert) { assert.expect(12); var nbRPCs = 0; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3179,14 +3221,14 @@ QUnit.module('Views', { // edit the title of column [5, 'xmo'] and close without saving testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group[data-id=5]')); - testUtils.dom.click(kanban.$('.o_kanban_group[data-id=5] .o_column_edit')); + await testUtils.dom.click(kanban.$('.o_kanban_group[data-id=5] .o_column_edit')); assert.containsOnce(document.body, '.modal .o_form_editable', "a form view should be open in a modal"); assert.strictEqual($('.modal .o_form_editable input').val(), 'xmo', 'the name should be "xmo"'); - testUtils.fields.editInput($('.modal .o_form_editable input'), 'ged'); // change the value + await testUtils.fields.editInput($('.modal .o_form_editable input'), 'ged'); // change the value nbRPCs = 0; - testUtils.dom.click($('.modal-header .close')); + await testUtils.dom.click($('.modal-header .close')); assert.containsNone(document.body, '.modal'); assert.strictEqual(kanban.$('.o_kanban_group[data-id=5] .o_column_title').text(), 'xmo', 'title of the column should still be "xmo"'); @@ -3194,10 +3236,10 @@ QUnit.module('Views', { // edit the title of column [5, 'xmo'] and discard testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group[data-id=5]')); - testUtils.dom.click(kanban.$('.o_kanban_group[data-id=5] .o_column_edit')); - testUtils.fields.editInput($('.modal .o_form_editable input'), 'ged'); // change the value + await testUtils.dom.click(kanban.$('.o_kanban_group[data-id=5] .o_column_edit')); + await testUtils.fields.editInput($('.modal .o_form_editable input'), 'ged'); // change the value nbRPCs = 0; - testUtils.modal.clickButton('Discard'); + await testUtils.modal.clickButton('Discard'); assert.containsNone(document.body, '.modal'); assert.strictEqual(kanban.$('.o_kanban_group[data-id=5] .o_column_title').text(), 'xmo', 'title of the column should still be "xmo"'); @@ -3205,10 +3247,10 @@ QUnit.module('Views', { // edit the title of column [5, 'xmo'] and save testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group[data-id=5]')); - testUtils.dom.click(kanban.$('.o_kanban_group[data-id=5] .o_column_edit')); - testUtils.fields.editInput($('.modal .o_form_editable input'), 'ged'); // change the value + await testUtils.dom.click(kanban.$('.o_kanban_group[data-id=5] .o_column_edit')); + await testUtils.fields.editInput($('.modal .o_form_editable input'), 'ged'); // change the value nbRPCs = 0; - testUtils.modal.clickButton('Save'); // click on save + await testUtils.modal.clickButton('Save'); // click on save assert.ok(!$('.modal').length, 'the modal should be closed'); assert.strictEqual(kanban.$('.o_kanban_group[data-id=5] .o_column_title').text(), 'ged', 'title of the column should be "ged"'); @@ -3216,10 +3258,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create column should be opened if there is no column', function (assert) { + QUnit.test('quick create column should be opened if there is no column', async function (assert) { assert.expect(3); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3241,10 +3283,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick create several columns in a row', function (assert) { + QUnit.test('quick create several columns in a row', async function (assert) { assert.expect(10); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3267,13 +3309,13 @@ QUnit.module('Views', { "the ColumnQuickCreate should be folded"); // add a new column - testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded')); + await testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded')); assert.containsNone(kanban, '.o_column_quick_create .o_quick_create_folded:visible', "the ColumnQuickCreate should be unfolded"); assert.containsOnce(kanban, '.o_column_quick_create .o_quick_create_unfolded:visible', "the ColumnQuickCreate should be unfolded"); kanban.$('.o_column_quick_create input').val('New Column 1'); - testUtils.dom.click(kanban.$('.o_column_quick_create .btn-primary')); + await testUtils.dom.click(kanban.$('.o_column_quick_create .btn-primary')); assert.containsN(kanban, '.o_kanban_group', 3, "should now have three columns"); @@ -3283,14 +3325,14 @@ QUnit.module('Views', { assert.containsOnce(kanban, '.o_column_quick_create .o_quick_create_unfolded:visible', "the ColumnQuickCreate should still be unfolded"); kanban.$('.o_column_quick_create input').val('New Column 2'); - testUtils.dom.click(kanban.$('.o_column_quick_create .btn-primary')); + await testUtils.dom.click(kanban.$('.o_column_quick_create .btn-primary')); assert.containsN(kanban, '.o_kanban_group', 4, "should now have four columns"); kanban.destroy(); }); - QUnit.test('quick create column and examples', function (assert) { + QUnit.test('quick create column and examples', async function (assert) { assert.expect(12); kanbanExamplesRegistry.add('test', [{ @@ -3302,7 +3344,7 @@ QUnit.module('Views', { columns: ["Col 1", "Col 2"], }]); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3319,13 +3361,13 @@ QUnit.module('Views', { "should have a ColumnQuickCreate widget"); // open the quick create - testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded')); + await testUtils.dom.click(kanban.$('.o_column_quick_create .o_quick_create_folded')); assert.containsOnce(kanban, '.o_column_quick_create .o_kanban_examples:visible', "should have a link to see examples"); // click to see the examples - testUtils.dom.click(kanban.$('.o_column_quick_create .o_kanban_examples')); + await testUtils.dom.click(kanban.$('.o_column_quick_create .o_kanban_examples')); assert.strictEqual($('.modal .o_kanban_examples_dialog').length, 1, "should have open the examples dialog"); @@ -3355,10 +3397,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('if view was not grouped at start, it can be grouped and ungrouped', function (assert) { + QUnit.test('if view was not grouped at start, it can be grouped and ungrouped', async function (assert) { assert.expect(3); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3371,22 +3413,22 @@ QUnit.module('Views', { }); assert.doesNotHaveClass(kanban.$('.o_kanban_view'), 'o_kanban_grouped'); - kanban.update({groupBy: ['product_id']}); + await kanban.update({groupBy: ['product_id']}); assert.hasClass(kanban.$('.o_kanban_view'),'o_kanban_grouped'); - kanban.update({groupBy: []}); + await kanban.update({groupBy: []}); assert.doesNotHaveClass(kanban.$('.o_kanban_view'), 'o_kanban_grouped'); kanban.destroy(); }); - QUnit.test('no content helper when no data', function (assert) { + QUnit.test('no content helper when no data', async function (assert) { assert.expect(3); var records = this.data.partner.records; this.data.partner.records = []; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3410,17 +3452,17 @@ QUnit.module('Views', { "should have rendered no content helper from action"); this.data.partner.records = records; - kanban.reload(); + await kanban.reload(); assert.containsNone(kanban, '.o_view_nocontent', "should not display the no content helper"); kanban.destroy(); }); - QUnit.test('no nocontent helper for grouped kanban with empty groups', function (assert) { + QUnit.test('no nocontent helper for grouped kanban with empty groups', async function (assert) { assert.expect(2); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3460,12 +3502,12 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('no nocontent helper for grouped kanban with no records', function (assert) { + QUnit.test('no nocontent helper for grouped kanban with no records', async function (assert) { assert.expect(4); this.data.partner.records = []; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3493,12 +3535,12 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('no nocontent helper is shown when no longer creating column', function (assert) { + QUnit.test('no nocontent helper is shown when no longer creating column', async function (assert) { assert.expect(3); this.data.partner.records = []; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3520,7 +3562,7 @@ QUnit.module('Views', { // creating a new column kanban.$('.o_column_quick_create .o_input').val('applejack'); - testUtils.dom.click(kanban.$('.o_column_quick_create .o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_column_quick_create .o_kanban_add')); assert.containsNone(kanban, '.o_view_nocontent', "there should be no nocontent helper (still in 'column creation mode')"); @@ -3537,12 +3579,12 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('no nocontent helper is hidden when quick creating a column', function (assert) { + QUnit.test('no nocontent helper is hidden when quick creating a column', async function (assert) { assert.expect(2); this.data.partner.records = []; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3557,7 +3599,7 @@ QUnit.module('Views', { var result = [ {__domain: [['product_id', '=', 3]], product_id_count: 0, product_id: [3, 'hello']}, ]; - return $.when(result); + return Promise.resolve(result); } return this._super.apply(this, arguments); }, @@ -3571,7 +3613,7 @@ QUnit.module('Views', { assert.containsOnce(kanban, '.o_view_nocontent', "there should be a nocontent helper"); - testUtils.dom.click(kanban.$('.o_kanban_add_column')); + await testUtils.dom.click(kanban.$('.o_kanban_add_column')); assert.containsNone(kanban, '.o_view_nocontent', "there should be no nocontent helper (we are in 'column creation mode')"); @@ -3579,12 +3621,12 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('remove nocontent helper after adding a record', function (assert) { + QUnit.test('remove nocontent helper after adding a record', async function (assert) { assert.expect(2); this.data.partner.records = []; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3599,7 +3641,7 @@ QUnit.module('Views', { var result = [ {__domain: [['product_id', '=', 3]], product_id_count: 0, product_id: [3, 'hello']}, ]; - return $.when(result); + return Promise.resolve(result); } return this._super.apply(this, arguments); }, @@ -3614,9 +3656,9 @@ QUnit.module('Views', { "there should be a nocontent helper"); // add a record - testUtils.dom.click(kanban.$('.o_kanban_quick_add')); - testUtils.fields.editInput(kanban.$('.o_kanban_quick_create .o_input'), 'twilight sparkle'); - testUtils.dom.click(kanban.$('.o_kanban_quick_create button.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_kanban_quick_add')); + await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create .o_input'), 'twilight sparkle'); + await testUtils.dom.click(kanban.$('.o_kanban_quick_create button.o_kanban_add')); assert.containsNone(kanban, '.o_view_nocontent', "there should be no nocontent helper (there is now one record)"); @@ -3624,12 +3666,12 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('remove nocontent helper when adding a record', function (assert) { + QUnit.test('remove nocontent helper when adding a record', async function (assert) { assert.expect(2); this.data.partner.records = []; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3644,7 +3686,7 @@ QUnit.module('Views', { var result = [ {__domain: [['product_id', '=', 3]], product_id_count: 0, product_id: [3, 'hello']}, ]; - return $.when(result); + return Promise.resolve(result); } return this._super.apply(this, arguments); }, @@ -3659,8 +3701,8 @@ QUnit.module('Views', { "there should be a nocontent helper"); // add a record - testUtils.dom.click(kanban.$('.o_kanban_quick_add')); - testUtils.fields.editInput(kanban.$('.o_kanban_quick_create .o_input'), 'twilight sparkle'); + await testUtils.dom.click(kanban.$('.o_kanban_quick_add')); + await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create .o_input'), 'twilight sparkle'); assert.containsNone(kanban, '.o_view_nocontent', "there should be no nocontent helper (there is now one record)"); @@ -3668,12 +3710,12 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('nocontent helper is displayed again after canceling quick create', function (assert) { + QUnit.test('nocontent helper is displayed again after canceling quick create', async function (assert) { assert.expect(1); this.data.partner.records = []; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3688,7 +3730,7 @@ QUnit.module('Views', { var result = [ {__domain: [['product_id', '=', 3]], product_id_count: 0, product_id: [3, 'hello']}, ]; - return $.when(result); + return Promise.resolve(result); } return this._super.apply(this, arguments); }, @@ -3700,9 +3742,9 @@ QUnit.module('Views', { }); // add a record - testUtils.dom.click(kanban.$('.o_kanban_quick_add')); + await testUtils.dom.click(kanban.$('.o_kanban_quick_add')); - testUtils.dom.click(kanban.$('.o_kanban_view')); + await testUtils.dom.click(kanban.$('.o_kanban_view')); assert.containsOnce(kanban, '.o_view_nocontent', "there should be again a nocontent helper"); @@ -3710,12 +3752,12 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('nocontent helper for grouped kanban with no records with no group_create', function (assert) { + QUnit.test('nocontent helper for grouped kanban with no records with no group_create', async function (assert) { assert.expect(4); this.data.partner.records = []; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3743,12 +3785,12 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('buttons with modifiers', function (assert) { + QUnit.test('buttons with modifiers', async function (assert) { assert.expect(2); this.data.partner.records[1].bar = false; // so that test is more complete - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: "partner", data: this.data, @@ -3774,10 +3816,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('button executes action and reloads', function (assert) { + QUnit.test('button executes action and reloads', async function (assert) { assert.expect(6); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: "partner", data: this.data, @@ -3802,10 +3844,10 @@ QUnit.module('Views', { count++; event.data.on_closed(); }); - testUtils.dom.click($('button[data-name="a1"]').first()); + await testUtils.dom.click($('button[data-name="a1"]').first()); assert.strictEqual(count, 1, "should have triggered a execute action"); - testUtils.dom.click($('button[data-name="a1"]').first()); + await testUtils.dom.click($('button[data-name="a1"]').first()); assert.strictEqual(count, 1, "double-click on kanban actions should be debounced"); assert.verifySteps([ @@ -3816,7 +3858,7 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('button executes action and check domain', function (assert) { + QUnit.test('button executes action and check domain', async function (assert) { assert.expect(2); var data = this.data; @@ -3825,7 +3867,7 @@ QUnit.module('Views', { data.partner.records[k].active = true; } - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: "partner", data: data, @@ -3846,16 +3888,16 @@ QUnit.module('Views', { }); assert.strictEqual(kanban.$('.o_kanban_record:contains(yop)').length, 1, "should display 'yop' record"); - testUtils.dom.click(kanban.$('.o_kanban_record:contains(yop) button[data-name="toggle_active"]')); + await testUtils.dom.click(kanban.$('.o_kanban_record:contains(yop) button[data-name="toggle_active"]')); assert.strictEqual(kanban.$('.o_kanban_record:contains(yop)').length, 0, "should remove 'yop' record from the view"); kanban.destroy(); }); - QUnit.test('button executes action with domain field not in view', function (assert) { + QUnit.test('button executes action with domain field not in view', async function (assert) { assert.expect(1); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: "partner", data: this.data, @@ -3875,7 +3917,7 @@ QUnit.module('Views', { }); try { - testUtils.dom.click(kanban.$('.o_kanban_record:contains(yop) button[data-name="toggle_action"]')); + await testUtils.dom.click(kanban.$('.o_kanban_record:contains(yop) button[data-name="toggle_action"]')); assert.strictEqual(true, true, 'Everything went fine'); } catch (e) { assert.strictEqual(true, false, 'Error triggered at action execution'); @@ -3883,13 +3925,13 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('rendering date and datetime', function (assert) { + QUnit.test('rendering date and datetime', async function (assert) { assert.expect(2); this.data.partner.records[0].date = "2017-01-25"; this.data.partner.records[1].datetime= "2016-12-12 10:55:05"; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3913,12 +3955,12 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('evaluate conditions on relational fields', function (assert) { + QUnit.test('evaluate conditions on relational fields', async function (assert) { assert.expect(3); this.data.partner.records[0].product_id = false; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3944,12 +3986,12 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('resequence columns in grouped by m2o', function (assert) { + QUnit.test('resequence columns in grouped by m2o', async function (assert) { assert.expect(6); this.data.product.fields.sequence = {string: "Sequence", type: "integer"}; var envIDs = [1, 3, 2, 4]; // the ids that should be in the environment during this test - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -3975,7 +4017,8 @@ QUnit.module('Views', { // for this test, and directly call the event handler envIDs = [2, 4, 1, 3]; // the columns will be inverted kanban._onResequenceColumn({data: {ids: [5, 3]}}); - kanban.update({}, {reload: false}); // re-render without reloading + await nextTick(); // wait for resequencing before re-rendering + await kanban.update({}, {reload: false}); // re-render without reloading assert.strictEqual(kanban.$('.o_kanban_group:first').data('id'), 5, "first column should be id 5 after resequencing"); @@ -3984,10 +4027,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('properly evaluate more complex domains', function (assert) { + QUnit.test('properly evaluate more complex domains', async function (assert) { assert.expect(1); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4011,14 +4054,14 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('edit the kanban color with the colorpicker', function (assert) { + QUnit.test('edit the kanban color with the colorpicker', async function (assert) { assert.expect(5); var writeOnColor; this.data.category.records[0].color = 12; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'category', data: this.data, @@ -4061,7 +4104,7 @@ QUnit.module('Views', { // Set a color testUtils.kanban.toggleRecordDropdown($firstRecord); - testUtils.dom.click($firstRecord.find('.oe_kanban_colorpicker a.oe_kanban_color_9')); + await testUtils.dom.click($firstRecord.find('.oe_kanban_colorpicker a.oe_kanban_color_9')); assert.ok(writeOnColor, "should write on the color field"); $firstRecord = kanban.$('.o_kanban_record:first()'); // First record is reloaded here assert.ok($firstRecord.is('.oe_kanban_color_9'), @@ -4070,11 +4113,11 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('load more records in column', function (assert) { - assert.expect(12); + QUnit.test('load more records in column', async function (assert) { + assert.expect(13); var envIDs = [1, 2, 4]; // the ids that should be in the environment during this test - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4089,7 +4132,7 @@ QUnit.module('Views', { }, mockRPC: function (route, args) { if (route === '/web/dataset/search_read') { - assert.step([args.limit, args.offset]); + assert.step(args.limit + ' - ' + args.offset); } return this._super.apply(this, arguments); }, @@ -4101,24 +4144,25 @@ QUnit.module('Views', { // load more envIDs = [1, 2, 3, 4]; // id 3 will be loaded - testUtils.dom.click(kanban.$('.o_kanban_group:eq(1)').find('.o_kanban_load_more')); + await testUtils.dom.click(kanban.$('.o_kanban_group:eq(1)').find('.o_kanban_load_more')); assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_record').length, 3, "there should now be 3 records in the column"); - assert.verifySteps([[2, undefined], [2, undefined], [2, 2]], + assert.verifySteps(['2 - undefined', '2 - undefined', '2 - 2'], "the records should be correctly fetched"); assert.deepEqual(kanban.exportState().resIds, envIDs); // reload - kanban.reload(); + await kanban.reload(); assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_record').length, 3, "there should still be 3 records in the column after reload"); assert.deepEqual(kanban.exportState().resIds, envIDs); + assert.verifySteps(['4 - undefined', '2 - undefined']); kanban.destroy(); }); - QUnit.test('load more records in column with x2many', function (assert) { + QUnit.test('load more records in column with x2many', async function (assert) { assert.expect(10); this.data.partner.records[0].category_ids = [7]; @@ -4128,7 +4172,7 @@ QUnit.module('Views', { // record [2] will be loaded after - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4146,7 +4190,7 @@ QUnit.module('Views', { }, mockRPC: function (route, args) { if (args.model === 'category' && args.method === 'read') { - assert.step(args.args[0]); + assert.step(String(args.args[0])); } if (route === '/web/dataset/search_read') { if (args.limit) { @@ -4165,25 +4209,25 @@ QUnit.module('Views', { assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_record').length, 2, "there should be 2 records in the column"); - assert.verifySteps([[7]], "only the appearing category should be fetched"); + assert.verifySteps(['7'], "only the appearing category should be fetched"); // load more - testUtils.dom.click(kanban.$('.o_kanban_group:eq(1)').find('.o_kanban_load_more')); + await testUtils.dom.click(kanban.$('.o_kanban_group:eq(1)').find('.o_kanban_load_more')); assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_record').length, 3, "there should now be 3 records in the column"); - assert.verifySteps([[7], [6]], "the other categories should not be fetched"); + assert.verifySteps(['6'], "the other categories should not be fetched"); kanban.destroy(); }); - QUnit.test('update buttons after column creation', function (assert) { + QUnit.test('update buttons after column creation', async function (assert) { assert.expect(2); this.data.partner.records = []; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4197,19 +4241,19 @@ QUnit.module('Views', { assert.isNotVisible(kanban.$buttons.find('.o-kanban-button-new'), "Create button should be hidden"); - testUtils.dom.click(kanban.$('.o_column_quick_create')); + await testUtils.dom.click(kanban.$('.o_column_quick_create')); kanban.$('.o_column_quick_create input').val('new column'); - testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); assert.isVisible(kanban.$buttons.find('.o-kanban-button-new'), "Create button should now be visible"); kanban.destroy(); }); - QUnit.test('group_by_tooltip option when grouping on a many2one', function (assert) { + QUnit.test('group_by_tooltip option when grouping on a many2one', async function (assert) { assert.expect(12); delete this.data.partner.records[3].product_id; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4236,7 +4280,7 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_group', 2, "should have " + 2 + " columns"); // simulate an update coming from the searchview, with another groupby given - kanban.update({groupBy: ['product_id']}); + await kanban.update({groupBy: ['product_id']}); assert.containsN(kanban, '.o_kanban_group', 3, "should have " + 3 + " columns"); assert.strictEqual(kanban.$('.o_kanban_group:nth-child(1) .o_kanban_record').length, 1, "column should contain 1 record(s)"); @@ -4257,12 +4301,12 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('move a record then put it again in the same column', function (assert) { + QUnit.test('move a record then put it again in the same column', async function (assert) { assert.expect(6); this.data.partner.records = []; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4274,18 +4318,18 @@ QUnit.module('Views', { groupBy: ['product_id'], }); - testUtils.dom.click(kanban.$('.o_column_quick_create')); + await testUtils.dom.click(kanban.$('.o_column_quick_create')); kanban.$('.o_column_quick_create input').val('column1'); - testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); - testUtils.dom.click(kanban.$('.o_column_quick_create')); + await testUtils.dom.click(kanban.$('.o_column_quick_create')); kanban.$('.o_column_quick_create input').val('column2'); - testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); - testUtils.dom.click(kanban.$('.o_kanban_group:eq(1) .o_kanban_quick_add i')); + await testUtils.dom.click(kanban.$('.o_kanban_group:eq(1) .o_kanban_quick_add i')); var $quickCreate = kanban.$('.o_kanban_group:eq(1) .o_kanban_quick_create'); - testUtils.fields.editInput($quickCreate.find('input'), 'new partner'); - testUtils.dom.click($quickCreate.find('button.o_kanban_add')); + await testUtils.fields.editInput($quickCreate.find('input'), 'new partner'); + await testUtils.dom.click($quickCreate.find('button.o_kanban_add')); assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 0, "column should contain 0 record"); @@ -4294,7 +4338,8 @@ QUnit.module('Views', { var $record = kanban.$('.o_kanban_group:eq(1) .o_kanban_record:eq(0)'); var $group = kanban.$('.o_kanban_group:eq(0)'); - testUtils.dom.dragAndDrop($record, $group); + await testUtils.dom.dragAndDrop($record, $group); + await nextTick(); // wait for resequencing after drag and drop assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 1, "column should contain 1 records"); @@ -4304,7 +4349,8 @@ QUnit.module('Views', { $record = kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)'); $group = kanban.$('.o_kanban_group:eq(1)'); - testUtils.dom.dragAndDrop($record, $group); + await testUtils.dom.dragAndDrop($record, $group); + await nextTick(); // wait for resequencing after drag and drop assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 0, "column should contain 0 records"); @@ -4313,13 +4359,13 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('resequence a record twice', function (assert) { + QUnit.test('resequence a record twice', async function (assert) { assert.expect(10); this.data.partner.records = []; var nbResequence = 0; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4332,25 +4378,25 @@ QUnit.module('Views', { mockRPC: function (route) { if (route === '/web/dataset/resequence') { nbResequence++; - return $.when(); + return Promise.resolve(); } return this._super.apply(this, arguments); }, }); - testUtils.dom.click(kanban.$('.o_column_quick_create')); + await testUtils.dom.click(kanban.$('.o_column_quick_create')); kanban.$('.o_column_quick_create input').val('column1'); - testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add')); - testUtils.dom.click(kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_add i')); + await testUtils.dom.click(kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_add i')); var $quickCreate = kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_create'); - testUtils.fields.editInput($quickCreate.find('input'), 'record1'); - testUtils.dom.click($quickCreate.find('button.o_kanban_add')); + await testUtils.fields.editInput($quickCreate.find('input'), 'record1'); + await testUtils.dom.click($quickCreate.find('button.o_kanban_add')); - testUtils.dom.click(kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_add i')); + await testUtils.dom.click(kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_add i')); $quickCreate = kanban.$('.o_kanban_group:eq(0) .o_kanban_quick_create'); - testUtils.fields.editInput($quickCreate.find('input'), 'record2'); - testUtils.dom.click($quickCreate.find('button.o_kanban_add')); + await testUtils.fields.editInput($quickCreate.find('input'), 'record2'); + await testUtils.dom.click($quickCreate.find('button.o_kanban_add')); assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 2, "column should contain 2 records"); @@ -4361,7 +4407,7 @@ QUnit.module('Views', { var $record1 = kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(1)'); var $record2 = kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)'); - testUtils.dom.dragAndDrop($record1, $record2, {position: 'top'}); + await testUtils.dom.dragAndDrop($record1, $record2, {position: 'top'}); assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 2, "column should contain 2 records"); @@ -4370,7 +4416,7 @@ QUnit.module('Views', { assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(1)').text(), "record2", "records should be correctly ordered"); - testUtils.dom.dragAndDrop($record2, $record1, {position: 'top'}); + await testUtils.dom.dragAndDrop($record2, $record1, {position: 'top'}); assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_record').length, 2, "column should contain 2 records"); @@ -4382,7 +4428,7 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('basic support for widgets', function (assert) { + QUnit.test('basic support for widgets', async function (assert) { assert.expect(1); var MyWidget = Widget.extend({ @@ -4395,7 +4441,7 @@ QUnit.module('Views', { }); widgetRegistry.add('test', MyWidget); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4415,10 +4461,10 @@ QUnit.module('Views', { delete widgetRegistry.map.test; }); - QUnit.test('column progressbars properly work', function (assert) { + QUnit.test('column progressbars properly work', async function (assert) { assert.expect(2); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4444,10 +4490,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('column progressbars should not crash in non grouped views', function (assert) { + QUnit.test('column progressbars should not crash in non grouped views', async function (assert) { assert.expect(3); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4475,10 +4521,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('column progressbars: creating a new column should create a new progressbar', function (assert) { + QUnit.test('column progressbars: creating a new column should create a new progressbar', async function (assert) { assert.expect(1); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4499,9 +4545,9 @@ QUnit.module('Views', { // Create a new column: this should create an empty progressbar var $columnQuickCreate = kanban.$('.o_column_quick_create'); - testUtils.dom.click($columnQuickCreate.find('.o_quick_create_folded')); + await testUtils.dom.click($columnQuickCreate.find('.o_quick_create_folded')); $columnQuickCreate.find('input').val('test'); - testUtils.dom.click($columnQuickCreate.find('.btn-primary')); + await testUtils.dom.click($columnQuickCreate.find('.btn-primary')); assert.containsN(kanban, '.o_kanban_counter', nbProgressBars + 1, "a new column with a new column progressbar should have been created"); @@ -4509,10 +4555,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('column progressbars on quick create properly update counter', function (assert) { + QUnit.test('column progressbars on quick create properly update counter', async function (assert) { assert.expect(1); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4529,20 +4575,22 @@ QUnit.module('Views', { }); var initialCount = parseInt(kanban.$('.o_kanban_counter_side:first').text()); - testUtils.dom.click(kanban.$('.o_kanban_quick_add:first')); - testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'Test'); - testUtils.dom.click(kanban.$('.o_kanban_add')); + await testUtils.dom.click(kanban.$('.o_kanban_quick_add:first')); + await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create input'), 'Test'); + await testUtils.dom.click(kanban.$('.o_kanban_add')); var lastCount = parseInt(kanban.$('.o_kanban_counter_side:first').text()); + await nextTick(); // await update + await nextTick(); // await read assert.strictEqual(lastCount, initialCount + 1, "kanban counters should have updated on quick create"); kanban.destroy(); }); - QUnit.test('column progressbars are working with load more', function (assert) { + QUnit.test('column progressbars are working with load more', async function (assert) { assert.expect(1); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4560,8 +4608,8 @@ QUnit.module('Views', { }); // we have 1 record shown, load 2 more and check it worked - testUtils.dom.click(kanban.$('.o_kanban_group').find('.o_kanban_load_more')); - testUtils.dom.click(kanban.$('.o_kanban_group').find('.o_kanban_load_more')); + await testUtils.dom.click(kanban.$('.o_kanban_group').find('.o_kanban_load_more')); + await testUtils.dom.click(kanban.$('.o_kanban_group').find('.o_kanban_load_more')); var shownIDs = _.map(kanban.$('.o_kanban_record'), function(record) { return parseInt(record.innerText); }); @@ -4570,13 +4618,13 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('column progressbars on archiving records update counter', function (assert) { + QUnit.test('column progressbars on archiving records update counter', async function (assert) { assert.expect(4); // add active field on partner model and make all records active this.data.partner.fields.active = {string: 'Active', type: 'char', default: true}; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4602,8 +4650,8 @@ QUnit.module('Views', { // archive all records of the second columns testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:eq(1)')); - testUtils.dom.click(kanban.$('.o_column_archive_records:visible')); - testUtils.dom.click($('.modal-footer button:first')); + await testUtils.dom.click(kanban.$('.o_column_archive_records:visible')); + await testUtils.dom.click($('.modal-footer button:first')); assert.strictEqual(kanban.$('.o_kanban_group:eq(1) .o_kanban_counter_side').text(), "0", "counter should contain the correct value"); @@ -4613,13 +4661,13 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('kanban with progressbars: correctly update env when archiving records', function (assert) { + QUnit.test('kanban with progressbars: correctly update env when archiving records', async function (assert) { assert.expect(2); // add active field on partner model and make all records active this.data.partner.fields.active = {string: 'Active', type: 'char', default: true}; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4642,18 +4690,18 @@ QUnit.module('Views', { // archive all records of the first column testUtils.kanban.toggleGroupSettings(kanban.$('.o_kanban_group:first')); - testUtils.dom.click(kanban.$('.o_column_archive_records:visible')); - testUtils.dom.click($('.modal-footer button:first')); + await testUtils.dom.click(kanban.$('.o_column_archive_records:visible')); + await testUtils.dom.click($('.modal-footer button:first')); assert.deepEqual(kanban.exportState().resIds, [1, 2, 3]); kanban.destroy(); }); - QUnit.test('RPCs when (re)loading kanban view progressbars', function (assert) { + QUnit.test('RPCs when (re)loading kanban view progressbars', async function (assert) { assert.expect(9); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4675,30 +4723,30 @@ QUnit.module('Views', { }, }); - kanban.reload(); + await kanban.reload(); assert.verifySteps([ // initial load 'read_group', + 'read_progress_bar', '/web/dataset/search_read', '/web/dataset/search_read', - 'read_progress_bar', // reload 'read_group', + 'read_progress_bar', '/web/dataset/search_read', '/web/dataset/search_read', - 'read_progress_bar', ]); kanban.destroy(); }); - QUnit.test('drag & drop records grouped by m2o with progressbar', function (assert) { + QUnit.test('drag & drop records grouped by m2o with progressbar', async function (assert) { assert.expect(4); this.data.partner.records[0].product_id = false; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4714,7 +4762,7 @@ QUnit.module('Views', { groupBy: ['product_id'], mockRPC: function (route, args) { if (route === '/web/dataset/resequence') { - return $.when(true); + return Promise.resolve(true); } return this._super(route, args); }, @@ -4723,25 +4771,28 @@ QUnit.module('Views', { assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_counter_side').text(), "1", "counter should contain the correct value"); - testUtils.dom.dragAndDrop(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)'), kanban.$('.o_kanban_group:eq(1)')); + await testUtils.dom.dragAndDrop(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)'), kanban.$('.o_kanban_group:eq(1)')); + await nextTick(); // wait for update resulting from drag and drop assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_counter_side').text(), "0", "counter should contain the correct value"); - testUtils.dom.dragAndDrop(kanban.$('.o_kanban_group:eq(1) .o_kanban_record:eq(2)'), kanban.$('.o_kanban_group:eq(0)')); + await testUtils.dom.dragAndDrop(kanban.$('.o_kanban_group:eq(1) .o_kanban_record:eq(2)'), kanban.$('.o_kanban_group:eq(0)')); + await nextTick(); // wait for update resulting from drag and drop assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_counter_side').text(), "1", "counter should contain the correct value"); - testUtils.dom.dragAndDrop(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)'), kanban.$('.o_kanban_group:eq(1)')); + await testUtils.dom.dragAndDrop(kanban.$('.o_kanban_group:eq(0) .o_kanban_record:eq(0)'), kanban.$('.o_kanban_group:eq(1)')); + await nextTick(); // wait for update resulting from drag and drop assert.strictEqual(kanban.$('.o_kanban_group:eq(0) .o_kanban_counter_side').text(), "0", "counter should contain the correct value"); kanban.destroy(); }); - QUnit.test('progress bar subgroup count recompute', function (assert) { + QUnit.test('progress bar subgroup count recompute', async function (assert) { assert.expect(2); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4761,7 +4812,7 @@ QUnit.module('Views', { var initialCount = parseInt($secondGroup.find('.o_kanban_counter_side').text()); assert.strictEqual(initialCount, 3, "Initial count should be Three"); - testUtils.dom.click($secondGroup.find('.bg-success-full')); + await testUtils.dom.click($secondGroup.find('.bg-success-full')); var lastCount = parseInt($secondGroup.find('.o_kanban_counter_side').text()); assert.strictEqual(lastCount, 1, "kanban counters should vary according to what subgroup is selected"); @@ -4769,10 +4820,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('column progressbars on quick create with quick_create_view are updated', function (assert) { + QUnit.test('column progressbars on quick create with quick_create_view are updated', async function (assert) { assert.expect(1); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4795,11 +4846,11 @@ QUnit.module('Views', { var initialCount = parseInt(kanban.$('.o_kanban_counter_side:first').text()); - testUtils.kanban.clickCreate(kanban); + await testUtils.kanban.clickCreate(kanban); // fill the quick create and validate var $quickCreate = kanban.$('.o_kanban_group:first .o_kanban_quick_create'); - testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=int_field]'), '44'); - testUtils.dom.click($quickCreate.find('button.o_kanban_add')); + await testUtils.fields.editInput($quickCreate.find('.o_field_widget[name=int_field]'), '44'); + await testUtils.dom.click($quickCreate.find('button.o_kanban_add')); var lastCount = parseInt(kanban.$('.o_kanban_counter_side:first').text()); assert.strictEqual(lastCount, initialCount + 44, @@ -4808,10 +4859,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('keep adding quickcreate in first column after a record from this column was moved', function (assert) { + QUnit.test('keep adding quickcreate in first column after a record from this column was moved', async function (assert) { assert.expect(2); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4825,7 +4876,7 @@ QUnit.module('Views', { groupBy: ['foo'], mockRPC: function (route, args) { if (route === '/web/dataset/resequence') { - return $.when(true); + return Promise.resolve(true); } return this._super(route, args); }, @@ -4833,13 +4884,13 @@ QUnit.module('Views', { var $quickCreateGroup; var $groups; - _quickCreateAndTest(); - testUtils.dom.dragAndDrop($groups.first().find('.o_kanban_record:first'), $groups.eq(1)); - _quickCreateAndTest(); + await _quickCreateAndTest(); + await testUtils.dom.dragAndDrop($groups.first().find('.o_kanban_record:first'), $groups.eq(1)); + await _quickCreateAndTest(); kanban.destroy(); - function _quickCreateAndTest() { - testUtils.kanban.clickCreate(kanban); + async function _quickCreateAndTest() { + await testUtils.kanban.clickCreate(kanban); $quickCreateGroup = kanban.$('.o_kanban_quick_create').closest('.o_kanban_group'); $groups = kanban.$('.o_kanban_group'); assert.strictEqual($quickCreateGroup[0], $groups[0], @@ -4847,10 +4898,10 @@ QUnit.module('Views', { } }); - QUnit.test('test displaying image (URL)', function (assert) { + QUnit.test('test displaying image (URL)', async function (assert) { assert.expect(1); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4869,10 +4920,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('test displaying image (binary & placeholder)', function (assert) { + QUnit.test('test displaying image (binary & placeholder)', async function (assert) { assert.expect(2); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4886,7 +4937,7 @@ QUnit.module('Views', { mockRPC: function (route, args) { if (route === 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACAA==') { assert.ok("The view's image should have been fetched."); - return $.when(); + return Promise.resolve(); } return this._super.apply(this, arguments); }, @@ -4899,7 +4950,7 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('check if the view destroys all widgets and instances', function (assert) { + QUnit.test('check if the view destroys all widgets and instances', async function (assert) { assert.expect(1); var instanceNumber = 0; @@ -4936,13 +4987,13 @@ QUnit.module('Views', { '</kanban>', }; - var kanban = createView(params); + var kanban = await createView(params); kanban.destroy(); var initialInstanceNumber = instanceNumber; instanceNumber = 0; - kanban = createView(params); + kanban = await createView(params); // call destroy function of controller to ensure that it correctly destroys everything kanban.__destroy(); @@ -4956,15 +5007,15 @@ QUnit.module('Views', { testUtils.mock.unpatch(mixins.ParentedMixin); }); - QUnit.test('grouped kanban becomes ungrouped when clearing domain then clearing groupby', function (assert) { + QUnit.test('grouped kanban becomes ungrouped when clearing domain then clearing groupby', async function (assert) { // in this test, we simulate that clearing the domain is slow, so that // clearing the groupby does not corrupt the data handled while // reloading the kanban view. assert.expect(4); - var def = $.Deferred(); + var prom = makeTestPromise(); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -4982,7 +5033,9 @@ QUnit.module('Views', { args.kwargs.groupby && args.kwargs.groupby[0] === 'bar'; if (isFirstUpdate) { - return def.then(_.constant(result)); + return prom.then(function () { + return result; + }); } } return result; @@ -4996,8 +5049,9 @@ QUnit.module('Views', { kanban.update({domain: []}); // 1st update on kanban view kanban.update({groupBy: false}); // 2n update on kanban view - def.resolve(); // simulate slow 1st update of kanban view + prom.resolve(); // simulate slow 1st update of kanban view + await nextTick(); assert.doesNotHaveClass(kanban.$('.o_kanban_view'), 'o_kanban_grouped', "the kanban view should not longer be grouped"); assert.hasClass(kanban.$('.o_kanban_view'),'o_kanban_ungrouped', @@ -5006,10 +5060,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('quick_create on grouped kanban without column', function (assert) { + QUnit.test('quick_create on grouped kanban without column', async function (assert) { assert.expect(1); this.data.partner.records = []; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -5027,14 +5081,14 @@ QUnit.module('Views', { }, }, }); - testUtils.kanban.clickCreate(kanban); + await testUtils.kanban.clickCreate(kanban); kanban.destroy(); }); - QUnit.test('keyboard navigation on kanban basic rendering', function (assert) { + QUnit.test('keyboard navigation on kanban basic rendering', async function (assert) { assert.expect(3); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -5060,10 +5114,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('keyboard navigation on kanban grouped rendering', function (assert) { + QUnit.test('keyboard navigation on kanban grouped rendering', async function (assert) { assert.expect(3); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -5096,13 +5150,13 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('keyboard navigation on kanban grouped rendering with empty columns', function (assert) { + QUnit.test('keyboard navigation on kanban grouped rendering with empty columns', async function (assert) { assert.expect(2); var data = this.data; data.partner.records[1].state = "abc"; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: data, @@ -5165,9 +5219,9 @@ QUnit.module('Views', { }); QUnit.test('keyboard navigation on kanban when the focus is on a link that ' + - 'has an action and the kanban has no oe_kanban_global_... class', function (assert) { + 'has an action and the kanban has no oe_kanban_global_... class', async function (assert) { assert.expect(1); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -5188,18 +5242,19 @@ QUnit.module('Views', { keyCode: $.ui.keyCode.ENTER, which: $.ui.keyCode.ENTER, })); + await testUtils.nextTick(); kanban.destroy(); }); - QUnit.test('asynchronous rendering of a field widget (ungrouped)', function (assert) { - assert.expect(2); + QUnit.test('asynchronous rendering of a field widget (ungrouped)', async function (assert) { + assert.expect(4); - var fooFieldDef = $.Deferred(); + var fooFieldProm = makeTestPromise(); var FieldChar = fieldRegistry.get('char'); fieldRegistry.add('asyncwidget', FieldChar.extend({ willStart: function () { - return fooFieldDef; + return fooFieldProm; }, start: function () { this.$el.html('LOADED'); @@ -5207,7 +5262,7 @@ QUnit.module('Views', { })); var kanbanController; - testUtils.createAsyncView({ + testUtils.createView({ View: KanbanView, model: 'partner', data: this.data, @@ -5220,21 +5275,33 @@ QUnit.module('Views', { assert.strictEqual($('.o_kanban_record').length, 0, "kanban view is not ready yet"); - fooFieldDef.resolve(); + fooFieldProm.resolve(); + await nextTick(); + assert.strictEqual($('.o_kanban_record').text(), "LOADEDLOADEDLOADEDLOADED"); + + // reload with a domain + fooFieldProm = makeTestPromise(); + kanbanController.reload({domain: [['id', '=', 1]]}); + await nextTick(); + assert.strictEqual($('.o_kanban_record').text(), "LOADEDLOADEDLOADEDLOADED"); + fooFieldProm.resolve(); + await nextTick(); + assert.strictEqual($('.o_kanban_record').text(), "LOADED"); + kanbanController.destroy(); delete fieldRegistry.map.asyncWidget; }); - QUnit.test('asynchronous rendering of a field widget (grouped)', function (assert) { - assert.expect(2); + QUnit.test('asynchronous rendering of a field widget (grouped)', async function (assert) { + assert.expect(4); - var fooFieldDef = $.Deferred(); + var fooFieldProm = makeTestPromise(); var FieldChar = fieldRegistry.get('char'); fieldRegistry.add('asyncwidget', FieldChar.extend({ willStart: function () { - return fooFieldDef; + return fooFieldProm; }, start: function () { this.$el.html('LOADED'); @@ -5242,7 +5309,7 @@ QUnit.module('Views', { })); var kanbanController; - testUtils.createAsyncView({ + testUtils.createView({ View: KanbanView, model: 'partner', data: this.data, @@ -5256,13 +5323,24 @@ QUnit.module('Views', { assert.strictEqual($('.o_kanban_record').length, 0, "kanban view is not ready yet"); - fooFieldDef.resolve(); + fooFieldProm.resolve(); + await nextTick(); + assert.strictEqual($('.o_kanban_record').text(), "LOADEDLOADEDLOADEDLOADED"); + + // reload with a domain + fooFieldProm = makeTestPromise(); + kanbanController.reload({domain: [['id', '=', 1]]}); + await nextTick(); + assert.strictEqual($('.o_kanban_record').text(), "LOADEDLOADEDLOADEDLOADED"); + fooFieldProm.resolve(); + await nextTick(); + assert.strictEqual($('.o_kanban_record').text(), "LOADED"); + kanbanController.destroy(); delete fieldRegistry.map.asyncWidget; }); - }); }); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index f5e0088173b..3b9e5836cb5 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -15,6 +15,7 @@ var Widget = require('web.Widget'); var createView = testUtils.createView; + QUnit.module('Views', { beforeEach: function () { this.data = { @@ -95,10 +96,10 @@ QUnit.module('Views', { QUnit.module('ListView'); - QUnit.test('simple readonly list', function (assert) { + QUnit.test('simple readonly list', async function (assert) { assert.expect(10); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -120,19 +121,16 @@ QUnit.module('Views', { assert.strictEqual(list.$('tbody tr:first td:nth(2)').css('text-align'), 'right', "integer cells should be right aligned"); - assert.ok(list.$buttons.find('.o_list_button_add').is(':visible'), - "should have a visible Create button"); - assert.ok(!list.$buttons.find('.o_list_button_save').is(':visible'), - "should not have a visible save button"); - assert.ok(!list.$buttons.find('.o_list_button_discard').is(':visible'), - "should not have a visible save button"); + assert.isVisible(list.$buttons.find('.o_list_button_add')); + assert.isNotVisible(list.$buttons.find('.o_list_button_save')); + assert.isNotVisible(list.$buttons.find('.o_list_button_discard')); list.destroy(); }); - QUnit.test('list with create="0"', function (assert) { + QUnit.test('list with create="0"', async function (assert) { assert.expect(2); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -141,16 +139,16 @@ QUnit.module('Views', { assert.hasClass(list.$el,'o_cannot_create', "should have className 'o_cannot_create'"); - assert.strictEqual(list.$buttons.find('.o_list_button_add').length, 0, + assert.containsNone(list.$buttons, '.o_list_button_add', "should not have the 'Create' button"); list.destroy(); }); - QUnit.test('list with delete="0"', function (assert) { + QUnit.test('list with delete="0"', async function (assert) { assert.expect(4); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -161,17 +159,17 @@ QUnit.module('Views', { assert.isNotVisible(list.sidebar.$el, 'sidebar should be invisible'); assert.ok(list.$('tbody td.o_list_record_selector').length, 'should have at least one record'); - testUtils.dom.click(list.$('tbody td.o_list_record_selector:first input')); + await testUtils.dom.click(list.$('tbody td.o_list_record_selector:first input')); assert.isVisible(list.sidebar.$el, 'sidebar should be visible'); assert.notOk(list.sidebar.$('a:contains(Delete)').length, 'sidebar should not have Delete button'); list.destroy(); }); - QUnit.test('simple editable rendering', function (assert) { + QUnit.test('simple editable rendering', async function (assert) { assert.expect(12); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -180,39 +178,39 @@ QUnit.module('Views', { assert.containsN(list, 'th', 3, "should have 2 th"); assert.containsN(list, 'th', 3, "should have 3 th"); - assert.strictEqual(list.$('td:contains(yop)').length, 1, "should contain yop"); + assert.containsOnce(list, 'td:contains(yop)', "should contain yop"); - assert.ok(list.$buttons.find('.o_list_button_add').is(':visible'), + assert.isVisible(list.$buttons.find('.o_list_button_add'), "should have a visible Create button"); - assert.ok(!list.$buttons.find('.o_list_button_save').is(':visible'), + assert.isNotVisible(list.$buttons.find('.o_list_button_save'), "should not have a visible save button"); - assert.ok(!list.$buttons.find('.o_list_button_discard').is(':visible'), + assert.isNotVisible(list.$buttons.find('.o_list_button_discard'), "should not have a visible discard button"); - testUtils.dom.click(list.$('td:not(.o_list_record_selector)').first()); + await testUtils.dom.click(list.$('td:not(.o_list_record_selector)').first()); - assert.ok(!list.$buttons.find('.o_list_button_add').is(':visible'), + assert.isNotVisible(list.$buttons.find('.o_list_button_add'), "should not have a visible Create button"); - assert.ok(list.$buttons.find('.o_list_button_save').is(':visible'), + assert.isVisible(list.$buttons.find('.o_list_button_save'), "should have a visible save button"); - assert.ok(list.$buttons.find('.o_list_button_discard').is(':visible'), + assert.isVisible(list.$buttons.find('.o_list_button_discard'), "should have a visible discard button"); - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); - assert.ok(list.$buttons.find('.o_list_button_add').is(':visible'), + assert.isVisible(list.$buttons.find('.o_list_button_add'), "should have a visible Create button"); - assert.ok(!list.$buttons.find('.o_list_button_save').is(':visible'), + assert.isNotVisible(list.$buttons.find('.o_list_button_save'), "should not have a visible save button"); - assert.ok(!list.$buttons.find('.o_list_button_discard').is(':visible'), + assert.isNotVisible(list.$buttons.find('.o_list_button_discard'), "should not have a visible discard button"); list.destroy(); }); - QUnit.test('invisible columns are not displayed', function (assert) { + QUnit.test('invisible columns are not displayed', async function (assert) { assert.expect(1); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -227,10 +225,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('record-depending invisible lines are correctly aligned', function (assert) { + QUnit.test('record-depending invisible lines are correctly aligned', async function (assert) { assert.expect(4); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -247,17 +245,17 @@ QUnit.module('Views', { "there should be 1 invisible bar cell"); assert.hasClass(list.$('tbody tr:first td:eq(2)'),'o_invisible_modifier', "the 3rd cell should be invisible"); - assert.strictEqual(list.$('tbody tr:eq(0) td:visible').length, list.$('tbody tr:eq(1) td:visible').length, + assert.containsN(list, 'tbody tr:eq(0) td:visible', list.$('tbody tr:eq(1) td:visible').length, "there should be the same number of visible cells in different rows"); list.destroy(); }); - QUnit.test('do not perform extra RPC to read invisible many2one fields', function (assert) { + QUnit.test('do not perform extra RPC to read invisible many2one fields', async function (assert) { assert.expect(3); this.data.foo.fields.m2o.default = 2; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -271,16 +269,16 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); assert.verifySteps(['search_read', 'default_get'], "no nameget should be done"); list.destroy(); }); - QUnit.test('at least 4 rows are rendered, even if less data', function (assert) { + QUnit.test('at least 4 rows are rendered, even if less data', async function (assert) { assert.expect(1); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -292,10 +290,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('basic grouped list rendering', function (assert) { + QUnit.test('basic grouped list rendering', async function (assert) { assert.expect(4); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -310,10 +308,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('basic grouped list rendering 1 col without selector', function (assert) { + QUnit.test('basic grouped list rendering 1 col without selector', async function (assert) { assert.expect(1); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -327,10 +325,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('basic grouped list rendering 1 col with selector', function (assert) { + QUnit.test('basic grouped list rendering 1 col with selector', async function (assert) { assert.expect(1); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -344,10 +342,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('basic grouped list rendering 2 col without selector', function (assert) { + QUnit.test('basic grouped list rendering 2 col without selector', async function (assert) { assert.expect(1); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -361,10 +359,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('basic grouped list rendering 2 col with selector', function (assert) { + QUnit.test('basic grouped list rendering 2 col with selector', async function (assert) { assert.expect(1); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -378,14 +376,14 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('ordered list, sort attribute in context', function (assert) { + QUnit.test('ordered list, sort attribute in context', async function (assert) { assert.expect(1); // Equivalent to saving a custom filter this.data.foo.fields.foo.sortable = true; this.data.foo.fields.date.sortable = true; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -396,11 +394,11 @@ QUnit.module('Views', { }); // Descending order on Foo - list.$('th.o_column_sortable:contains("Foo")').click(); - list.$('th.o_column_sortable:contains("Foo")').click(); + await testUtils.dom.click(list.$('th.o_column_sortable:contains("Foo")')); + await testUtils.dom.click(list.$('th.o_column_sortable:contains("Foo")')); // Ascending order on Date - list.$('th.o_column_sortable:contains("Date")').click(); + await testUtils.dom.click(list.$('th.o_column_sortable:contains("Date")')); var listContext = list.getOwnedQueryParams(); assert.deepEqual(listContext, @@ -416,14 +414,14 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('loading a filter with a sort attribute', function (assert) { + QUnit.test('Loading a filter with a sort attribute', async function (assert) { assert.expect(2); this.data.foo.fields.foo.sortable = true; this.data.foo.fields.date.sortable = true; var searchReads = 0; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -446,7 +444,7 @@ QUnit.module('Views', { }, intercepts: { load_filters: function (event) { - return $.when([ + return Promise.resolve([ { context: "{}", domain: "[]", @@ -469,15 +467,15 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(list.$('.o_control_panel .o_search_options button.o_favorites_menu_button')); - testUtils.dom.click(list.$('.o_control_panel .o_search_options .o_favorites_menu .o_menu_item').eq(1)); + await testUtils.dom.click(list.$('.o_control_panel .o_search_options button.o_favorites_menu_button')); + await testUtils.dom.click(list.$('.o_control_panel .o_search_options .o_favorites_menu .o_menu_item').eq(1)); list.destroy(); }); - QUnit.test('many2one field rendering', function (assert) { + QUnit.test('many2one field rendering', async function (assert) { assert.expect(1); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -489,10 +487,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('grouped list view, with 1 open group', function (assert) { + QUnit.test('grouped list view, with 1 open group', async function (assert) { assert.expect(6); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -500,20 +498,21 @@ QUnit.module('Views', { groupBy: ['foo'], }); - testUtils.dom.click(list.$('th.o_group_name:nth(1)')); - assert.strictEqual(list.$('tbody:eq(1) tr').length, 2, "open group should contain 2 records"); + await testUtils.dom.click(list.$('th.o_group_name:nth(1)')); + await testUtils.nextTick(); + assert.containsN(list, 'tbody:eq(1) tr', 2, "open group should contain 2 records"); assert.containsN(list, 'tbody', 3, "should contain 3 tbody"); - assert.strictEqual(list.$('td:contains(9)').length, 1, "should contain 9"); - assert.strictEqual(list.$('td:contains(-4)').length, 1, "should contain -4"); - assert.strictEqual(list.$('td:contains(10)').length, 1, "should contain 10"); - assert.strictEqual(list.$('tr.o_group_header td:contains(10)').length, 1, "but 10 should be in a header"); + assert.containsOnce(list, 'td:contains(9)', "should contain 9"); + assert.containsOnce(list, 'td:contains(-4)', "should contain -4"); + assert.containsOnce(list, 'td:contains(10)', "should contain 10"); + assert.containsOnce(list, 'tr.o_group_header td:contains(10)', "but 10 should be in a header"); list.destroy(); }); - QUnit.test('opening records when clicking on record', function (assert) { + QUnit.test('opening records when clicking on record', async function (assert) { assert.expect(3); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -526,19 +525,20 @@ QUnit.module('Views', { testUtils.dom.click(list.$('tr td:not(.o_list_record_selector)').first()); list.update({groupBy: ['foo']}); + await testUtils.nextTick(); assert.containsN(list, 'tr.o_group_header', 3, "list should be grouped"); - testUtils.dom.click(list.$('th.o_group_name').first()); + await testUtils.dom.click(list.$('th.o_group_name').first()); testUtils.dom.click(list.$('tr:not(.o_group_header) td:not(.o_list_record_selector)').first()); list.destroy(); }); - QUnit.test('editable list view: readonly fields cannot be edited', function (assert) { + QUnit.test('editable list view: readonly fields cannot be edited', async function (assert) { assert.expect(4); this.data.foo.fields.foo.readonly = true; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -551,7 +551,7 @@ QUnit.module('Views', { var $td = list.$('td:not(.o_list_record_selector)').first(); var $second_td = list.$('td:not(.o_list_record_selector)').eq(1); var $third_td = list.$('td:not(.o_list_record_selector)').eq(2); - testUtils.dom.click($td); + await testUtils.dom.click($td); assert.hasClass($td.parent(),'o_selected_row', "row should be in edit mode"); assert.hasClass($td,'o_readonly_modifier', @@ -563,7 +563,7 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('editable list view: line with no active element', function (assert) { + QUnit.test('editable list view: line with no active element', async function (assert) { assert.expect(3); this.data.bar = { @@ -578,7 +578,7 @@ QUnit.module('Views', { }; this.data.foo.records[0].o2m = [1, 2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'foo', data: this.data, @@ -606,18 +606,19 @@ QUnit.module('Views', { var $td2 = form.$('.o_data_cell').eq(1); assert.hasClass($td, 'o_readonly_modifier'); assert.hasClass($td2, 'o_boolean_toggle_cell'); - testUtils.dom.click($td); - testUtils.dom.click($td2.find('.o_boolean_toggle input')); - - testUtils.form.clickSave(form); + await testUtils.dom.click($td); + await testUtils.dom.click($td2.find('.o_boolean_toggle input')); + await testUtils.nextTick(); + await testUtils.form.clickSave(form); + await testUtils.nextTick(); form.destroy(); }); - QUnit.test('basic operations for editable list renderer', function (assert) { + QUnit.test('basic operations for editable list renderer', async function (assert) { assert.expect(2); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -626,12 +627,12 @@ QUnit.module('Views', { var $td = list.$('td:not(.o_list_record_selector)').first(); assert.doesNotHaveClass($td.parent(), 'o_selected_row', "td should not be in edit mode"); - testUtils.dom.click($td); + await testUtils.dom.click($td); assert.hasClass($td.parent(),'o_selected_row', "td should be in edit mode"); list.destroy(); }); - QUnit.test('editable list: add a line and discard', function (assert) { + QUnit.test('editable list: add a line and discard', async function (assert) { assert.expect(11); testUtils.mock.patch(basicFields.FieldChar, { @@ -641,7 +642,7 @@ QUnit.module('Views', { }, }); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -656,7 +657,7 @@ QUnit.module('Views', { assert.strictEqual(list.pager.$('.o_pager_value').text(), '1-1', "pager should be correct"); - testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); assert.containsN(list, 'tbody tr', 4, "list should still contain 4 rows"); @@ -665,7 +666,7 @@ QUnit.module('Views', { assert.strictEqual(list.pager.$('.o_pager_value').text(), '1-2', "pager should be correct"); - testUtils.dom.click(list.$buttons.find('.o_list_button_discard')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_discard')); assert.containsN(list, 'tbody tr', 4, "list should still contain 4 rows"); @@ -680,10 +681,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('field changes are triggered correctly', function (assert) { + QUnit.test('field changes are triggered correctly', async function (assert) { assert.expect(2); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -695,30 +696,31 @@ QUnit.module('Views', { testUtils.mock.intercept(list, "field_changed", function () { n += 1; }); - testUtils.dom.click($td); + await testUtils.dom.click($td); testUtils.fields.editInput($td.find('input'), 'abc'); assert.strictEqual(n, 1, "field_changed should have been triggered"); - testUtils.dom.click(list.$('td:not(.o_list_record_selector)').eq(2)); + await testUtils.dom.click(list.$('td:not(.o_list_record_selector)').eq(2)); assert.strictEqual(n, 1, "field_changed should not have been triggered"); list.destroy(); }); - QUnit.test('editable list view: basic char field edition', function (assert) { + QUnit.test('editable list view: basic char field edition', async function (assert) { assert.expect(4); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, arch: '<tree editable="bottom"><field name="foo"/><field name="bar"/></tree>', }); + var $td = list.$('td:not(.o_list_record_selector)').first(); - testUtils.dom.click($td); - testUtils.fields.editInput($td.find('input'), 'abc'); + await testUtils.dom.click($td); + await testUtils.fields.editInput($td.find('input'), 'abc'); assert.strictEqual($td.find('input').val(), 'abc', "char field has been edited correctly"); var $next_row_td = list.$('tbody tr:eq(1) td:not(.o_list_record_selector)').first(); - testUtils.dom.click($next_row_td); + await testUtils.dom.click($next_row_td); assert.strictEqual(list.$('td:not(.o_list_record_selector)').first().text(), 'abc', 'changes should be saved correctly'); assert.doesNotHaveClass(list.$('tbody tr').first(), 'o_selected_row', @@ -728,12 +730,12 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('editable list view: save data when list sorting in edit mode', function (assert) { + QUnit.test('editable list view: save data when list sorting in edit mode', async function (assert) { assert.expect(3); this.data.foo.fields.foo.sortable = true; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -744,27 +746,27 @@ QUnit.module('Views', { "should correctly save the edited record"); } return this._super.apply(this, arguments); - } + }, }); - testUtils.dom.click(list.$('.o_data_cell:first')); - testUtils.fields.editInput(list.$('input[name="foo"]'), 'xyz'); - testUtils.dom.click(list.$('.o_column_sortable')); + await testUtils.dom.click(list.$('.o_data_cell:first')); + await testUtils.fields.editInput(list.$('input[name="foo"]'), 'xyz'); + await testUtils.dom.click(list.$('.o_column_sortable')); assert.hasClass(list.$('.o_data_row:first'),'o_selected_row', "first row should still be in edition"); - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); assert.doesNotHaveClass(list.$buttons, 'o-editing', "list buttons should be back to their readonly mode"); list.destroy(); }); - QUnit.test('selection changes are triggered correctly', function (assert) { + QUnit.test('selection changes are triggered correctly', async function (assert) { assert.expect(8); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -800,10 +802,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('selection is reset on reload', function (assert) { + QUnit.test('selection is reset on reload', async function (assert) { assert.expect(5); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -824,7 +826,7 @@ QUnit.module('Views', { "total should be 10 (first record selected)"); // reload - list.reload(); + await list.reload(); $firstRowSelector = list.$('tbody .o_list_record_selector input').first(); assert.notOk($firstRowSelector.is(':checked'), "first row should no longer be selected"); @@ -834,10 +836,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('selection is kept on render without reload', function (assert) { + QUnit.test('selection is kept on render without reload', async function (assert) { assert.expect(6); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -852,19 +854,19 @@ QUnit.module('Views', { assert.isNotVisible(list.sidebar); // open blip grouping and check all lines - testUtils.dom.click(list.$('.o_group_header:contains("blip (2)")')); - testUtils.dom.click(list.$('.o_data_row:first input')); + await testUtils.dom.click(list.$('.o_group_header:contains("blip (2)")')); + await testUtils.dom.click(list.$('.o_data_row:first input')); assert.isVisible(list.sidebar); // open yop grouping and verify blip are still checked - testUtils.dom.click(list.$('.o_group_header:contains("yop (1)")')); + await testUtils.dom.click(list.$('.o_group_header:contains("yop (1)")')); assert.containsOnce(list, '.o_data_row input:checked', "opening a grouping does not uncheck others"); assert.isVisible(list.sidebar); // close and open blip grouping and verify blip are unchecked - testUtils.dom.click(list.$('.o_group_header:contains("blip (2)")')); - testUtils.dom.click(list.$('.o_group_header:contains("blip (2)")')); + await testUtils.dom.click(list.$('.o_group_header:contains("blip (2)")')); + await testUtils.dom.click(list.$('.o_group_header:contains("blip (2)")')); assert.containsNone(list, '.o_data_row input:checked', "opening and closing a grouping uncheck its elements"); assert.isNotVisible(list.sidebar); @@ -872,10 +874,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('aggregates are computed correctly', function (assert) { + QUnit.test('aggregates are computed correctly', async function (assert) { assert.expect(4); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -896,16 +898,16 @@ QUnit.module('Views', { "total should be 32 as all records are selected"); // Let's update the view to dislay NO records - list.update({domain: ['&', ['bar', '=', false], ['int_field', '>', 0]]}); + await list.update({domain: ['&', ['bar', '=', false], ['int_field', '>', 0]]}); assert.strictEqual(list.$('tfoot td:nth(2)').text(), "0", "total should have been recomputed to 0"); list.destroy(); }); - QUnit.test('aggregates are computed correctly in grouped lists', function (assert) { + QUnit.test('aggregates are computed correctly in grouped lists', async function (assert) { assert.expect(4); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -923,17 +925,17 @@ QUnit.module('Views', { assert.strictEqual($groupHeader2.find('td:last()').text(), "9", "second group total should be 9"); assert.strictEqual(list.$('tfoot td:last()').text(), "32", "total should be 32"); - testUtils.dom.click($groupHeader1); - testUtils.dom.click(list.$('tbody .o_list_record_selector input').first()); + await testUtils.dom.click($groupHeader1); + await testUtils.dom.click(list.$('tbody .o_list_record_selector input').first()); assert.strictEqual(list.$('tfoot td:last()').text(), "10", "total should be 10 as first record of first group is selected"); list.destroy(); }); - QUnit.test('aggregates are updated when a line is edited', function (assert) { + QUnit.test('aggregates are updated when a line is edited', async function (assert) { assert.expect(2); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -942,18 +944,18 @@ QUnit.module('Views', { assert.strictEqual(list.$('td[title="Sum"]').text(), "32", "current total should be 32"); - testUtils.dom.click(list.$('tr.o_data_row td.o_data_cell').first()); - testUtils.fields.editInput(list.$('td.o_data_cell input'), "15"); + await testUtils.dom.click(list.$('tr.o_data_row td.o_data_cell').first()); + await testUtils.fields.editInput(list.$('td.o_data_cell input'), "15"); assert.strictEqual(list.$('td[title="Sum"]').text(), "37", "current total should now be 37"); list.destroy(); }); - QUnit.test('aggregates are formatted according to field widget', function (assert) { + QUnit.test('aggregates are formatted according to field widget', async function (assert) { assert.expect(1); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -969,9 +971,9 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('groups can be sorted on aggregates', function (assert) { + QUnit.test('groups can be sorted on aggregates', async function (assert) { assert.expect(10); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -989,12 +991,12 @@ QUnit.module('Views', { "initial order should be 10, 5, 17"); assert.strictEqual(list.$('tfoot td:last()').text(), '32', "total should be 32"); - testUtils.dom.click(list.$('.o_column_sortable')); + await testUtils.dom.click(list.$('.o_column_sortable')); assert.strictEqual(list.$('tfoot td:last()').text(), '32', "total should still be 32"); assert.strictEqual(list.$('tbody .o_list_number').text(), '51017', "order should be 5, 10, 17"); - testUtils.dom.click(list.$('.o_column_sortable')); + await testUtils.dom.click(list.$('.o_column_sortable')); assert.strictEqual(list.$('tbody .o_list_number').text(), '17105', "initial order should be 17, 10, 5"); assert.strictEqual(list.$('tfoot td:last()').text(), '32', "total should still be 32"); @@ -1004,14 +1006,14 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('groups cannot be sorted on non-aggregable fields', function (assert) { + QUnit.test('groups cannot be sorted on non-aggregable fields', async function (assert) { assert.expect(6); this.data.foo.fields.sort_field = {string: "sortable_field", type: "sting", sortable: true, default: "value"}; _.each(this.data.records, function(elem) { elem.sort_field = "value" + elem.id; }); this.data.foo.fields.foo.sortable= true; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1025,13 +1027,13 @@ QUnit.module('Views', { }, }); //we cannot sort by sort_field since it doesn't have a group_operator - testUtils.dom.click(list.$('.o_column_sortable:eq(2)')); + await testUtils.dom.click(list.$('.o_column_sortable:eq(2)')); //we can sort by int_field since it has a group_operator - testUtils.dom.click(list.$('.o_column_sortable:eq(1)')); + await testUtils.dom.click(list.$('.o_column_sortable:eq(1)')); //we keep previous order - testUtils.dom.click(list.$('.o_column_sortable:eq(2)')); + await testUtils.dom.click(list.$('.o_column_sortable:eq(2)')); //we can sort on foo since we are groupped by foo + previous order - testUtils.dom.click(list.$('.o_column_sortable:eq(0)')); + await testUtils.dom.click(list.$('.o_column_sortable:eq(0)')); assert.verifySteps([ 'default order', @@ -1045,7 +1047,7 @@ QUnit.module('Views', { }); - QUnit.test('properly apply onchange in simple case', function (assert) { + QUnit.test('properly apply onchange in simple case', async function (assert) { assert.expect(2); this.data.foo.onchanges = { @@ -1053,7 +1055,7 @@ QUnit.module('Views', { obj.int_field = obj.foo.length + 1000; }, }; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1065,19 +1067,19 @@ QUnit.module('Views', { assert.strictEqual($int_field_td.text(), '10', "should contain initial value"); - testUtils.dom.click($foo_td); - testUtils.fields.editInput($foo_td.find('input'), 'tralala'); + await testUtils.dom.click($foo_td); + await testUtils.fields.editInput($foo_td.find('input'), 'tralala'); assert.strictEqual($int_field_td.find('input').val(), "1007", "should contain input with onchange applied"); list.destroy(); }); - QUnit.test('column width should not change when switching mode', function (assert) { + QUnit.test('column width should not change when switching mode', async function (assert) { assert.expect(4); // Warning: this test is css dependant - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1093,13 +1095,13 @@ QUnit.module('Views', { var startWidth = list.$('table').addBack('table').width(); // start edition of first row - testUtils.dom.click(list.$('td:not(.o_list_record_selector)').first()); + await testUtils.dom.click(list.$('td:not(.o_list_record_selector)').first()); var editionWidths = _.pluck(list.$('thead th'), 'offsetWidth'); var editionWidth = list.$('table').addBack('table').width(); // leave edition - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); var readonlyWidths = _.pluck(list.$('thead th'), 'offsetWidth'); var readonlyWidth = list.$('table').addBack('table').width(); @@ -1116,10 +1118,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('deleting one record', function (assert) { + QUnit.test('deleting one record', async function (assert) { assert.expect(5); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1130,27 +1132,27 @@ QUnit.module('Views', { assert.isNotVisible(list.sidebar.$el, 'sidebar should be invisible'); assert.containsN(list, 'tbody td.o_list_record_selector', 4, "should have 4 records"); - testUtils.dom.click(list.$('tbody td.o_list_record_selector:first input')); + await testUtils.dom.click(list.$('tbody td.o_list_record_selector:first input')); assert.isVisible(list.sidebar.$el, 'sidebar should be visible'); - testUtils.dom.click(list.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); - testUtils.dom.click(list.sidebar.$('a:contains(Delete)')); + await testUtils.dom.click(list.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + await testUtils.dom.click(list.sidebar.$('a:contains(Delete)')); assert.hasClass($('body'),'modal-open', 'body should have modal-open clsss'); - testUtils.dom.click($('body .modal button span:contains(Ok)')); + await testUtils.dom.click($('body .modal button span:contains(Ok)')); assert.containsN(list, 'tbody td.o_list_record_selector', 3, "should have 3 records"); list.destroy(); }); - QUnit.test('archiving one record', function (assert) { + QUnit.test('archiving one record', async function (assert) { assert.expect(12); // add active field on foo model and make all records active this.data.foo.fields.active = {string: 'Active', type: 'boolean', default: true}; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1171,24 +1173,24 @@ QUnit.module('Views', { assert.verifySteps(['/web/dataset/search_read']); testUtils.dom.click(list.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); - testUtils.dom.click(list.sidebar.$('a:contains(Archive)')); + await testUtils.dom.click(list.sidebar.$('a:contains(Archive)')); assert.strictEqual($('.modal').length, 1, 'a confirm modal should be displayed'); testUtils.dom.click($('.modal-footer .btn-secondary')); assert.containsN(list, 'tbody td.o_list_record_selector', 4, "still should have 4 records"); testUtils.dom.click(list.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); - testUtils.dom.click(list.sidebar.$('a:contains(Archive)')); + await testUtils.dom.click(list.sidebar.$('a:contains(Archive)')); assert.strictEqual($('.modal').length, 1, 'a confirm modal should be displayed'); - testUtils.dom.click($('.modal-footer .btn-primary')); + await testUtils.dom.click($('.modal-footer .btn-primary')); assert.containsN(list, 'tbody td.o_list_record_selector', 3, "should have 3 records"); - assert.verifySteps(['/web/dataset/search_read', '/web/dataset/call_kw/foo/write', '/web/dataset/search_read']); + assert.verifySteps(['/web/dataset/call_kw/foo/write', '/web/dataset/search_read']); list.destroy(); }); - QUnit.test('pager (ungrouped and grouped mode), default limit', function (assert) { + QUnit.test('pager (ungrouped and grouped mode), default limit', async function (assert) { assert.expect(4); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1203,18 +1205,18 @@ QUnit.module('Views', { assert.isVisible(list.pager.$el, "pager should be visible"); assert.strictEqual(list.pager.state.size, 4, "pager's size should be 4"); - list.update({ groupBy: ['bar']}); + await list.update({ groupBy: ['bar']}); assert.isNotVisible(list.pager.$el, "pager should be invisible"); list.destroy(); }); - QUnit.test('can sort records when clicking on header', function (assert) { + QUnit.test('can sort records when clicking on header', async function (assert) { assert.expect(9); this.data.foo.fields.foo.sortable = true; var nbSearchRead = 0; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1234,7 +1236,7 @@ QUnit.module('Views', { "record 3 should be first"); nbSearchRead = 0; - testUtils.dom.click(list.$('thead th:contains(Foo)')); + await testUtils.dom.click(list.$('thead th:contains(Foo)')); assert.strictEqual(nbSearchRead, 1, "should have done one search_read"); assert.ok(list.$('tbody tr:first td:contains(blip)').length, "record 3 should be first"); @@ -1242,7 +1244,7 @@ QUnit.module('Views', { "record 1 should be first"); nbSearchRead = 0; - testUtils.dom.click(list.$('thead th:contains(Foo)')); + await testUtils.dom.click(list.$('thead th:contains(Foo)')); assert.strictEqual(nbSearchRead, 1, "should have done one search_read"); assert.ok(list.$('tbody tr:first td:contains(yop)').length, "record 3 should be first"); @@ -1252,10 +1254,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('use default_order', function (assert) { + QUnit.test('use default_order', async function (assert) { assert.expect(3); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1277,10 +1279,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('use more complex default_order', function (assert) { + QUnit.test('use more complex default_order', async function (assert) { assert.expect(3); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1304,12 +1306,12 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('use default_order on editable tree: sort on save', function (assert) { + QUnit.test('use default_order on editable tree: sort on save', async function (assert) { assert.expect(8); this.data.foo.records[0].o2m = [1, 3]; - var form = createView({ + var form = await createView({ View: FormView, model: 'foo', data: this.data, @@ -1325,15 +1327,15 @@ QUnit.module('Views', { res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.ok(form.$('tbody tr:first td:contains(Value 1)').length, "Value 1 should be first"); assert.ok(form.$('tbody tr:eq(1) td:contains(Value 3)').length, "Value 3 should be second"); var $o2m = form.$('.o_field_widget[name=o2m]'); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput($o2m.find('.o_field_widget'), "Value 2"); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput($o2m.find('.o_field_widget'), "Value 2"); assert.ok(form.$('tbody tr:first td:contains(Value 1)').length, "Value 1 should be first"); assert.ok(form.$('tbody tr:eq(1) td:contains(Value 3)').length, @@ -1341,7 +1343,7 @@ QUnit.module('Views', { assert.ok(form.$('tbody tr:eq(2) td input').val(), "Value 2 should be third (shouldn't be sorted)"); - testUtils.form.clickSave(form); + await testUtils.form.clickSave(form); assert.ok(form.$('tbody tr:first td:contains(Value 1)').length, "Value 1 should be first"); assert.ok(form.$('tbody tr:eq(1) td:contains(Value 2)').length, @@ -1352,7 +1354,7 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('use default_order on editable tree: sort on demand', function (assert) { + QUnit.test('use default_order on editable tree: sort on demand', async function (assert) { assert.expect(11); this.data.foo.records[0].o2m = [1, 3]; @@ -1360,7 +1362,7 @@ QUnit.module('Views', { this.data.bar.records[0].name = "Value 1"; this.data.bar.records[2].name = "Value 3"; - var form = createView({ + var form = await createView({ View: FormView, model: 'foo', data: this.data, @@ -1376,15 +1378,15 @@ QUnit.module('Views', { res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); assert.ok(form.$('tbody tr:first td:contains(Value 1)').length, "Value 1 should be first"); assert.ok(form.$('tbody tr:eq(1) td:contains(Value 3)').length, "Value 3 should be second"); var $o2m = form.$('.o_field_widget[name=o2m]'); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.fields.editInput($o2m.find('.o_field_widget'), "Value 2"); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.fields.editInput($o2m.find('.o_field_widget'), "Value 2"); assert.ok(form.$('tbody tr:first td:contains(Value 1)').length, "Value 1 should be first"); assert.ok(form.$('tbody tr:eq(1) td:contains(Value 3)').length, @@ -1392,9 +1394,9 @@ QUnit.module('Views', { assert.ok(form.$('tbody tr:eq(2) td input').val(), "Value 2 should be third (shouldn't be sorted)"); - testUtils.dom.click(form.$('.o_form_sheet_bg')); + await testUtils.dom.click(form.$('.o_form_sheet_bg')); - testUtils.dom.click($o2m.find('.o_column_sortable')); + await testUtils.dom.click($o2m.find('.o_column_sortable')); assert.strictEqual(form.$('tbody tr:first').text(), 'Value 1', "Value 1 should be first"); assert.strictEqual(form.$('tbody tr:eq(1)').text(), 'Value 2', @@ -1402,7 +1404,7 @@ QUnit.module('Views', { assert.strictEqual(form.$('tbody tr:eq(2)').text(), 'Value 3', "Value 3 should be third"); - testUtils.dom.click($o2m.find('.o_column_sortable')); + await testUtils.dom.click($o2m.find('.o_column_sortable')); assert.strictEqual(form.$('tbody tr:first').text(), 'Value 3', "Value 3 should be first"); assert.strictEqual(form.$('tbody tr:eq(1)').text(), 'Value 2', @@ -1413,7 +1415,7 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('use default_order on editable tree: sort on demand in page', function (assert) { + QUnit.test('use default_order on editable tree: sort on demand in page', async function (assert) { assert.expect(4); this.data.bar.fields = {name: {string: "Name", type: "char", sortable: true}}; @@ -1429,7 +1431,7 @@ QUnit.module('Views', { } this.data.foo.records[0].o2m = ids; - var form = createView({ + var form = await createView({ View: FormView, model: 'foo', data: this.data, @@ -1446,13 +1448,13 @@ QUnit.module('Views', { }); // Change page - testUtils.dom.click(form.$('.o_field_widget[name=o2m] .o_pager_next')); + await testUtils.dom.click(form.$('.o_field_widget[name=o2m] .o_pager_next')); assert.strictEqual(form.$('tbody tr:first').text(), 'Value 44', "record 44 should be first"); assert.strictEqual(form.$('tbody tr:eq(4)').text(), 'Value 48', "record 48 should be last"); - testUtils.dom.click(form.$('.o_column_sortable')); + await testUtils.dom.click(form.$('.o_column_sortable')); assert.strictEqual(form.$('tbody tr:first').text(), 'Value 08', "record 48 should be first"); assert.strictEqual(form.$('tbody tr:eq(4)').text(), 'Value 04', @@ -1461,10 +1463,10 @@ QUnit.module('Views', { form.destroy(); }); - QUnit.test('can display button in edit mode', function (assert) { + QUnit.test('can display button in edit mode', async function (assert) { assert.expect(2); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1478,10 +1480,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('can display a list with a many2many field', function (assert) { + QUnit.test('can display a list with a many2many field', async function (assert) { assert.expect(3); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1499,10 +1501,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('list without import button', function (assert) { + QUnit.test('list without import button', async function (assert) { assert.expect(1); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1516,13 +1518,13 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('display a tooltip on a field', function (assert) { + QUnit.test('display a tooltip on a field', async function (assert) { assert.expect(2); var initialDebugMode = config.debug; config.debug = false; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1538,7 +1540,7 @@ QUnit.module('Views', { config.debug = true; // it is necessary to rerender the list so tooltips can be properly created - list.reload(); + await list.reload(); list.$('th:not(.o_list_record_selector)').tooltip('show', false); list.$('th:not(.o_list_record_selector)').trigger($.Event('mouseenter')); @@ -1548,10 +1550,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('support row decoration', function (assert) { + QUnit.test('support row decoration', async function (assert) { assert.expect(2); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1567,12 +1569,12 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('support row decoration (with unset numeric values)', function (assert) { + QUnit.test('support row decoration (with unset numeric values)', async function (assert) { assert.expect(2); this.data.foo.records = []; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1581,22 +1583,22 @@ QUnit.module('Views', { '</tree>', }); - testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); assert.containsNone(list, 'tr.o_data_row.text-danger', "the data row should not have .text-danger decoration (int_field is unset)"); - testUtils.fields.editInput(list.$('input[name="int_field"]'), '-3'); + await testUtils.fields.editInput(list.$('input[name="int_field"]'), '-3'); assert.containsOnce(list, 'tr.o_data_row.text-danger', "the data row should have .text-danger decoration (int_field is negative)"); list.destroy(); }); - QUnit.test('support row decoration with date', function (assert) { + QUnit.test('support row decoration with date', async function (assert) { assert.expect(3); this.data.foo.records[0].datetime = '2017-02-27 12:51:35'; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1615,14 +1617,14 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('no content helper when no data', function (assert) { + QUnit.test('no content helper when no data', async function (assert) { assert.expect(5); var records = this.data.foo.records; this.data.foo.records = []; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1643,7 +1645,7 @@ QUnit.module('Views', { "should have rendered no content helper from action"); this.data.foo.records = records; - list.reload(); + await list.reload(); assert.containsNone(list, '.o_view_nocontent', "should not display the no content helper"); @@ -1651,12 +1653,12 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('no nocontent helper when no data and no help', function (assert) { + QUnit.test('no nocontent helper when no data and no help', async function (assert) { assert.expect(3); this.data.foo.records = []; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1673,14 +1675,14 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('list view, editable, without data', function (assert) { + QUnit.test('list view, editable, without data', async function (assert) { assert.expect(13); this.data.foo.records = []; this.data.foo.fields.date.default = "2017-02-10"; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1710,7 +1712,7 @@ QUnit.module('Views', { "should not have a div.table-responsive"); assert.containsNone(list, 'table', "should not have rendered a table"); - testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); assert.containsNone(list, '.o_view_nocontent', "should not have a no content helper displayed"); @@ -1728,7 +1730,7 @@ QUnit.module('Views', { assert.strictEqual(list.$('.o_list_button button').prop('disabled'), true, "buttons should be disabled while the record is not yet created"); - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); assert.strictEqual(list.$('tbody tr:eq(0) .o_list_record_selector input').prop('disabled'), false, "record selector checkbox should not be disabled once the record is created"); @@ -1738,12 +1740,12 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('list view, editable, with a button', function (assert) { + QUnit.test('list view, editable, with a button', async function (assert) { assert.expect(1); this.data.foo.records = []; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1753,17 +1755,17 @@ QUnit.module('Views', { '</tree>', }); - testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); assert.containsOnce(list, 'table button.o_icon_button i.fa-phone', "should have rendered a button"); list.destroy(); }); - QUnit.test('list view with a button without icon', function (assert) { + QUnit.test('list view with a button without icon', async function (assert) { assert.expect(1); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1778,10 +1780,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('list view, editable, can discard', function (assert) { + QUnit.test('list view, editable, can discard', async function (assert) { assert.expect(5); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1792,13 +1794,13 @@ QUnit.module('Views', { assert.strictEqual(list.$('td:not(.o_list_record_selector) input').length, 0, "no input should be in the table"); - testUtils.dom.click(list.$('tbody td:not(.o_list_record_selector):first')); + await testUtils.dom.click(list.$('tbody td:not(.o_list_record_selector):first')); assert.strictEqual(list.$('td:not(.o_list_record_selector) input').length, 1, "first cell should be editable"); assert.ok(list.$buttons.find('.o_list_button_discard').is(':visible'), "discard button should be visible"); - testUtils.dom.click(list.$buttons.find('.o_list_button_discard')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_discard')); assert.strictEqual(list.$('td:not(.o_list_record_selector) input').length, 0, "no input should be in the table"); @@ -1807,7 +1809,7 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('editable list view, click on the list to save', function (assert) { + QUnit.test('editable list view, click on the list to save', async function (assert) { assert.expect(3); this.data.foo.fields.date.default = "2017-02-10"; @@ -1815,7 +1817,7 @@ QUnit.module('Views', { var createCount = 0; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1830,27 +1832,27 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(list.$buttons.find('.o_list_button_add')); - testUtils.dom.click(list.$('div.table-responsive')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + await testUtils.dom.click(list.$('div.table-responsive')); assert.strictEqual(createCount, 1, "should have created a record"); - testUtils.dom.click(list.$buttons.find('.o_list_button_add')); - testUtils.dom.click(list.$('tfoot')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + await testUtils.dom.click(list.$('tfoot')); assert.strictEqual(createCount, 2, "should have created a record"); - testUtils.dom.click(list.$buttons.find('.o_list_button_add')); - testUtils.dom.click(list.$('tbody tr').last()); + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + await testUtils.dom.click(list.$('tbody tr').last()); assert.strictEqual(createCount, 3, "should have created a record"); list.destroy(); }); - QUnit.test('click on a button in a list view', function (assert) { + QUnit.test('click on a button in a list view', async function (assert) { assert.expect(9); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1882,16 +1884,16 @@ QUnit.module('Views', { assert.containsOnce(list, '.o_list_button:first .o_icon_button .fa.fa-car', 'buttons should have correct icon'); - testUtils.dom.click(list.$('.o_list_button:first > button')); + await testUtils.dom.click(list.$('.o_list_button:first > button')); assert.verifySteps(['/web/dataset/search_read', '/web/dataset/search_read'], "should have reloaded the view (after the action is complete)"); list.destroy(); }); - QUnit.test('invisible attrs in readonly and editable list', function (assert) { + QUnit.test('invisible attrs in readonly and editable list', async function (assert) { assert.expect(5); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1910,28 +1912,28 @@ QUnit.module('Views', { "td that contains an invisible button should be empty"); // edit first row - testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2)')); + await testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2)')); assert.strictEqual(list.$('tbody tr:nth(0) td:nth(4) input.o_invisible_modifier').length, 1, "td that contains an invisible field should not be empty in edition"); assert.strictEqual(list.$('tbody tr:nth(0) td:nth(1) > button.o_invisible_modifier').length, 1, "td that contains an invisible button should not be empty in edition"); - testUtils.dom.click(list.$buttons.find('.o_list_button_discard')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_discard')); // click on the invisible field's cell to edit first row - testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(4)')); + await testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(4)')); assert.hasClass(list.$('tbody tr:nth(0)'),'o_selected_row', "first row should be in edition"); list.destroy(); }); - QUnit.test('monetary fields are properly rendered', function (assert) { + QUnit.test('monetary fields are properly rendered', async function (assert) { assert.expect(3); var currencies = {}; _.each(this.data.res_currency.records, function (currency) { currencies[currency.id] = currency; }); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1955,10 +1957,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('simple list with date and datetime', function (assert) { + QUnit.test('simple list with date and datetime', async function (assert) { assert.expect(2); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1977,12 +1979,12 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('edit a row by clicking on a readonly field', function (assert) { + QUnit.test('edit a row by clicking on a readonly field', async function (assert) { assert.expect(9); this.data.foo.fields.foo.readonly = true; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -1993,7 +1995,7 @@ QUnit.module('Views', { "foo field cells should have class 'o_readonly_modifier'"); // edit the first row - testUtils.dom.click(list.$('.o_data_row:first td:nth(1)')); + await testUtils.dom.click(list.$('.o_data_row:first td:nth(1)')); assert.hasClass(list.$('.o_data_row:first'),'o_selected_row', "first row should be selected"); var $cell = list.$('.o_data_row:first td:nth(1)'); @@ -2008,7 +2010,7 @@ QUnit.module('Views', { "a widget for field 'int_field should have been rendered'"); // click again on readonly cell of first line: nothing should have changed - testUtils.dom.click(list.$('.o_data_row:first td:nth(1)')); + await testUtils.dom.click(list.$('.o_data_row:first td:nth(1)')); assert.hasClass(list.$('.o_data_row:first'),'o_selected_row', "first row should be selected"); assert.strictEqual(list.$('.o_data_row:first td:nth(2) input').length, 1, @@ -2017,7 +2019,7 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('list view with nested groups', function (assert) { + QUnit.test('list view with nested groups', async function (assert) { assert.expect(42); this.data.foo.records.push({id: 5, foo: "blip", int_field: -7, m2o: 1}); @@ -2026,7 +2028,7 @@ QUnit.module('Views', { var nbRPCs = {readGroup: 0, searchRead: 0}; var envIDs = []; // the ids that should be in the environment during this test - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2075,7 +2077,7 @@ QUnit.module('Views', { // open the first group nbRPCs = {readGroup: 0, searchRead: 0}; - testUtils.dom.click(list.$('.o_group_header:first')); + await testUtils.dom.click(list.$('.o_group_header:first')); assert.strictEqual(nbRPCs.readGroup, 1, "should have done one read_group"); assert.strictEqual(nbRPCs.searchRead, 0, "should have done no search_read"); assert.deepEqual(list.exportState().resIds, envIDs); @@ -2098,7 +2100,7 @@ QUnit.module('Views', { // open subgroup nbRPCs = {readGroup: 0, searchRead: 0}; envIDs = [4, 5]; // the opened subgroup contains these two records - testUtils.dom.click($openGroup.find('.o_group_header:nth(2)')); + await testUtils.dom.click($openGroup.find('.o_group_header:nth(2)')); assert.strictEqual(nbRPCs.readGroup, 0, "should have done no read_group"); assert.strictEqual(nbRPCs.searchRead, 1, "should have done one search_read"); assert.deepEqual(list.exportState().resIds, envIDs); @@ -2111,12 +2113,12 @@ QUnit.module('Views', { "first record in open subgroup should be res_id 4 (with int_field -4)"); // open a record (should trigger event 'open_record') - testUtils.dom.click($openSubGroup.find('.o_data_row:first')); + await testUtils.dom.click($openSubGroup.find('.o_data_row:first')); // sort by int_field (ASC) and check that open groups are still open nbRPCs = {readGroup: 0, searchRead: 0}; envIDs = [5, 4]; // order of the records changed - testUtils.dom.click(list.$('thead th:last')); + await testUtils.dom.click(list.$('thead th:last')); assert.strictEqual(nbRPCs.readGroup, 2, "should have done two read_groups"); assert.strictEqual(nbRPCs.searchRead, 1, "should have done one search_read"); assert.deepEqual(list.exportState().resIds, envIDs); @@ -2131,7 +2133,7 @@ QUnit.module('Views', { // close first level group nbRPCs = {readGroup: 0, searchRead: 0}; envIDs = []; // the group being closed, there is no more record in the environment - testUtils.dom.click(list.$('.o_group_header:nth(1)')); + await testUtils.dom.click(list.$('.o_group_header:nth(1)')); assert.strictEqual(nbRPCs.readGroup, 0, "should have done no read_group"); assert.strictEqual(nbRPCs.searchRead, 0, "should have done no search_read"); assert.deepEqual(list.exportState().resIds, envIDs); @@ -2145,7 +2147,7 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('grouped list on selection field at level 2', function (assert) { + QUnit.test('grouped list on selection field at level 2', async function (assert) { assert.expect(4); this.data.foo.fields.priority = { @@ -2157,7 +2159,7 @@ QUnit.module('Views', { this.data.foo.records.push({id: 5, foo: "blip", int_field: -7, m2o: 1, priority: 2}); this.data.foo.records.push({id: 6, foo: "blip", int_field: 5, m2o: 1, priority: 3}); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2169,7 +2171,7 @@ QUnit.module('Views', { "should contain 2 groups at first level"); // open the first group - testUtils.dom.click(list.$('.o_group_header:first')); + await testUtils.dom.click(list.$('.o_group_header:first')); var $openGroup = list.$('tbody:nth(1)'); assert.strictEqual($openGroup.find('tr').length, 3, @@ -2182,11 +2184,11 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('grouped list with a pager in a group', function (assert) { + QUnit.test('grouped list with a pager in a group', async function (assert) { assert.expect(6); this.data.foo.records[3].bar = true; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2200,7 +2202,7 @@ QUnit.module('Views', { var headerHeight = list.$('.o_group_header').css('height'); // basic rendering checks - testUtils.dom.click(list.$('.o_group_header')); + await testUtils.dom.click(list.$('.o_group_header')); assert.strictEqual(list.$('.o_group_header').css('height'), headerHeight, "height of group header shouldn't have changed"); assert.hasClass(list.$('.o_group_header td:last'),'o_group_pager', @@ -2211,7 +2213,7 @@ QUnit.module('Views', { "open group should display 3 records"); // go to next page - testUtils.dom.click(list.$('.o_group_header .o_pager_next')); + await testUtils.dom.click(list.$('.o_group_header .o_pager_next')); assert.strictEqual(list.$('.o_group_header .o_pager_value').text(), '4-4', "pager's value should be correct"); assert.containsOnce(list, '.o_data_row', @@ -2220,10 +2222,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('edition: create new line, then discard', function (assert) { + QUnit.test('edition: create new line, then discard', async function (assert) { assert.expect(8); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2236,12 +2238,12 @@ QUnit.module('Views', { "create button should be visible"); assert.strictEqual(list.$buttons.find('.o_list_button_discard:visible').length, 0, "discard button should be hidden"); - testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); assert.strictEqual(list.$buttons.find('.o_list_button_add:visible').length, 0, "create button should be hidden"); assert.strictEqual(list.$buttons.find('.o_list_button_discard:visible').length, 1, "discard button should be visible"); - testUtils.dom.click(list.$buttons.find('.o_list_button_discard')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_discard')); assert.containsN(list, 'tr.o_data_row', 4, "should still have 4 records"); assert.strictEqual(list.$buttons.find('.o_list_button_add:visible').length, 1, @@ -2251,10 +2253,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('invisible attrs on fields are re-evaluated on field change', function (assert) { + QUnit.test('invisible attrs on fields are re-evaluated on field change', async function (assert) { assert.expect(7); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2269,26 +2271,26 @@ QUnit.module('Views', { "there should be 3 invisible foo cells in readonly mode"); // Make first line editable - testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(1)')); + await testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(1)')); assert.strictEqual(list.$('tbody tr:nth(0) td:nth(1) > input[name="foo"].o_invisible_modifier').length, 1, "the foo field widget should have been rendered as invisible"); - testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2) input')); + await testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2) input')); assert.strictEqual(list.$('tbody tr:nth(0) td:nth(1) > input[name="foo"]:not(.o_invisible_modifier)').length, 1, "the foo field widget should have been marked as non-invisible"); assert.containsN(list, 'tbody td.o_invisible_modifier', 2, "the foo field widget parent cell should not be invisible anymore"); - testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2) input')); + await testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2) input')); assert.strictEqual(list.$('tbody tr:nth(0) td:nth(1) > input[name="foo"].o_invisible_modifier').length, 1, "the foo field widget should have been marked as invisible again"); assert.containsN(list, 'tbody td.o_invisible_modifier', 3, "the foo field widget parent cell should now be invisible again"); // Reswitch the cell to editable and save the row - testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2) input')); - testUtils.dom.click(list.$('thead')); + await testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2) input')); + await testUtils.dom.click(list.$('thead')); assert.containsN(list, 'tbody td.o_invisible_modifier', 2, "there should be 2 invisible foo cells in readonly mode"); @@ -2296,10 +2298,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('readonly attrs on fields are re-evaluated on field change', function (assert) { + QUnit.test('readonly attrs on fields are re-evaluated on field change', async function (assert) { assert.expect(9); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2314,31 +2316,31 @@ QUnit.module('Views', { "there should be 3 readonly foo cells in readonly mode"); // Make first line editable - testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(1)')); + await testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(1)')); assert.strictEqual(list.$('tbody tr:nth(0) td:nth(1) > span[name="foo"]').length, 1, "the foo field widget should have been rendered as readonly"); - testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2) input')); + await testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2) input')); assert.strictEqual(list.$('tbody tr:nth(0) td:nth(1) > input[name="foo"]').length, 1, "the foo field widget should have been rerendered as editable"); assert.containsN(list, 'tbody td.o_readonly_modifier', 2, "the foo field widget parent cell should not be readonly anymore"); - testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2) input')); + await testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2) input')); assert.strictEqual(list.$('tbody tr:nth(0) td:nth(1) > span[name="foo"]').length, 1, "the foo field widget should have been rerendered as readonly"); assert.containsN(list, 'tbody td.o_readonly_modifier', 3, "the foo field widget parent cell should now be readonly again"); - testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2) input')); + await testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2) input')); assert.strictEqual(list.$('tbody tr:nth(0) td:nth(1) > input[name="foo"]').length, 1, "the foo field widget should have been rerendered as editable again"); assert.containsN(list, 'tbody td.o_readonly_modifier', 2, "the foo field widget parent cell should not be readonly again"); // Click outside to leave edition mode - testUtils.dom.click(list.$el); + await testUtils.dom.click(list.$el); assert.containsN(list, 'tbody td.o_readonly_modifier', 2, "there should be 2 readonly foo cells in readonly mode"); @@ -2346,10 +2348,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('required attrs on fields are re-evaluated on field change', function (assert) { + QUnit.test('required attrs on fields are re-evaluated on field change', async function (assert) { assert.expect(7); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2364,26 +2366,26 @@ QUnit.module('Views', { "there should be 3 required foo cells in readonly mode"); // Make first line editable - testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(1)')); + await testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(1)')); assert.strictEqual(list.$('tbody tr:nth(0) td:nth(1) > input[name="foo"].o_required_modifier').length, 1, "the foo field widget should have been rendered as required"); - testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2) input')); + await testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2) input')); assert.strictEqual(list.$('tbody tr:nth(0) td:nth(1) > input[name="foo"]:not(.o_required_modifier)').length, 1, "the foo field widget should have been marked as non-required"); assert.containsN(list, 'tbody td.o_required_modifier', 2, "the foo field widget parent cell should not be required anymore"); - testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2) input')); + await testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2) input')); assert.strictEqual(list.$('tbody tr:nth(0) td:nth(1) > input[name="foo"].o_required_modifier').length, 1, "the foo field widget should have been marked as required again"); assert.containsN(list, 'tbody td.o_required_modifier', 3, "the foo field widget parent cell should now be required again"); // Reswitch the cell to editable and save the row - testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2) input')); - testUtils.dom.click(list.$('thead')); + await testUtils.dom.click(list.$('tbody tr:nth(0) td:nth(2) input')); + await testUtils.dom.click(list.$('thead')); assert.containsN(list, 'tbody td.o_required_modifier', 2, "there should be 2 required foo cells in readonly mode"); @@ -2391,11 +2393,11 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('leaving unvalid rows in edition', function (assert) { + QUnit.test('leaving unvalid rows in edition', async function (assert) { assert.expect(4); var warnings = 0; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2417,14 +2419,15 @@ QUnit.module('Views', { // Start first line edition var $firstFooTd = list.$('tbody tr:nth(0) td:nth(1)'); - testUtils.dom.click($firstFooTd); + await testUtils.dom.click($firstFooTd); // Remove required foo field value - testUtils.fields.editInput($firstFooTd.find('input'), "") + await testUtils.fields.editInput($firstFooTd.find('input'), ""); // Try starting other line edition var $secondFooTd = list.$('tbody tr:nth(1) td:nth(1)'); - testUtils.dom.click($secondFooTd); + await testUtils.dom.click($secondFooTd); + await testUtils.nextTick(); assert.strictEqual($firstFooTd.parent('.o_selected_row').length, 1, "first line should still be in edition as invalid"); @@ -2438,10 +2441,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('open a virtual id', function (assert) { + QUnit.test('open a virtual id', async function (assert) { assert.expect(1); - var list = createView({ + var list = await createView({ View: ListView, model: 'event', data: this.data, @@ -2461,10 +2464,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('pressing enter on last line of editable list view', function (assert) { + QUnit.test('pressing enter on last line of editable list view', async function (assert) { assert.expect(7); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2476,29 +2479,29 @@ QUnit.module('Views', { }); // click on 3rd line - testUtils.dom.click(list.$('td:contains(gnap)')); + await testUtils.dom.click(list.$('td:contains(gnap)')); assert.hasClass(list.$('tr.o_data_row:eq(2)'),'o_selected_row', "3rd row should be selected"); // press enter in input - list.$('tr.o_selected_row input').trigger({type: 'keydown', which: 13}); // enter + await testUtils.fields.triggerKeydown(list.$('tr.o_selected_row input'), 'enter'); assert.hasClass(list.$('tr.o_data_row:eq(3)'),'o_selected_row', "4rd row should be selected"); assert.doesNotHaveClass(list.$('tr.o_data_row:eq(2)'), 'o_selected_row', "3rd row should no longer be selected"); // press enter on last row - list.$('tr.o_selected_row input').trigger({type: 'keydown', which: 13}); // enter + await testUtils.fields.triggerKeydown(list.$('tr.o_selected_row input'), 'enter'); assert.containsN(list, 'tr.o_data_row', 5, "should have created a 5th row"); assert.verifySteps(['/web/dataset/search_read', '/web/dataset/call_kw/foo/default_get']); list.destroy(); }); - QUnit.test('pressing tab on last cell of editable list view', function (assert) { + QUnit.test('pressing tab on last cell of editable list view', async function (assert) { assert.expect(9); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2509,7 +2512,7 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(list.$('td:contains(blip)').last()); + await testUtils.dom.click(list.$('td:contains(blip)').last()); assert.strictEqual(document.activeElement.name, "foo", "focus should be on an input with name = foo"); @@ -2517,12 +2520,11 @@ QUnit.module('Views', { document.activeElement.value = "blip-changed"; $(document.activeElement).trigger({type: 'change'}); - list.$('tr.o_selected_row input[name="foo"]').trigger({type: 'keydown', which: 9}); // tab + await testUtils.fields.triggerKeydown(list.$('tr.o_selected_row input[name="foo"]'), 'tab'); assert.strictEqual(document.activeElement.name, "int_field", "focus should be on an input with name = int_field"); - list.$('tr.o_selected_row input[name="int_field"]').trigger({type: 'keydown', which: 9}); // tab - + await testUtils.fields.triggerKeydown(list.$('tr.o_selected_row input[name="foo"]'), 'tab'); assert.hasClass(list.$('tr.o_data_row:eq(4)'),'o_selected_row', "5th row should be selected"); assert.strictEqual(document.activeElement.name, "foo", @@ -2535,13 +2537,13 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('navigation with tab and read completes after default_get', function (assert) { + QUnit.test('navigation with tab and read completes after default_get', async function (assert) { assert.expect(8); - var defaultGetDef = $.Deferred(); - var readDef = $.Deferred(); + var defaultGetPromise = testUtils.makeTestPromise(); + var readPromise = testUtils.makeTestPromise(); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2552,24 +2554,30 @@ QUnit.module('Views', { } var result = this._super.apply(this, arguments); if (args.method === 'read') { - return readDef.then(_.constant(result)); + return readPromise.then(function () { + return result; + }); } if (args.method === 'default_get') { - return defaultGetDef.then(_.constant(result)); + return defaultGetPromise.then(function () { + return result; + }); } return result; }, }); - testUtils.dom.click(list.$('td:contains(-4)').last()); + await testUtils.dom.click(list.$('td:contains(-4)').last()); - testUtils.fields.editInput(list.$('tr.o_selected_row input[name="int_field"]'), '1234'); - list.$('tr.o_selected_row input[name="int_field"]').trigger({type: 'keydown', which: 9}); // tab + await testUtils.fields.editInput(list.$('tr.o_selected_row input[name="int_field"]'), '1234'); + await testUtils.fields.triggerKeydown(list.$('tr.o_selected_row input[name="int_field"]'), 'tab'); - defaultGetDef.resolve(); + defaultGetPromise.resolve(); assert.containsN(list, 'tbody tr.o_data_row', 4, "should have 4 data rows"); - readDef.resolve(); + + readPromise.resolve(); + await testUtils.nextTick(); assert.containsN(list, 'tbody tr.o_data_row', 5, "should have 5 data rows"); assert.strictEqual(list.$('td:contains(1234)').length, 1, @@ -2578,7 +2586,7 @@ QUnit.module('Views', { // we trigger a tab to move to the second cell in the current row. this // operation requires that this.currentRow is properly set in the // list editable renderer. - list.$('tr.o_selected_row input[name="foo"]').trigger({type: 'keydown', which: 9}); // tab + await testUtils.fields.triggerKeydown(list.$('tr.o_selected_row input[name="foo"]'), 'tab'); assert.hasClass(list.$('tr.o_data_row:eq(4)'),'o_selected_row', "5th row should be selected"); @@ -2586,10 +2594,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('display toolbar', function (assert) { + QUnit.test('display toolbar', async function (assert) { assert.expect(6); - var list = createView({ + var list = await createView({ View: ListView, model: 'event', data: this.data, @@ -2628,27 +2636,27 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('edit list line after line deletion', function (assert) { + QUnit.test('edit list line after line deletion', async function (assert) { assert.expect(5); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, arch: '<tree editable="top"><field name="foo"/><field name="int_field"/></tree>', }); - testUtils.dom.click(list.$('.o_data_row:nth(2) > td:not(.o_list_record_selector)').first()); + await testUtils.dom.click(list.$('.o_data_row:nth(2) > td:not(.o_list_record_selector)').first()); assert.ok(list.$('.o_data_row:nth(2)').is('.o_selected_row'), "third row should be in edition"); - testUtils.dom.click(list.$buttons.find('.o_list_button_discard')); - testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_discard')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); assert.ok(list.$('.o_data_row:nth(0)').is('.o_selected_row'), "first row should be in edition (creation)"); - testUtils.dom.click(list.$buttons.find('.o_list_button_discard')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_discard')); assert.containsNone(list, '.o_selected_row', "no row should be selected"); - testUtils.dom.click(list.$('.o_data_row:nth(2) > td:not(.o_list_record_selector)').first()); + await testUtils.dom.click(list.$('.o_data_row:nth(2) > td:not(.o_list_record_selector)').first()); assert.ok(list.$('.o_data_row:nth(2)').is('.o_selected_row'), "third row should be in edition"); assert.containsOnce(list, '.o_selected_row', @@ -2657,14 +2665,14 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('navigation with tab and readonly field (no modification)', function (assert) { + QUnit.test('navigation with tab and readonly field (no modification)', async function (assert) { // This test makes sure that if we have 2 cells in a row, the first in // edit mode, and the second one readonly, then if we press TAB when the // focus is on the first, then the focus skip the readonly cells and // directly goes to the next line instead. assert.expect(2); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2672,31 +2680,30 @@ QUnit.module('Views', { }); // click on first td and press TAB - testUtils.dom.click(list.$('td:contains(yop)').last()); + await testUtils.dom.click(list.$('td:contains(yop)').last()); - list.$('tr.o_selected_row input[name="foo"]').trigger({type: 'keydown', which: $.ui.keyCode.TAB}); + await testUtils.fields.triggerKeydown(list.$('tr.o_selected_row input[name="foo"]'), 'tab'); assert.hasClass(list.$('tr.o_data_row:eq(1)'),'o_selected_row', "2nd row should be selected"); // we do it again. This was broken because the this.currentRow variable // was not properly set, and the second TAB could cause a crash. - list.$('tr.o_selected_row input[name="foo"]').trigger({type: 'keydown', which: $.ui.keyCode.TAB}); + await testUtils.fields.triggerKeydown(list.$('tr.o_selected_row input[name="foo"]'), 'tab'); assert.hasClass(list.$('tr.o_data_row:eq(2)'),'o_selected_row', "3rd row should be selected"); list.destroy(); }); - - QUnit.test('navigation with tab and readonly field (with modification)', function (assert) { + QUnit.test('navigation with tab and readonly field (with modification)', async function (assert) { // This test makes sure that if we have 2 cells in a row, the first in // edit mode, and the second one readonly, then if we press TAB when the // focus is on the first, then the focus skips the readonly cells and // directly goes to the next line instead. assert.expect(2); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2704,30 +2711,30 @@ QUnit.module('Views', { }); // click on first td and press TAB - testUtils.dom.click(list.$('td:contains(yop)').last()); + await testUtils.dom.click(list.$('td:contains(yop)')); //modity the cell content - document.activeElement.value = "blip-changed"; - $(document.activeElement).trigger({type: 'change'}); + testUtils.fields.editAndTrigger($(document.activeElement), + 'blip-changed', ['change']); - list.$('tr.o_selected_row input[name="foo"]').trigger({type: 'keydown', which: $.ui.keyCode.TAB}); + await testUtils.fields.triggerKeydown(list.$('tr.o_selected_row input[name="foo"]'), 'tab'); assert.hasClass(list.$('tr.o_data_row:eq(1)'),'o_selected_row', "2nd row should be selected"); // we do it again. This was broken because the this.currentRow variable // was not properly set, and the second TAB could cause a crash. - list.$('tr.o_selected_row input[name="foo"]').trigger({type: 'keydown', which: $.ui.keyCode.TAB}); + await testUtils.fields.triggerKeydown(list.$('tr.o_selected_row input[name="foo"]'), 'tab'); assert.hasClass(list.$('tr.o_data_row:eq(2)'),'o_selected_row', "3rd row should be selected"); list.destroy(); }); - QUnit.test('navigation with tab on a list with create="0"', function (assert) { + QUnit.test('navigation with tab on a list with create="0"', async function (assert) { assert.expect(4); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2739,31 +2746,31 @@ QUnit.module('Views', { assert.containsN(list, '.o_data_row', 4, "the list should contain 4 rows"); - testUtils.dom.click(list.$('.o_data_row:nth(2) .o_data_cell:first')); + await testUtils.dom.click(list.$('.o_data_row:nth(2) .o_data_cell:first')); assert.hasClass(list.$('.o_data_row:nth(2)'),'o_selected_row', "third row should be in edition"); // Press 'Tab' -> should go to next line // add a value in the cell because the Tab on an empty first cell would activate the next widget in the view - testUtils.fields.editInput(list.$('.o_selected_row input').eq(1), 11); - list.$('.o_selected_row input').trigger({type: 'keydown', which: 9}); + await testUtils.fields.editInput(list.$('.o_selected_row input').eq(1), 11); + await testUtils.fields.triggerKeydown(list.$('.o_selected_row input'), 'tab'); assert.hasClass(list.$('.o_data_row:nth(3)'),'o_selected_row', "fourth row should be in edition"); // Press 'Tab' -> should go back to first line as the create action isn't available - testUtils.fields.editInput(list.$('.o_selected_row input').eq(1), 11); - list.$('.o_selected_row input').trigger({type: 'keydown', which: 9}); + await testUtils.fields.editInput(list.$('.o_selected_row input').eq(1), 11); + await testUtils.fields.triggerKeydown(list.$('.o_selected_row input'), 'tab'); assert.hasClass(list.$('.o_data_row:first'),'o_selected_row', "first row should be in edition"); list.destroy(); }); - QUnit.test('navigation with tab on a one2many list with create="0"', function (assert) { + QUnit.test('navigation with tab on a one2many list with create="0"', async function (assert) { assert.expect(4); this.data.foo.records[0].o2m = [1, 2]; - var form = createView({ + var form = await createView({ View: FormView, model: 'foo', data: this.data, @@ -2784,31 +2791,31 @@ QUnit.module('Views', { assert.containsN(form, '.o_field_widget[name=o2m] .o_data_row', 2, "there should be two records in the many2many"); - testUtils.dom.click(form.$('.o_field_widget[name=o2m] .o_data_cell:first')); + await testUtils.dom.click(form.$('.o_field_widget[name=o2m] .o_data_cell:first')); assert.hasClass(form.$('.o_field_widget[name=o2m] .o_data_row:first'),'o_selected_row', "first row should be in edition"); // Press 'Tab' -> should go to next line - form.$('.o_field_widget[name=o2m] .o_selected_row input').trigger({type: 'keydown', which: 9}); + await testUtils.fields.triggerKeydown(form.$('.o_field_widget[name=o2m] .o_selected_row input'), 'tab'); assert.hasClass(form.$('.o_field_widget[name=o2m] .o_data_row:nth(1)'),'o_selected_row', "second row should be in edition"); // Press 'Tab' -> should get out of the one to many and go to the next field of the form - form.$('.o_field_widget[name=o2m] .o_selected_row input').trigger({type: 'keydown', which: 9}); + await testUtils.fields.triggerKeydown(form.$('.o_field_widget[name=o2m] .o_selected_row input'), 'tab'); assert.strictEqual(document.activeElement, form.$('input[name="foo"]')[0], "the next field should be selected"); form.destroy(); }); - QUnit.test('edition, then navigation with tab (with a readonly field)', function (assert) { + QUnit.test('edition, then navigation with tab (with a readonly field)', async function (assert) { // This test makes sure that if we have 2 cells in a row, the first in // edit mode, and the second one readonly, then if we edit and press TAB, // (before debounce), the save operation is properly done (before // selecting the next row) assert.expect(4); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2823,9 +2830,9 @@ QUnit.module('Views', { }); // click on first td and press TAB - testUtils.dom.click(list.$('td:contains(yop)')); - testUtils.fields.editSelect(list.$('tr.o_selected_row input[name="foo"]'), 'new value'); - list.$('tr.o_selected_row input[name="foo"]').trigger({type: 'keydown', which: $.ui.keyCode.TAB}); + await testUtils.dom.click(list.$('td:contains(yop)')); + await testUtils.fields.editSelect(list.$('tr.o_selected_row input[name="foo"]'), 'new value'); + await testUtils.fields.triggerKeydown(list.$('tr.o_selected_row input[name="foo"]'), 'tab'); assert.strictEqual(list.$('tbody tr:first td:contains(new value)').length, 1, "should have the new value visible in dom"); @@ -2833,10 +2840,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('skip invisible fields when navigating list view with TAB', function (assert) { + QUnit.test('skip invisible fields when navigating list view with TAB', async function (assert) { assert.expect(2); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2848,20 +2855,20 @@ QUnit.module('Views', { res_id: 1, }); - testUtils.dom.click(list.$('td:contains(gnap)')); + await testUtils.dom.click(list.$('td:contains(gnap)')); assert.strictEqual(list.$('input[name="foo"]')[0], document.activeElement, "foo should be focused"); - list.$('input[name="foo"]').trigger($.Event('keydown', {which: $.ui.keyCode.TAB})); + await testUtils.fields.triggerKeydown(list.$('input[name="foo"]'), 'tab'); assert.strictEqual(list.$('input[name="int_field"]')[0], document.activeElement, "int_field should be focused"); list.destroy(); }); - QUnit.test('skip buttons when navigating list view with TAB (end)', function (assert) { + QUnit.test('skip buttons when navigating list view with TAB (end)', async function (assert) { assert.expect(2); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2872,20 +2879,20 @@ QUnit.module('Views', { res_id: 1, }); - testUtils.dom.click(list.$('tbody tr:eq(2) td:eq(1)')); + await testUtils.dom.click(list.$('tbody tr:eq(2) td:eq(1)')); assert.strictEqual(list.$('tbody tr:eq(2) input[name="foo"]')[0], document.activeElement, "foo should be focused"); - list.$('tbody tr:eq(2) input[name="foo"]').trigger($.Event('keydown', {which: $.ui.keyCode.TAB})); + await testUtils.fields.triggerKeydown(list.$('tbody tr:eq(2) input[name="foo"]'), 'tab'); assert.strictEqual(list.$('tbody tr:eq(3) input[name="foo"]')[0], document.activeElement, "next line should be selected"); list.destroy(); }); - QUnit.test('skip buttons when navigating list view with TAB (middle)', function (assert) { + QUnit.test('skip buttons when navigating list view with TAB (middle)', async function (assert) { assert.expect(2); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2899,40 +2906,40 @@ QUnit.module('Views', { res_id: 1, }); - testUtils.dom.click(list.$('tbody tr:eq(2) td:eq(2)')); + await testUtils.dom.click(list.$('tbody tr:eq(2) td:eq(2)')); assert.strictEqual(list.$('tbody tr:eq(2) input[name="foo"]')[0], document.activeElement, "foo should be focused"); - list.$('tbody tr:eq(2) input[name="foo"]').trigger($.Event('keydown', {which: $.ui.keyCode.TAB})); + await testUtils.fields.triggerKeydown(list.$('tbody tr:eq(2) input[name="foo"]'), 'tab'); assert.strictEqual(list.$('tbody tr:eq(2) input[name="int_field"]')[0], document.activeElement, "int_field should be focused"); list.destroy(); }); - QUnit.test('navigation: not moving down with keydown', function (assert) { + QUnit.test('navigation: not moving down with keydown', async function (assert) { assert.expect(2); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, arch: '<tree editable="bottom"><field name="foo"/></tree>', }); - testUtils.dom.click(list.$('td:contains(yop)')); + await testUtils.dom.click(list.$('td:contains(yop)')); assert.hasClass(list.$('tr.o_data_row:eq(0)'),'o_selected_row', "1st row should be selected"); - list.$('tr.o_selected_row input[name="foo"]').trigger({type: 'keydown', which: $.ui.keyCode.DOWN}); + await testUtils.fields.triggerKeydown(list.$('tr.o_selected_row input[name="foo"]'), 'down'); assert.hasClass(list.$('tr.o_data_row:eq(0)'),'o_selected_row', "1st row should still be selected"); list.destroy(); }); - QUnit.test('navigation: moving right with keydown from text field does not move the focus', function (assert) { + QUnit.test('navigation: moving right with keydown from text field does not move the focus', async function (assert) { assert.expect(6); this.data.foo.fields.foo.type = 'text'; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2943,7 +2950,7 @@ QUnit.module('Views', { '</tree>', }); - testUtils.dom.click(list.$('td:contains(yop)')); + await testUtils.dom.click(list.$('td:contains(yop)')); var textarea = list.$('textarea[name="foo"]')[0]; assert.strictEqual(document.activeElement, textarea, "textarea should be focused"); @@ -2956,16 +2963,16 @@ QUnit.module('Views', { "textarea should still be focused"); assert.ok(textarea.selectionStart === 3 && textarea.selectionEnd === 3, "textarea value ('yop') should not be selected and cursor should be at the end"); - $(textarea).trigger({type: 'keydown', which: $.ui.keyCode.RIGHT}); + await testUtils.fields.triggerKeydown($(textarea), 'right'); assert.strictEqual(document.activeElement, list.$('textarea[name="foo"]')[0], "next field (checkbox) should now be focused"); list.destroy(); }); - QUnit.test('discarding changes in a row properly updates the rendering', function (assert) { + QUnit.test('discarding changes in a row properly updates the rendering', async function (assert) { assert.expect(3); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -2978,27 +2985,27 @@ QUnit.module('Views', { assert.strictEqual(list.$('.o_data_cell:first').text(), "yop", "first cell should contain 'yop'"); - testUtils.dom.click(list.$('.o_data_cell:first')); - testUtils.fields.editInput(list.$('input[name="foo"]'), "hello"); - testUtils.dom.click(list.$buttons.find('.o_list_button_discard')); + await testUtils.dom.click(list.$('.o_data_cell:first')); + await testUtils.fields.editInput(list.$('input[name="foo"]'), "hello"); + await testUtils.dom.click(list.$buttons.find('.o_list_button_discard')); assert.strictEqual($('.modal:visible').length, 1, "a modal to ask for discard should be visible"); - testUtils.dom.click($('.modal:visible .btn-primary')); + await testUtils.dom.click($('.modal:visible .btn-primary')); assert.strictEqual(list.$('.o_data_cell:first').text(), "yop", "first cell should still contain 'yop'"); list.destroy(); }); - QUnit.test('numbers in list are right-aligned', function (assert) { + QUnit.test('numbers in list are right-aligned', async function (assert) { assert.expect(2); var currencies = {}; _.each(this.data.res_currency.records, function (currency) { currencies[currency.id] = currency; }); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3021,7 +3028,7 @@ QUnit.module('Views', { assert.strictEqual(nbCellRight, 2, "there should be two right-aligned cells"); - testUtils.dom.click(list.$('.o_data_cell:first')); + await testUtils.dom.click(list.$('.o_data_cell:first')); var nbInputRight = _.filter(list.$('.o_data_row:first > .o_data_cell input'), function (el) { var style = window.getComputedStyle(el); @@ -3033,7 +3040,7 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('grouped list are not editable (ungrouped first)', function (assert) { + QUnit.test('grouped list are not editable (ungrouped first)', async function (assert) { // Editable grouped list views are not supported, so the purpose of this // test is to check that when a list view is grouped, its editable // attribute is ignored @@ -3042,7 +3049,7 @@ QUnit.module('Views', { // editable assert.expect(5); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3055,28 +3062,28 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(list.$('.o_data_cell:first')); + await testUtils.dom.click(list.$('.o_data_cell:first')); assert.verifySteps([], 'no switch view should have been requested'); assert.containsOnce(list, '.o_selected_row', "a row should be in edition"); - testUtils.dom.click(list.$buttons.find('.o_list_button_discard')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_discard')); // reload with groupBy - list.reload({groupBy: ['bar']}); + await list.reload({groupBy: ['bar']}); // clicking on record should open the form view - testUtils.dom.click(list.$('.o_group_header:first')); - testUtils.dom.click(list.$('.o_data_cell:first')); + await testUtils.dom.click(list.$('.o_group_header:first')); + await testUtils.dom.click(list.$('.o_data_cell:first')); // clicking on create button should open the form view - testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); assert.verifySteps(['switch view form 1', 'switch view form false'], 'two switch view to form should have been requested'); list.destroy(); }); - QUnit.test('grouped list are not editable (grouped first)', function (assert) { + QUnit.test('grouped list are not editable (grouped first)', async function (assert) { // Editable grouped list views are not supported, so the purpose of this // test is to check that when a list view is grouped, its editable // attribute is ignored @@ -3084,7 +3091,7 @@ QUnit.module('Views', { // editable, and then it is reloaded with no groupBy and becomes editable assert.expect(6); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3100,42 +3107,40 @@ QUnit.module('Views', { // the view being grouped, it is not editable, so clicking on a record // should open the form view - testUtils.dom.click(list.$('.o_group_header:first')); - testUtils.dom.click(list.$('.o_data_cell:first')); + await testUtils.dom.click(list.$('.o_group_header:first')); + await testUtils.dom.click(list.$('.o_data_cell:first')); // for the same reason, clicking on 'Create' should open the form view - testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); assert.verifySteps(['switch view form 1', 'switch view form false'], "two switch view to form should have been requested"); // reload without groupBy - list.reload({groupBy: []}); + await list.reload({groupBy: []}); // as the view is no longer grouped, it is editable, so clicking on a // row should switch it in edition - testUtils.dom.click(list.$('.o_data_cell:first')); + await testUtils.dom.click(list.$('.o_data_cell:first')); - assert.verifySteps(['switch view form 1', 'switch view form false'], - "no more switch view should have been requested"); - assert.containsOnce(list, '.o_selected_row', - "a row should be in edition"); + assert.verifySteps([], "no more switch view should have been requested"); + assert.containsOnce(list, '.o_selected_row', "a row should be in edition"); // clicking on the body should leave the edition - testUtils.dom.click($('body')); + await testUtils.dom.click($('body')); assert.containsNone(list, '.o_selected_row', "the row should no longer be in edition"); list.destroy(); }); - QUnit.test('field values are escaped', function (assert) { + QUnit.test('field values are escaped', async function (assert) { assert.expect(1); var value = '<script>throw Error();</script>'; this.data.foo.records[0].foo = value; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3148,21 +3153,23 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('pressing ESC discard the current line changes', function (assert) { - assert.expect(3); + QUnit.test('pressing ESC discard the current line changes', async function (assert) { + assert.expect(4); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, arch: '<tree editable="top"><field name="foo"/></tree>', }); - testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + assert.containsN(list, 'tr.o_data_row', 5, + "should currently adding a 5th data row"); - list.$('input[name="foo"]').trigger({type: 'keydown', which: $.ui.keyCode.ESCAPE}); + await testUtils.fields.triggerKeydown(list.$('input[name="foo"]'), 'escape'); assert.containsN(list, 'tr.o_data_row', 4, - "should have 4 data row in list"); + "should have only 4 data row after escape"); assert.containsNone(list, 'tr.o_data_row.o_selected_row', "no rows should be selected"); assert.ok(!list.$buttons.find('.o_list_button_save').is(':visible'), @@ -3170,21 +3177,23 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('pressing ESC discard the current line changes (with required)', function (assert) { - assert.expect(3); + QUnit.test('pressing ESC discard the current line changes (with required)', async function (assert) { + assert.expect(4); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, arch: '<tree editable="top"><field name="foo" required="1"/></tree>', }); - testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + assert.containsN(list, 'tr.o_data_row', 5, + "should currently adding a 5th data row"); - list.$('input[name="foo"]').trigger({type: 'keydown', which: $.ui.keyCode.ESCAPE}); + await testUtils.fields.triggerKeydown(list.$('input[name="foo"]'), 'escape'); assert.containsN(list, 'tr.o_data_row', 4, - "should have 4 data row in list"); + "should have only 4 data row after escape"); assert.containsNone(list, 'tr.o_data_row.o_selected_row', "no rows should be selected"); assert.ok(!list.$buttons.find('.o_list_button_save').is(':visible'), @@ -3192,10 +3201,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('field with password attribute', function (assert) { + QUnit.test('field with password attribute', async function (assert) { assert.expect(2); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3210,10 +3219,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('list with handle widget', function (assert) { + QUnit.test('list with handle widget', async function (assert) { assert.expect(11); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3229,7 +3238,7 @@ QUnit.module('Views', { "should write the right field as sequence"); assert.deepEqual(args.ids, [4, 2 , 3], "should write the sequence in correct order"); - return $.when(); + return Promise.resolve(); } return this._super.apply(this, arguments); }, @@ -3245,7 +3254,7 @@ QUnit.module('Views', { "default fourth record should have amount 0"); // Drag and drop the fourth line in second position - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( list.$('.ui-sortable-handle').eq(3), list.$('tbody tr').first(), {position: 'bottom'} @@ -3263,7 +3272,7 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('result of consecutive resequences is correctly sorted', function (assert) { + QUnit.test('result of consecutive resequences is correctly sorted', async function (assert) { assert.expect(9); this.data = { // we want the data to be minimal to have a minimal test foo: { @@ -3277,7 +3286,7 @@ QUnit.module('Views', { } }; var moves = 0; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3326,28 +3335,31 @@ QUnit.module('Views', { }); assert.strictEqual(list.$('tbody tr td.o_list_number').text(), '1234', "default should be sorted by id"); - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( list.$('.ui-sortable-handle').eq(3), list.$('tbody tr').eq(2), {position: 'top'} ); assert.strictEqual(list.$('tbody tr td.o_list_number').text(), '1243', "the int_field (sequence) should have been correctly updated"); - testUtils.dom.dragAndDrop( + + await testUtils.dom.dragAndDrop( list.$('.ui-sortable-handle').eq(2), list.$('tbody tr').eq(1), {position: 'top'} ); assert.deepEqual(list.$('tbody tr td.o_list_number').text(), '1423', "the int_field (sequence) should have been correctly updated"); - testUtils.dom.dragAndDrop( + + await testUtils.dom.dragAndDrop( list.$('.ui-sortable-handle').eq(1), list.$('tbody tr').eq(3), {position: 'top'} ); assert.deepEqual(list.$('tbody tr td.o_list_number').text(), '1243', "the int_field (sequence) should have been correctly updated"); - testUtils.dom.dragAndDrop( + + await testUtils.dom.dragAndDrop( list.$('.ui-sortable-handle').eq(2), list.$('tbody tr').eq(1), {position: 'top'} @@ -3357,7 +3369,7 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('editable list with handle widget', function (assert) { + QUnit.test('editable list with handle widget', async function (assert) { assert.expect(12); // resequence makes sense on a sequence field, not on arbitrary fields @@ -3366,7 +3378,7 @@ QUnit.module('Views', { this.data.foo.records[2].int_field = 2; this.data.foo.records[3].int_field = 3; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3397,7 +3409,7 @@ QUnit.module('Views', { "default fourth record should have amount 0"); // Drag and drop the fourth line in second position - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( list.$('.ui-sortable-handle').eq(3), list.$('tbody tr').first(), {position: 'bottom'} @@ -3412,7 +3424,7 @@ QUnit.module('Views', { assert.strictEqual(list.$('tbody tr:eq(3) td:last').text(), '300', "new fourth record should have amount 300"); - testUtils.dom.click(list.$('tbody tr:eq(1) td:last')); + await testUtils.dom.click(list.$('tbody tr:eq(1) td:last')); assert.strictEqual(list.$('tbody tr:eq(1) td:last input').val(), '0', "the edited record should be the good one"); @@ -3420,7 +3432,7 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('editable list, handle widget locks and unlocks on sort', function (assert) { + QUnit.test('editable list, handle widget locks and unlocks on sort', async function (assert) { assert.expect(6); // we need another sortable field to lock/unlock the handle @@ -3431,7 +3443,7 @@ QUnit.module('Views', { this.data.foo.records[2].int_field = 2; this.data.foo.records[3].int_field = 3; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3445,7 +3457,7 @@ QUnit.module('Views', { "default should be sorted by int_field"); // Drag and drop the fourth line in second position - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( list.$('.ui-sortable-handle').eq(3), list.$('tbody tr').first(), {position: 'bottom'} @@ -3456,13 +3468,13 @@ QUnit.module('Views', { "drag and drop should have succeeded, as the handle is unlocked"); // Sorting by a field different for int_field should lock the handle - testUtils.dom.click(list.$('.o_column_sortable').eq(1)); + await testUtils.dom.click(list.$('.o_column_sortable').eq(1)); assert.strictEqual(list.$('tbody span[name="amount"]').text(), '0.00300.00500.001200.00', "should have been sorted by amount"); // Drag and drop the fourth line in second position (not) - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( list.$('.ui-sortable-handle').eq(3), list.$('tbody tr').first(), {position: 'bottom'} @@ -3472,13 +3484,13 @@ QUnit.module('Views', { "drag and drop should have failed as the handle is locked"); // Sorting by int_field should unlock the handle - testUtils.dom.click(list.$('.o_column_sortable').eq(0)); + await testUtils.dom.click(list.$('.o_column_sortable').eq(0)); assert.strictEqual(list.$('tbody span[name="amount"]').text(), '1200.000.00500.00300.00', "records should be ordered as per the previous resequence"); // Drag and drop the fourth line in second position - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( list.$('.ui-sortable-handle').eq(3), list.$('tbody tr').first(), {position: 'bottom'} @@ -3490,7 +3502,7 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('editable list with handle widget with slow network', function (assert) { + QUnit.test('editable list with handle widget with slow network', async function (assert) { assert.expect(15); // resequence makes sense on a sequence field, not on arbitrary fields @@ -3499,9 +3511,9 @@ QUnit.module('Views', { this.data.foo.records[2].int_field = 2; this.data.foo.records[3].int_field = 3; - var def = $.Deferred(); + var prom = testUtils.makeTestPromise(); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3518,14 +3530,13 @@ QUnit.module('Views', { "should write the right field as sequence"); assert.deepEqual(args.ids, [4, 2, 3], "should write the sequence in correct order"); - return $.when(def).then(function () { + return prom.then(function () { return _super(route, args); }); } return this._super.apply(this, arguments); }, }); - assert.strictEqual(list.$('tbody tr:eq(0) td:last').text(), '1200', "default first record should have amount 1200"); assert.strictEqual(list.$('tbody tr:eq(1) td:last').text(), '500', @@ -3536,19 +3547,21 @@ QUnit.module('Views', { "default fourth record should have amount 0"); // drag and drop the fourth line in second position - testUtils.dom.dragAndDrop( + await testUtils.dom.dragAndDrop( list.$('.ui-sortable-handle').eq(3), list.$('tbody tr').first(), {position: 'bottom'} ); // edit moved row before the end of resequence - testUtils.dom.click(list.$('tbody tr:eq(3) td:last')); + await testUtils.dom.click(list.$('tbody tr:eq(3) td:last')); + await testUtils.nextTick(); assert.strictEqual(list.$('tbody tr:eq(3) td:last input').length, 0, "shouldn't edit the line before resequence"); - def.resolve(); + prom.resolve(); + await testUtils.nextTick(); assert.strictEqual(list.$('tbody tr:eq(3) td:last input').length, 1, "should edit the line after resequence"); @@ -3556,10 +3569,10 @@ QUnit.module('Views', { assert.strictEqual(list.$('tbody tr:eq(3) td:last input').val(), '300', "fourth record should have amount 300"); - testUtils.fields.editInput(list.$('tbody tr:eq(3) td:last input'), 301); - testUtils.dom.click(list.$('tbody tr:eq(0) td:last')); + await testUtils.fields.editInput(list.$('tbody tr:eq(3) td:last input'), 301); + await testUtils.dom.click(list.$('tbody tr:eq(0) td:last')); - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); assert.strictEqual(list.$('tbody tr:eq(0) td:last').text(), '1200', "first record should have amount 1200"); @@ -3570,22 +3583,22 @@ QUnit.module('Views', { assert.strictEqual(list.$('tbody tr:eq(3) td:last').text(), '301', "fourth record should have amount 301"); - testUtils.dom.click(list.$('tbody tr:eq(3) td:last')); + await testUtils.dom.click(list.$('tbody tr:eq(3) td:last')); assert.strictEqual(list.$('tbody tr:eq(3) td:last input').val(), '301', "fourth record should have amount 301"); list.destroy(); }); - QUnit.test('multiple clicks on Add do not create invalid rows', function (assert) { + QUnit.test('multiple clicks on Add do not create invalid rows', async function (assert) { assert.expect(2); this.data.foo.onchanges = { m2o: function () {}, }; - var def = $.Deferred(); - var list = createView({ + var prom = testUtils.makeTestPromise(); + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3593,7 +3606,9 @@ QUnit.module('Views', { mockRPC: function (route, args) { var result = this._super.apply(this, arguments); if (args.method === 'onchange') { - return $.when(def).then(_.constant(result)); + return prom.then(function () { + return result; + }); } return result; }, @@ -3606,7 +3621,8 @@ QUnit.module('Views', { testUtils.dom.click(list.$buttons.find('.o_list_button_add')); testUtils.dom.click(list.$buttons.find('.o_list_button_add')); - def.resolve(); + prom.resolve(); + await testUtils.nextTick(); assert.containsN(list, '.o_data_row', 5, "only one record should have been created"); @@ -3614,7 +3630,7 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('reference field rendering', function (assert) { + QUnit.test('reference field rendering', async function (assert) { assert.expect(4); this.data.foo.records.push({ @@ -3622,7 +3638,7 @@ QUnit.module('Views', { reference: 'res_currency,2', }); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3641,10 +3657,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('editable list view: contexts are correctly sent', function (assert) { + QUnit.test('editable list view: contexts are correctly sent', async function (assert) { assert.expect(6); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3670,17 +3686,17 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(list.$('.o_data_cell:first')); - testUtils.fields.editInput(list.$('.o_field_widget[name=foo]'), 'abc'); - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$('.o_data_cell:first')); + await testUtils.fields.editInput(list.$('.o_field_widget[name=foo]'), 'abc'); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); list.destroy(); }); - QUnit.test('list grouped by date:month', function (assert) { + QUnit.test('list grouped by date:month', async function (assert) { assert.expect(1); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3694,10 +3710,10 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('grouped list edition with toggle_button widget', function (assert) { + QUnit.test('grouped list edition with toggle_button widget', async function (assert) { assert.expect(3); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3712,17 +3728,17 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(list.$('.o_group_header:first')); + await testUtils.dom.click(list.$('.o_group_header:first')); assert.containsOnce(list, '.o_data_row:first .o_toggle_button_success', "boolean value of the first record should be true"); - testUtils.dom.click(list.$('.o_data_row:first .o_icon_button')); + await testUtils.dom.click(list.$('.o_data_row:first .o_icon_button')); assert.strictEqual(list.$('.o_data_row:first .text-muted:not(.o_toggle_button_success)').length, 1, "boolean button should have been updated"); list.destroy(); }); - QUnit.test('grouped list view, indentation for empty group', function (assert) { + QUnit.test('grouped list view, indentation for empty group', async function (assert) { assert.expect(3); this.data.foo.fields.priority = { @@ -3734,7 +3750,7 @@ QUnit.module('Views', { this.data.foo.records.push({id: 5, foo: "blip", int_field: -7, m2o: 1, priority: 2}); this.data.foo.records.push({id: 6, foo: "blip", int_field: 5, m2o: 1, priority: 3}); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3744,7 +3760,7 @@ QUnit.module('Views', { // Override of the read_group to display the row even if there is no record in it, // to mock the behavihour of some fields e.g stage_id on the sale order. if (args.method === 'read_group' && args.kwargs.groupby[0] === "m2o") { - return $.when([ + return Promise.resolve([ { id: 8, m2o:[1,"Value 1"], @@ -3761,7 +3777,7 @@ QUnit.module('Views', { }); // open the first group - testUtils.dom.click(list.$('.o_group_header:first')); + await testUtils.dom.click(list.$('.o_group_header:first')); assert.strictEqual(list.$('th.o_group_name').eq(1).children().length, 1, "There should be an empty element creating the indentation for the subgroup."); assert.hasClass(list.$('th.o_group_name').eq(1).children().eq(0), 'fa', @@ -3771,7 +3787,7 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('basic support for widgets', function (assert) { + QUnit.test('basic support for widgets', async function (assert) { assert.expect(1); var MyWidget = Widget.extend({ @@ -3784,7 +3800,7 @@ QUnit.module('Views', { }); widgetRegistry.add('test', MyWidget); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3798,10 +3814,10 @@ QUnit.module('Views', { delete widgetRegistry.map.test; }); - QUnit.test('use the limit attribute in arch', function (assert) { + QUnit.test('use the limit attribute in arch', async function (assert) { assert.expect(3); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3821,7 +3837,7 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('check if the view destroys all widgets and instances', function (assert) { + QUnit.test('check if the view destroys all widgets and instances', async function (assert) { assert.expect(1); var instanceNumber = 0; @@ -3858,13 +3874,13 @@ QUnit.module('Views', { '</tree>', }; - var list = createView(params); + var list = await createView(params); list.destroy(); var initialInstanceNumber = instanceNumber; instanceNumber = 0; - list = createView(params); + list = await createView(params); // call destroy function of controller to ensure that it correctly destroys everything list.__destroy(); @@ -3878,12 +3894,12 @@ QUnit.module('Views', { testUtils.mock.unpatch(mixins.ParentedMixin); }); - QUnit.test('concurrent reloads finishing in inverse order', function (assert) { - assert.expect(3); + QUnit.test('concurrent reloads finishing in inverse order', async function (assert) { + assert.expect(4); var blockSearchRead = false; - var def = $.Deferred(); - var list = createView({ + var prom = testUtils.makeTestPromise(); + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3891,7 +3907,7 @@ QUnit.module('Views', { mockRPC: function (route) { var result = this._super.apply(this, arguments); if (route === '/web/dataset/search_read' && blockSearchRead) { - return $.when(def).then(_.constant(result)); + return prom.then(_.constant(result)); } return result; }, @@ -3903,6 +3919,7 @@ QUnit.module('Views', { // reload with a domain (this request is blocked) blockSearchRead = true; list.reload({domain: [['foo', '=', 'yop']]}); + await testUtils.nextTick(); assert.containsN(list, '.o_list_view .o_data_row', 4, "list view should still contain 4 records (search_read being blocked)"); @@ -3910,23 +3927,29 @@ QUnit.module('Views', { // reload without the domain blockSearchRead = false; list.reload({domain: []}); + await testUtils.nextTick(); + + assert.containsN(list, '.o_list_view .o_data_row', 4, + "list view should still contain 4 records"); // unblock the RPC - def.resolve(); + prom.resolve(); + await testUtils.nextTick(); + assert.containsN(list, '.o_list_view .o_data_row', 4, "list view should still contain 4 records"); list.destroy(); }); - QUnit.test('list view on a "noCache" model', function (assert) { - assert.expect(8); + QUnit.test('list view on a "noCache" model', async function (assert) { + assert.expect(9); testUtils.mock.patch(BasicModel, { noCacheModels: BasicModel.prototype.noCacheModels.concat(['foo']), }); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3946,20 +3969,20 @@ QUnit.module('Views', { core.bus.on('clear_cache', list, assert.step.bind(assert, 'clear_cache')); // create a new record - testUtils.dom.click(list.$buttons.find('.o_list_button_add')); - testUtils.fields.editInput(list.$('.o_selected_row .o_field_widget'), 'some value'); - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$buttons.find('.o_list_button_add')); + await testUtils.fields.editInput(list.$('.o_selected_row .o_field_widget'), 'some value'); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); // edit an existing record - testUtils.dom.click(list.$('.o_data_cell:first')); - testUtils.fields.editInput(list.$('.o_selected_row .o_field_widget'), 'new value'); - testUtils.dom.click(list.$buttons.find('.o_list_button_save')); + await testUtils.dom.click(list.$('.o_data_cell:first')); + await testUtils.fields.editInput(list.$('.o_selected_row .o_field_widget'), 'new value'); + await testUtils.dom.click(list.$buttons.find('.o_list_button_save')); // delete a record - testUtils.dom.click(list.$('.o_data_row:first .o_list_record_selector input')); - testUtils.dom.click(list.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); - testUtils.dom.click(list.sidebar.$('a:contains(Delete)')); - testUtils.dom.click($('.modal-footer .btn-primary')); + await testUtils.dom.click(list.$('.o_data_row:first .o_list_record_selector input')); + await testUtils.dom.click(list.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + await testUtils.dom.click(list.sidebar.$('a:contains(Delete)')); + await testUtils.dom.click($('.modal-footer .btn-primary')); assert.verifySteps([ 'create', @@ -3972,12 +3995,14 @@ QUnit.module('Views', { list.destroy(); testUtils.mock.unpatch(BasicModel); + + assert.verifySteps(['clear_cache']); // triggered by the test environment on destroy }); - QUnit.test('list should ask to scroll to top on page changes', function (assert) { + QUnit.test('list should ask to scroll to top on page changes', async function (assert) { assert.expect(10); - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -3994,36 +4019,34 @@ QUnit.module('Views', { }); // switch pages (should ask to scroll) - testUtils.dom.click(list.pager.$('.o_pager_next')); - testUtils.dom.click(list.pager.$('.o_pager_previous')); - + await testUtils.dom.click(list.pager.$('.o_pager_next')); + await testUtils.dom.click(list.pager.$('.o_pager_previous')); assert.verifySteps(['scroll', 'scroll'], "should ask to scroll when switching pages"); // change the limit (should not ask to scroll) - testUtils.dom.click(list.pager.$('.o_pager_value')); - list.pager.$('.o_pager_value input').val('1-2').blur(); + await testUtils.dom.click(list.pager.$('.o_pager_value')); + await testUtils.fields.editAndTrigger(list.pager.$('.o_pager_value input'), + '1-2', ['blur']); assert.strictEqual(list.pager.$('.o_pager_value').text(), '1-2', "should have changed the limit"); - assert.verifySteps(['scroll', 'scroll'], - "should not ask to scroll when changing the limit"); + assert.verifySteps([], "should not ask to scroll when changing the limit"); // switch pages again (should still ask to scroll) - testUtils.dom.click(list.pager.$('.o_pager_next')); + await testUtils.dom.click(list.pager.$('.o_pager_next')); - assert.verifySteps(['scroll', 'scroll', 'scroll'], - "this is still working after a limit change"); + assert.verifySteps(['scroll'], "this is still working after a limit change"); list.destroy(); }); - QUnit.test('list with handle field, override default_get, bottom when inline', function (assert) { + QUnit.test('list with handle field, override default_get, bottom when inline', async function (assert) { assert.expect(2); this.data.foo.fields.int_field.default = 10; - var list = createView({ + var list = await createView({ View: ListView, model: 'foo', data: this.data, @@ -4042,15 +4065,55 @@ QUnit.module('Views', { // check line is at the correct place var inputText = 'ninja'; - testUtils.dom.click($('.o_list_button_add')); - testUtils.fields.editInput(list.$('.o_input[name="foo"]'), inputText); - testUtils.dom.click($('.o_list_button_save')); - testUtils.dom.click($('.o_list_button_add')); + await testUtils.dom.click($('.o_list_button_add')); + await testUtils.fields.editInput(list.$('.o_input[name="foo"]'), inputText); + await testUtils.dom.click($('.o_list_button_save')); + await testUtils.dom.click($('.o_list_button_add')); assert.strictEqual($('.o_data_cell').text(), "blipblipyopgnap" + inputText); list.destroy(); }); + + QUnit.test('grouped list with async widget', async function (assert) { + assert.expect(4); + + var prom = testUtils.makeTestPromise(); + var AsyncWidget = Widget.extend({ + willStart: function () { + return prom; + }, + start: function () { + this.$el.text('ready'); + }, + }); + widgetRegistry.add('asyncWidget', AsyncWidget); + + var list = await createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '<tree><widget name="asyncWidget"/></tree>', + groupBy: ['int_field'], + }); + + assert.containsNone(list, '.o_data_row', "no group should be open"); + + await testUtils.dom.click(list.$('.o_group_header:first')); + + assert.containsNone(list, '.o_data_row', + "should wait for async widgets before opening the group"); + + prom.resolve(); + await testUtils.nextTick(); + + assert.containsN(list, '.o_data_row', 1, "group should be open"); + assert.strictEqual(list.$('.o_data_row .o_data_cell').text(), 'ready', + "async widget should be correctly displayed"); + + list.destroy(); + delete widgetRegistry.map.asyncWidget; + }); }); }); diff --git a/addons/web/static/tests/views/mobile_control_panel_tests.js b/addons/web/static/tests/views/mobile_control_panel_tests.js index 05e2cd09a73..19226741474 100644 --- a/addons/web/static/tests/views/mobile_control_panel_tests.js +++ b/addons/web/static/tests/views/mobile_control_panel_tests.js @@ -39,12 +39,12 @@ QUnit.module('Mobile Search view Screen', { }; }, }, function () { - QUnit.test('can activate a filter with mobile search view', function (assert) { + QUnit.test('can activate a filter with mobile search view', async function (assert) { assert.expect(3); var filterActiveFlag = false; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -57,23 +57,23 @@ QUnit.module('Mobile Search view Screen', { }, }); - actionManager.doAction(1); + await actionManager.doAction(1); assert.ok(!$('.o_mobile_search').is(':visible'), 'mobile search view is not visible'); // open the search view - testUtils.dom.click($('button.o_enable_searchview')); + await testUtils.dom.click($('button.o_enable_searchview')); assert.ok($('.o_mobile_search').is(':visible'), 'mobile search view is visible'); // open filter sub menu - testUtils.dom.click($('button.o_dropdown_toggler_btn').first()); + await testUtils.dom.click($('button.o_dropdown_toggler_btn').first()); filterActiveFlag = true; // click on Active filter - testUtils.dom.click($('.o_filters_menu a:contains(Active)')); + await testUtils.dom.click($('.o_filters_menu a:contains(Active)')); actionManager.destroy(); }); diff --git a/addons/web/static/tests/views/pivot_tests.js b/addons/web/static/tests/views/pivot_tests.js index 0e7e7ff0a2d..c098ecd7867 100644 --- a/addons/web/static/tests/views/pivot_tests.js +++ b/addons/web/static/tests/views/pivot_tests.js @@ -89,10 +89,10 @@ QUnit.module('Views', { }, function () { QUnit.module('PivotView'); - QUnit.test('simple pivot rendering', function (assert) { + QUnit.test('simple pivot rendering', async function (assert) { assert.expect(3); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -113,10 +113,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('pivot rendering with widget', function (assert) { + QUnit.test('pivot rendering with widget', async function (assert) { assert.expect(1); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -130,12 +130,12 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('pivot rendering with string attribute on field', function (assert) { + QUnit.test('pivot rendering with string attribute on field', async function (assert) { assert.expect(1); this.data.partner.fields.foo = {string: "Foo", type: "integer", store: true}; - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -149,12 +149,12 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('pivot rendering with string attribute on non stored field', function (assert) { + QUnit.test('pivot rendering with string attribute on non stored field', async function (assert) { assert.expect(1); this.data.partner.fields.fubar = {string: "Fubar", type: "integer", store:false}; - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -166,7 +166,7 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('pivot rendering with invisible attribute on field', function (assert) { + QUnit.test('pivot rendering with invisible attribute on field', async function (assert) { assert.expect(2); // when invisible, a field should neither be an active measure, // nor be a selectable measure. @@ -175,7 +175,7 @@ QUnit.module('Views', { foo2: {string: "Foo2", type: "integer", store: true} }); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -193,10 +193,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('pivot view without "string" attribute', function (assert) { + QUnit.test('pivot view without "string" attribute', async function (assert) { assert.expect(1); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -210,10 +210,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('pivot view add computed fields explicitly defined as measure', function (assert) { + QUnit.test('pivot view add computed fields explicitly defined as measure', async function (assert) { assert.expect(1); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -226,10 +226,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('clicking on a cell triggers a do_action', function (assert) { + QUnit.test('clicking on a cell triggers a do_action', async function (assert) { assert.expect(2); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -271,15 +271,15 @@ QUnit.module('Views', { assert.hasClass(pivot.$el,'o_enable_linking', "root node should have classname 'o_enable_linking'"); - testUtils.dom.click(pivot.$('.o_pivot_cell_value:contains(12)')); // should trigger a do_action + await testUtils.dom.click(pivot.$('.o_pivot_cell_value:contains(12)')); // should trigger a do_action pivot.destroy(); }); - QUnit.test('pivot view with disable_linking="True"', function (assert) { + QUnit.test('pivot view with disable_linking="True"', async function (assert) { assert.expect(2); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -297,16 +297,16 @@ QUnit.module('Views', { "root node should not have classname 'o_enable_linking'"); assert.containsOnce(pivot, '.o_pivot_cell_value', "should have one cell"); - testUtils.dom.click(pivot.$('.o_pivot_cell_value')); // should not trigger a do_action + await testUtils.dom.click(pivot.$('.o_pivot_cell_value')); // should not trigger a do_action pivot.destroy(); }); - QUnit.test('pivot view grouped by date field', function (assert) { + QUnit.test('pivot view grouped by date field', async function (assert) { assert.expect(2); var data = this.data; - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -325,10 +325,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('without measures, pivot view uses __count by default', function (assert) { + QUnit.test('without measures, pivot view uses __count by default', async function (assert) { assert.expect(2); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -347,11 +347,11 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('pivot view can be reloaded', function (assert) { + QUnit.test('pivot view can be reloaded', async function (assert) { assert.expect(4); var readGroupCount = 0; - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -368,17 +368,17 @@ QUnit.module('Views', { "should contain a pivot cell with the number of all records"); assert.strictEqual(readGroupCount, 1, "should have done 1 rpc"); - testUtils.pivot.reload(pivot, {domain: [['foo', '>', 10]]}); + await testUtils.pivot.reload(pivot, {domain: [['foo', '>', 10]]}); assert.strictEqual(pivot.$('td.o_pivot_cell_value:contains(2)').length, 1, "should contain a pivot cell with the number of remaining records"); assert.strictEqual(readGroupCount, 2, "should have done 2 rpcs"); pivot.destroy(); }); - QUnit.test('pivot view grouped by many2one field', function (assert) { + QUnit.test('pivot view grouped by many2one field', async function (assert) { assert.expect(3); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -397,12 +397,12 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('basic folding/unfolding', function (assert) { + QUnit.test('basic folding/unfolding', async function (assert) { assert.expect(7); var rpcCount = 0; - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -420,12 +420,12 @@ QUnit.module('Views', { "should have 3 rows: 1 for the opened header, and 2 for data"); // click on the opened header to close it - testUtils.dom.click(pivot.$('.o_pivot_header_cell_opened')); + await testUtils.dom.click(pivot.$('.o_pivot_header_cell_opened')); assert.containsOnce(pivot, 'tbody tr', "should have 1 row"); // click on closed header to open dropdown - testUtils.dom.click(pivot.$('tbody .o_pivot_header_cell_closed')); + await testUtils.dom.click(pivot.$('tbody .o_pivot_header_cell_closed')); assert.containsOnce(pivot, '.o_pivot_field_menu .dropdown-item[data-field="date"]:first', "should have the date field as proposition"); @@ -434,8 +434,7 @@ QUnit.module('Views', { assert.containsNone(pivot, '.o_field_selection .dropdown-item[data-field="non_stored_m2o"]:first', "should not have the non_stored_m2o field as proposition"); - - testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="date"]:first')); + await testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="date"]:first')); assert.containsN(pivot, 'tbody tr', 4, "should have 4 rows: one for header, 3 for data"); @@ -444,10 +443,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('more folding/unfolding', function (assert) { + QUnit.test('more folding/unfolding', async function (assert) { assert.expect(1); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -458,25 +457,26 @@ QUnit.module('Views', { }); // open dropdown to zoom into first row - testUtils.dom.clickFirst(pivot.$('tbody .o_pivot_header_cell_closed')); + await testUtils.dom.clickFirst(pivot.$('tbody .o_pivot_header_cell_closed')); // click on date by day pivot.$('.dropdown-menu.show .o_inline_dropdown .dropdown-menu').toggle(); // unfold inline dropdown - testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="date"][data-interval="day"]')); + await testUtils.nextTick(); + await testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="date"][data-interval="day"]')); // open dropdown to zoom into second row - testUtils.dom.clickLast(pivot.$('tbody td.o_pivot_header_cell_closed')); + await testUtils.dom.clickLast(pivot.$('tbody td.o_pivot_header_cell_closed')); assert.containsN(pivot, 'tbody tr', 7, "should have 7 rows (1 for total, 1 for xphone, 1 for xpad, 4 for data)"); pivot.destroy(); }); - QUnit.test('pivot view can be flipped', function (assert) { + QUnit.test('pivot view can be flipped', async function (assert) { assert.expect(3); var rpcCount = 0; - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -493,7 +493,7 @@ QUnit.module('Views', { "should have 3 rows: 1 for the open header, and 2 for data"); rpcCount = 0; - testUtils.dom.click(pivot.$buttons.find('.o_pivot_flip_button')); + await testUtils.dom.click(pivot.$buttons.find('.o_pivot_flip_button')); assert.strictEqual(rpcCount, 0, "should not have done any rpc"); assert.containsOnce(pivot, 'tbody tr', @@ -501,12 +501,12 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('can toggle extra measure', function (assert) { + QUnit.test('can toggle extra measure', async function (assert) { assert.expect(8); var rpcCount = 0; - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -526,8 +526,8 @@ QUnit.module('Views', { "the __count measure should not be selected"); rpcCount = 0; - testUtils.pivot.toggleMeasuresDropdown(pivot); - testUtils.pivot.clickMeasure(pivot, '__count'); + await testUtils.pivot.toggleMeasuresDropdown(pivot); + await testUtils.pivot.clickMeasure(pivot, '__count'); assert.hasClass(pivot.$buttons.find('.dropdown-item[data-field=__count]:first'),'selected', "the __count measure should be selected"); @@ -536,7 +536,7 @@ QUnit.module('Views', { assert.strictEqual(rpcCount, 2, "should have done 2 rpcs to reload data"); - testUtils.pivot.clickMeasure(pivot, '__count'); + await testUtils.pivot.clickMeasure(pivot, '__count'); assert.doesNotHaveClass(pivot.$buttons.find('.dropdown-item[data-field=__count]:first'), 'selected', "the __count measure should not be selected"); @@ -548,10 +548,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('no content helper when no active measure', function (assert) { + QUnit.test('no content helper when no active measure', async function (assert) { assert.expect(4); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -564,8 +564,8 @@ QUnit.module('Views', { assert.containsOnce(pivot, 'table', "should have a table in DOM"); - testUtils.pivot.toggleMeasuresDropdown(pivot); - testUtils.pivot.clickMeasure(pivot, '__count'); + await testUtils.pivot.toggleMeasuresDropdown(pivot); + await testUtils.pivot.clickMeasure(pivot, '__count'); assert.containsOnce(pivot, '.o_view_nocontent', "should have a no_content_helper"); @@ -574,10 +574,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('no content helper when no data', function (assert) { + QUnit.test('no content helper when no data', async function (assert) { assert.expect(4); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -590,7 +590,7 @@ QUnit.module('Views', { assert.containsOnce(pivot, 'table', "should have a table in DOM"); - testUtils.pivot.reload(pivot, {domain: [['foo', '=', 12345]]}); + await testUtils.pivot.reload(pivot, {domain: [['foo', '=', 12345]]}); assert.containsOnce(pivot, '.o_view_nocontent', "should have a no_content_helper"); @@ -599,12 +599,12 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('no content helper when no data, part 2', function (assert) { + QUnit.test('no content helper when no data, part 2', async function (assert) { assert.expect(1); this.data.partner.records = []; - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -616,10 +616,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('no content helper when no data, part 3', function (assert) { + QUnit.test('no content helper when no data, part 3', async function (assert) { assert.expect(4); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -631,27 +631,27 @@ QUnit.module('Views', { assert.containsOnce(pivot, '.o_view_nocontent', "should have a no_content_helper"); - testUtils.pivot.reload(pivot, {domain: [['foo', '=', 12345]]}); + await testUtils.pivot.reload(pivot, {domain: [['foo', '=', 12345]]}); assert.containsOnce(pivot, '.o_view_nocontent', "should still have a no_content_helper"); - testUtils.pivot.reload(pivot, {domain: []}); + await testUtils.pivot.reload(pivot, {domain: []}); assert.containsNone(pivot, '.o_view_nocontent', "should not have a no_content_helper"); // tries to open a field selection menu, to make sure it was not // removed from the dom. - testUtils.dom.clickFirst(pivot.$('.o_pivot_header_cell_closed')); + await testUtils.dom.clickFirst(pivot.$('.o_pivot_header_cell_closed')); assert.containsOnce(pivot, '.o_pivot_field_menu', "the field selector menu exists"); pivot.destroy(); }); - QUnit.test('tries to restore previous state after domain change', function (assert) { + QUnit.test('tries to restore previous state after domain change', async function (assert) { assert.expect(5); var rpcCount = 0; - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -670,10 +670,10 @@ QUnit.module('Views', { assert.strictEqual(pivot.$('.o_pivot_measure_row:contains(Foo)').length, 1, "should have 1 row for measure Foo"); - testUtils.pivot.reload(pivot, {domain: [['foo', '=', 12345]]}); + await testUtils.pivot.reload(pivot, {domain: [['foo', '=', 12345]]}); rpcCount = 0; - testUtils.pivot.reload(pivot, {domain: []}); + await testUtils.pivot.reload(pivot, {domain: []}); assert.equal(rpcCount, 2, "should have reloaded data"); assert.containsN(pivot, '.o_pivot_cell_value', 3, @@ -683,10 +683,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('can be grouped with the update function', function (assert) { + QUnit.test('can be grouped with the update function', async function (assert) { assert.expect(4); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -700,7 +700,7 @@ QUnit.module('Views', { assert.containsOnce(pivot, 'tbody tr', "should have 1 rows"); - testUtils.pivot.reload(pivot, {groupBy: ['product_id']}); + await testUtils.pivot.reload(pivot, {groupBy: ['product_id']}); assert.containsN(pivot, '.o_pivot_cell_value', 3, "should have 3 cells"); @@ -709,10 +709,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('can sort data in a column by clicking on header', function (assert) { + QUnit.test('can sort data in a column by clicking on header', async function (assert) { assert.expect(3); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -725,12 +725,12 @@ QUnit.module('Views', { assert.strictEqual($('td.o_pivot_cell_value').text(), "321220", "should have proper values in cells (total, result 1, result 2"); - testUtils.dom.click(pivot.$('th.o_pivot_measure_row')); + await testUtils.dom.click(pivot.$('th.o_pivot_measure_row')); assert.strictEqual($('td.o_pivot_cell_value').text(), "322012", "should have proper values in cells (total, result 2, result 1"); - testUtils.dom.click(pivot.$('th.o_pivot_measure_row')); + await testUtils.dom.click(pivot.$('th.o_pivot_measure_row')); assert.strictEqual($('td.o_pivot_cell_value').text(), "321220", "should have proper values in cells (total, result 1, result 2"); @@ -738,11 +738,11 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('can expand all rows', function (assert) { + QUnit.test('can expand all rows', async function (assert) { assert.expect(7); var nbReadGroups = 0; - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -764,22 +764,22 @@ QUnit.module('Views', { // expand on date:days, product nbReadGroups = 0; - testUtils.pivot.reload(pivot, {groupBy: ['date:days', 'product_id']}); + await testUtils.pivot.reload(pivot, {groupBy: ['date:days', 'product_id']}); assert.strictEqual(nbReadGroups, 3, "should have done 3 read_group RPCS"); assert.containsN(pivot, 'tbody tr', 8, "should have 7 rows (total + 3 for December and 2 for October and April)"); // collapse the last two rows - testUtils.dom.clickLast(pivot.$('.o_pivot_header_cell_opened')); - testUtils.dom.clickLast(pivot.$('.o_pivot_header_cell_opened')); + await testUtils.dom.clickLast(pivot.$('.o_pivot_header_cell_opened')); + await testUtils.dom.clickLast(pivot.$('.o_pivot_header_cell_opened')); assert.containsN(pivot, 'tbody tr', 6, "should have 6 rows now"); // expand all nbReadGroups = 0; - testUtils.dom.click(pivot.$buttons.find('.o_pivot_expand_button')); + await testUtils.dom.click(pivot.$buttons.find('.o_pivot_expand_button')); assert.strictEqual(nbReadGroups, 3, "should have done 3 read_group RPCS"); assert.containsN(pivot, 'tbody tr', 8, @@ -788,11 +788,11 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('expand all with a delay', function (assert) { + QUnit.test('expand all with a delay', async function (assert) { assert.expect(3); var def; - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -803,40 +803,40 @@ QUnit.module('Views', { mockRPC: function (route, args) { var result = this._super.apply(this, arguments); if (args.method === 'read_group') { - return $.when(def).then(_.constant(result)); + return Promise.resolve(def).then(_.constant(result)); } return result; }, }); // expand on date:days, product - testUtils.pivot.reload(pivot, {groupBy: ['date:days', 'product_id']}); + await testUtils.pivot.reload(pivot, {groupBy: ['date:days', 'product_id']}); assert.containsN(pivot, 'tbody tr', 8, "should have 7 rows (total + 3 for December and 2 for October and April)"); // collapse the last two rows - testUtils.dom.clickLast(pivot.$('.o_pivot_header_cell_opened')); - testUtils.dom.clickLast(pivot.$('.o_pivot_header_cell_opened')); + await testUtils.dom.clickLast(pivot.$('.o_pivot_header_cell_opened')); + await testUtils.dom.clickLast(pivot.$('.o_pivot_header_cell_opened')); assert.containsN(pivot, 'tbody tr', 6, "should have 6 rows now"); // expand all - def = $.Deferred(); - testUtils.dom.click(pivot.$buttons.find('.o_pivot_expand_button')); + def = testUtils.makeTestPromise(); + await testUtils.dom.click(pivot.$buttons.find('.o_pivot_expand_button')); def.resolve(); - + await testUtils.nextTick(); assert.containsN(pivot, 'tbody tr', 8, "should have 8 rows again"); pivot.destroy(); }); - QUnit.test('can download a file', function (assert) { + QUnit.test('can download a file', async function (assert) { assert.expect(1); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -853,16 +853,16 @@ QUnit.module('Views', { }, }); - testUtils.dom.click(pivot.$buttons.find('.o_pivot_download')); + await testUtils.dom.click(pivot.$buttons.find('.o_pivot_download')); pivot.destroy(); }); - QUnit.test('download button is disabled when there is no data', function (assert) { + QUnit.test('download button is disabled when there is no data', async function (assert) { assert.expect(1); this.data.partner.records = []; - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -877,10 +877,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('getOwnedQueryParams correctly returns measures and groupbys', function (assert) { + QUnit.test('getOwnedQueryParams correctly returns measures and groupbys', async function (assert) { assert.expect(3); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -899,8 +899,8 @@ QUnit.module('Views', { }, "context should be correct"); // expand header on field customer - testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed:nth(1)')); - testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="customer"]:first')); + await testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed:nth(1)')); + await testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="customer"]:first')); assert.deepEqual(pivot.getOwnedQueryParams(), { context: { pivot_column_groupby: ['date:day', 'customer'], @@ -910,8 +910,8 @@ QUnit.module('Views', { }, "context should be correct"); // expand row on field product_id - testUtils.dom.click(pivot.$('tbody .o_pivot_header_cell_closed')); - testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="product_id"]:first')); + await testUtils.dom.click(pivot.$('tbody .o_pivot_header_cell_closed')); + await testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="product_id"]:first')); assert.deepEqual(pivot.getOwnedQueryParams(), { context: { pivot_column_groupby: ['date:day', 'customer'], @@ -923,13 +923,13 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('correctly remove pivot_ keys from the context', function (assert) { + QUnit.test('correctly remove pivot_ keys from the context', async function (assert) { assert.expect(5); this.data.partner.fields.amount = {string: "Amount", type: "float"}; // Equivalent to loading with default filter - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -961,7 +961,7 @@ QUnit.module('Views', { }, "context should be correct"); // Let's get rid of the rows groupBy - pivot.$('tbody .o_pivot_header_cell_opened').click(); + await testUtils.dom.click(pivot.$('tbody .o_pivot_header_cell_opened')); assert.deepEqual(pivot.getOwnedQueryParams(), { context: { @@ -972,7 +972,7 @@ QUnit.module('Views', { }, "context should be correct"); // And now, get rid of the col groupby - pivot.$('thead .o_pivot_header_cell_opened').click(); + await testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_opened')); assert.deepEqual(pivot.getOwnedQueryParams(), { context: { @@ -982,8 +982,8 @@ QUnit.module('Views', { }, }, "context should be correct"); - pivot.$('tbody .o_pivot_header_cell_closed').click(); - pivot.$('.o_pivot_field_menu .dropdown-item[data-field="product_id"]:first').click(); + await testUtils.dom.click(pivot.$('tbody .o_pivot_header_cell_closed')); + await testUtils.dom.click( pivot.$('.o_pivot_field_menu .dropdown-item[data-field="product_id"]:first')); assert.deepEqual(pivot.getOwnedQueryParams(), { context: { @@ -993,8 +993,8 @@ QUnit.module('Views', { }, }, "context should be correct"); - pivot.$('thead .o_pivot_header_cell_closed').click(); - pivot.$('.o_pivot_field_menu .dropdown-item[data-field="customer"]:first').click(); + await testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed')); + await testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="customer"]:first')); assert.deepEqual(pivot.getOwnedQueryParams(), { context: { @@ -1007,10 +1007,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('Unload Filter, reset display, load another filter', function (assert) { + QUnit.test('Unload Filter, reset display, load another filter', async function (assert) { assert.expect(18); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -1046,10 +1046,10 @@ QUnit.module('Views', { var reloadParams = { context: {}, }; - pivot.reload(reloadParams); + await pivot.reload(reloadParams); // collapse all headers - pivot.$('.o_pivot_header_cell_opened').click(); - pivot.$('.o_pivot_header_cell_opened').click(); + await testUtils.dom.click(pivot.$('.o_pivot_header_cell_opened:first')); + await testUtils.dom.click(pivot.$('.o_pivot_header_cell_opened')); // Check Columns assert.strictEqual(pivot.$('thead .o_pivot_header_cell_closed').length, 1, @@ -1075,7 +1075,7 @@ QUnit.module('Views', { pivot_row_groupby: ['product_id'], }, }; - pivot.reload(reloadParams); + await pivot.reload(reloadParams); // Check Columns assert.strictEqual(pivot.$('thead .o_pivot_header_cell_opened').length, 1, @@ -1096,12 +1096,12 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('correctly uses pivot_ keys from the context', function (assert) { + QUnit.test('correctly uses pivot_ keys from the context', async function (assert) { assert.expect(7); this.data.partner.fields.amount = {string: "Amount", type: "float"}; - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -1138,12 +1138,12 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('correctly uses pivot_ keys from the context (at reload)', function (assert) { + QUnit.test('correctly uses pivot_ keys from the context (at reload)', async function (assert) { assert.expect(8); this.data.partner.fields.amount = {string: "Amount", type: "float"}; - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -1163,7 +1163,7 @@ QUnit.module('Views', { pivot_row_groupby: ['product_id'], }, }; - testUtils.pivot.reload(pivot, reloadParams); + await testUtils.pivot.reload(pivot, reloadParams); assert.containsOnce(pivot, 'thead .o_pivot_header_cell_opened', "column: should have one opened header"); @@ -1185,10 +1185,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('correctly use group_by key from the context', function (assert) { + QUnit.test('correctly use group_by key from the context', async function (assert) { assert.expect(7); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: 'partner', data: this.data, @@ -1219,12 +1219,12 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('pivot still handles __count__ measure', function (assert) { + QUnit.test('pivot still handles __count__ measure', async function (assert) { // for retro-compatibility reasons, the pivot view still handles // '__count__' measure. assert.expect(2); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -1249,10 +1249,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('not use a many2one as a measure by default', function (assert) { + QUnit.test('not use a many2one as a measure by default', async function (assert) { assert.expect(1); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -1266,10 +1266,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('use a many2one as a measure with specified additional measure', function (assert) { + QUnit.test('use a many2one as a measure with specified additional measure', async function (assert) { assert.expect(1); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -1286,10 +1286,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('pivot view with many2one field as a measure', function (assert) { + QUnit.test('pivot view with many2one field as a measure', async function (assert) { assert.expect(1); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -1304,10 +1304,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('m2o as measure, drilling down into data', function (assert) { + QUnit.test('m2o as measure, drilling down into data', async function (assert) { assert.expect(1); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -1315,20 +1315,20 @@ QUnit.module('Views', { '<field name="product_id" type="measure"/>' + '</pivot>', }); - testUtils.dom.click(pivot.$('tbody .o_pivot_header_cell_closed')); + await testUtils.dom.click(pivot.$('tbody .o_pivot_header_cell_closed')); // click on date by month pivot.$('.dropdown-menu.show .o_inline_dropdown .dropdown-menu').toggle(); // unfold inline dropdown - testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="date"][data-interval="month"]')); + await testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="date"][data-interval="month"]')); assert.strictEqual(pivot.$('.o_pivot_cell_value').text(), '2211', 'should have loaded the proper data'); pivot.destroy(); }); - QUnit.test('pivot view with same many2one field as a measure and grouped by', function (assert) { + QUnit.test('pivot view with same many2one field as a measure and grouped by', async function (assert) { assert.expect(1); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -1340,17 +1340,17 @@ QUnit.module('Views', { }, }); - testUtils.pivot.toggleMeasuresDropdown(pivot); - testUtils.pivot.clickMeasure(pivot, 'product_id'); + await testUtils.pivot.toggleMeasuresDropdown(pivot); + await testUtils.pivot.clickMeasure(pivot, 'product_id'); assert.strictEqual(pivot.$('.o_pivot_cell_value').text(), '421131', 'should have loaded the proper data'); pivot.destroy(); }); - QUnit.test('pivot view with same many2one field as a measure and grouped by (and drill down)', function (assert) { + QUnit.test('pivot view with same many2one field as a measure and grouped by (and drill down)', async function (assert) { assert.expect(1); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -1359,19 +1359,19 @@ QUnit.module('Views', { '</pivot>', }); - testUtils.dom.click(pivot.$('tbody .o_pivot_header_cell_closed')); + await testUtils.dom.click(pivot.$('tbody .o_pivot_header_cell_closed')); - testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="product_id"]:first')); + await testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field="product_id"]:first')); assert.strictEqual(pivot.$('.o_pivot_cell_value').text(), '211', 'should have loaded the proper data'); pivot.destroy(); }); - QUnit.test('Row and column groupbys plus a domain', function (assert) { + QUnit.test('Row and column groupbys plus a domain', async function (assert) { assert.expect(3); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -1381,15 +1381,15 @@ QUnit.module('Views', { }); // Set a column groupby - testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed')); - testUtils.dom.click(pivot.$('.o_field_selection .dropdown-item[data-field=customer]:first')); + await testUtils.dom.click(pivot.$('thead .o_pivot_header_cell_closed')); + await testUtils.dom.click(pivot.$('.o_field_selection .dropdown-item[data-field=customer]:first')); // Set a Row groupby - testUtils.dom.click(pivot.$('tbody .o_pivot_header_cell_closed')); - testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field=product_id]:first')); + await testUtils.dom.click(pivot.$('tbody .o_pivot_header_cell_closed')); + await testUtils.dom.click(pivot.$('.o_pivot_field_menu .dropdown-item[data-field=product_id]:first')); // Set a domain - testUtils.pivot.reload(pivot, {domain: [['product_id', '=', 41]]}); + await testUtils.pivot.reload(pivot, {domain: [['product_id', '=', 41]]}); var expectedContext = { context: { @@ -1413,12 +1413,12 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('parallel data loading should discard all but the last one', function (assert) { + QUnit.test('parallel data loading should discard all but the last one', async function (assert) { assert.expect(2); var def; - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -1428,17 +1428,17 @@ QUnit.module('Views', { mockRPC: function (route, args) { var result = this._super.apply(this, arguments); if (args.method === 'read_group') { - return $.when(def).then(_.constant(result)); + return Promise.resolve(def).then(_.constant(result)); } return result; }, }); - def = $.Deferred(); + def = testUtils.makeTestPromise(); testUtils.pivot.reload(pivot, {groupBy: ['product_id']}); testUtils.pivot.reload(pivot, {groupBy: ['product_id', 'customer']}); - def.resolve(); - + await def.resolve(); + await testUtils.nextTick(); assert.containsN(pivot, '.o_pivot_cell_value', 6, "should have 6 cells"); assert.containsN(pivot, 'tbody tr', 6, @@ -1446,7 +1446,7 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('pivot measures should be alphabetically sorted', function (assert) { + QUnit.test('pivot measures should be alphabetically sorted', async function (assert) { assert.expect(2); var data = this.data; @@ -1456,7 +1456,7 @@ QUnit.module('Views', { data.partner.fields.modd = {string: "modd", type: "integer"}; data.partner.fields.zip = {string: "Zip", type: "integer"}; - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: data, @@ -1475,10 +1475,10 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('pivot view should use default order for auto sorting', function (assert) { + QUnit.test('pivot view should use default order for auto sorting', async function (assert) { assert.expect(1); - var pivot = createView({ + var pivot = await createView({ View: PivotView, model: "partner", data: this.data, @@ -1493,7 +1493,7 @@ QUnit.module('Views', { pivot.destroy(); }); - QUnit.test('rendering of pivot view with comparison', function (assert) { + QUnit.test('rendering of pivot view with comparison', async function (assert) { assert.expect(91); this.data.partner.records[0].date = '2016-12-15'; @@ -1522,7 +1522,7 @@ QUnit.module('Views', { } // create an action manager to test the interactions with the search view - var actionManager = createActionManager({ + var actionManager = await createActionManager({ data: this.data, archs: { 'partner,false,pivot': '<pivot>' + @@ -1533,7 +1533,7 @@ QUnit.module('Views', { } }); - actionManager.doAction({ + await actionManager.doAction({ res_model: 'partner', type: 'ir.actions.act_window', views: [[false, 'pivot']], @@ -1547,17 +1547,17 @@ QUnit.module('Views', { // with no data - testUtils.dom.click($('.o_time_range_menu_button')); - testUtils.dom.click($('.o_time_range_menu .o_comparison_checkbox')); + await testUtils.dom.click($('.o_time_range_menu_button')); + await testUtils.dom.click($('.o_time_range_menu .o_comparison_checkbox')); $('.o_time_range_selector').val('today'); - testUtils.dom.click($('.o_time_range_menu .o_apply_range')); + await testUtils.dom.click($('.o_time_range_menu .o_apply_range')); assert.strictEqual($('.o_pivot p.o_view_nocontent_empty_folder').length, 1); // with data, no row groupby - testUtils.dom.click($('.o_time_range_menu_button')); + await testUtils.dom.click($('.o_time_range_menu_button')); $('.o_time_range_selector').val('this_month'); - testUtils.dom.click($('.o_time_range_menu .o_apply_range')); + await testUtils.dom.click($('.o_time_range_menu .o_apply_range')); results = [ "13", "0", "100%", "0", "19", "-100%", "13", "19", "-31.58%" ]; @@ -1565,8 +1565,8 @@ QUnit.module('Views', { // with data, with row groupby - testUtils.dom.click($('.o_pivot .o_pivot_header_cell_closed').eq(2)); - testUtils.dom.click($('.o_pivot .o_field_selection a[data-field="product_id"]')); + await testUtils.dom.click($('.o_pivot .o_pivot_header_cell_closed').eq(2)); + await testUtils.dom.click($('.o_pivot .o_field_selection a[data-field="product_id"]')); results = [ "13", "0", "100%", "0", "19", "-100%", "13", "19", "-31.58%" , "12", "0", "100%", "12", "0" , "100%" , @@ -1574,9 +1574,9 @@ QUnit.module('Views', { ]; checkCellValues(results); - testUtils.pivot.toggleMeasuresDropdown(actionManager.getCurrentController().widget); - testUtils.dom.click($('.o_control_panel div.o_pivot_measures_list a[data-field="foo"]')); - testUtils.dom.click($('.o_control_panel div.o_pivot_measures_list a[data-field="product_id"]')); + await testUtils.pivot.toggleMeasuresDropdown(actionManager.getCurrentController().widget); + await testUtils.dom.click($('.o_control_panel div.o_pivot_measures_list a[data-field="foo"]')); + await testUtils.dom.click($('.o_control_panel div.o_pivot_measures_list a[data-field="product_id"]')); results = [ "2", "0", "100%", "0", "1", "-100%", "2", "1", "100%" , "1", "0", "100%", "1", "0" , "100%" , @@ -1584,8 +1584,8 @@ QUnit.module('Views', { ]; checkCellValues(results); - testUtils.dom.click($('.o_control_panel div.o_pivot_measures_list a[data-field="__count"]')); - testUtils.dom.click($('.o_control_panel div.o_pivot_measures_list a[data-field="product_id"]')); + await testUtils.dom.click($('.o_control_panel div.o_pivot_measures_list a[data-field="__count"]')); + await testUtils.dom.click($('.o_control_panel div.o_pivot_measures_list a[data-field="product_id"]')); results = [ "2", "0", "100%", "0", "2", "-100%", "2", "2", "0%" , "1", "0", "100%", "1", "0" , "100%" , @@ -1593,7 +1593,7 @@ QUnit.module('Views', { ]; checkCellValues(results); - testUtils.dom.clickFirst($('.o_pivot .o_pivot_header_cell_opened')); + await testUtils.dom.clickFirst($('.o_pivot .o_pivot_header_cell_opened')); results = [ "2", "2", "0%" , "1", "0", "100%" , @@ -1611,7 +1611,7 @@ QUnit.module('Views', { actionManager.destroy(); }); - QUnit.test('export data in excel with comparison', function (assert) { + QUnit.test('export data in excel with comparison', async function (assert) { assert.expect(10); this.data.partner.records[0].date = '2016-12-15'; @@ -1622,7 +1622,7 @@ QUnit.module('Views', { var unpatchDate = patchDate(2016, 11, 20, 1, 0, 0); // create an action manager to test the interactions with the search view - var actionManager = createActionManager({ + var actionManager = await createActionManager({ data: this.data, archs: { 'partner,false,pivot': '<pivot>' + @@ -1635,11 +1635,14 @@ QUnit.module('Views', { get_file: function (args) { var data = JSON.parse(args.data.data); _.each(data.headers, function (l) { - assert.step(l.map(function (o) {return o.title;})); + var titles = l.map(function (o) {return o.title;}); + assert.step(JSON.stringify(titles)); }); - assert.step(data.measure_row.map(function (o) {return o.measure;})); - assert.step(data.nbr_measures); - assert.step(data.rows.map(function (o) {return o.values.length;})); + var measures = data.measure_row.map(function (o) {return o.measure;}); + assert.step(JSON.stringify(measures)); + assert.step(String(data.nbr_measures)); + var valuesLength = data.rows.map(function (o) {return o.values.length;}); + assert.step(JSON.stringify(valuesLength)); assert.strictEqual(args.url, '/web/pivot/export_xls', "should call get_file with correct parameters"); args.complete(); @@ -1647,20 +1650,20 @@ QUnit.module('Views', { }, }); - actionManager.doAction({ + await actionManager.doAction({ res_model: 'partner', type: 'ir.actions.act_window', views: [[false, 'pivot']], }); // open time range menu - testUtils.dom.click($('.o_control_panel .o_time_range_menu_button')); + await testUtils.dom.click($('.o_control_panel .o_time_range_menu_button')); // select 'Today' as range $('.o_control_panel .o_time_range_selector').val('today'); // check checkbox 'Compare To' - testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_comparison_checkbox')); + await testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_comparison_checkbox')); // Click on 'Apply' button - testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_apply_range')); + await testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_apply_range')); // the time range menu configuration is by now: Date, Today, checkbox checked, Previous Period // With the data above, the time ranges contain no record. @@ -1670,38 +1673,35 @@ QUnit.module('Views', { assert.ok($('.o_control_panel button.o_pivot_download').prop('disabled')); // open time range menu - testUtils.dom.click($('.o_control_panel .o_time_range_menu_button')); + await testUtils.dom.click($('.o_control_panel .o_time_range_menu_button')); // select 'This Month' as date range $('.o_control_panel .o_time_range_selector').val('this_month'); // Click on 'Apply' button - testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_apply_range')); + await testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_apply_range')); // the time range menu configuration is by now: Date, This Month, checkbox checked, Previous Period // With the data above, the time ranges contain some records. // export data. Should execute 'get_file' - testUtils.dom.click($('.o_control_panel button.o_pivot_download')); + await testUtils.dom.click($('.o_control_panel button.o_pivot_download')); assert.verifySteps([ // Headers - ["Total", ""], - ["December 2016" , "November 2016"], - ["Foo", "Foo", "Foo"], - [ - "This Month", "Previous Period", "Variation", - "This Month", "Previous Period", "Variation", - "This Month", "Previous Period", "Variation" - ], + '["Total",""]', + '["December 2016","November 2016"]', + '["Foo","Foo","Foo"]', + '["This Month","Previous Period","Variation","This Month","Previous Period"' + + ',"Variation","This Month","Previous Period","Variation"]', // number of 'measures' - 3, + '3', // rows values length - [9] + '[9]', ]); unpatchDate(); actionManager.destroy(); }); - QUnit.test('rendering of pivot view with comparison and count measure', function (assert) { + QUnit.test('rendering of pivot view with comparison and count measure', async function (assert) { assert.expect(10); var mockMock = false; @@ -1715,7 +1715,7 @@ QUnit.module('Views', { var unpatchDate = patchDate(2016, 11, 20, 1, 0, 0); // create an action manager to test the interactions with the search view - var actionManager = createActionManager({ + var actionManager = await createActionManager({ data: this.data, archs: { 'partner,false,pivot': '<pivot>' + @@ -1731,14 +1731,14 @@ QUnit.module('Views', { // this modification is necessary because mockReadGroup does not // properly reflect the server response when there is no record // and a groupby list of length at least one. - return $.when([{}]); + return Promise.resolve([{}]); } } return result; }, }); - actionManager.doAction({ + await actionManager.doAction({ res_model: 'partner', type: 'ir.actions.act_window', views: [[false, 'pivot']], @@ -1747,10 +1747,10 @@ QUnit.module('Views', { mockMock = true; // activate 'This Month' and 'Previous Period' in time range menu - testUtils.dom.click($('.o_control_panel .o_time_range_menu_button')); + await testUtils.dom.click($('.o_control_panel .o_time_range_menu_button')); $('.o_control_panel .o_time_range_selector').val('this_month'); - testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_comparison_checkbox')); - testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_apply_range')); + await testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_comparison_checkbox')); + await testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_apply_range')); var results = [ "4", "0", "100%", @@ -1768,7 +1768,7 @@ QUnit.module('Views', { }); QUnit.module('Sort in comparison mode', { - beforeEach: function () { + beforeEach: async function () { this.data.partner.records[0].date = '2016-12-15'; this.data.partner.records[1].date = '2016-12-17'; this.data.partner.records[2].date = '2016-11-22'; @@ -1792,7 +1792,7 @@ QUnit.module('Views', { }; // create an action manager to test the interactions with the search view - this.actionManager = createActionManager({ + this.actionManager = await createActionManager({ data: this.data, archs: { 'partner,false,pivot': '<pivot>' + @@ -1802,10 +1802,9 @@ QUnit.module('Views', { '</pivot>', 'partner,false,search': '<search></search>', }, - debug: 1, }); - this.actionManager.doAction({ + await this.actionManager.doAction({ res_model: 'partner', type: 'ir.actions.act_window', views: [[false, 'pivot']], @@ -1817,13 +1816,13 @@ QUnit.module('Views', { }); // open time range menu - testUtils.dom.click($('.o_control_panel .o_time_range_menu_button')); + await testUtils.dom.click($('.o_control_panel .o_time_range_menu_button')); // select 'Today' as range - testUtils.fields.editInput($('.o_control_panel .o_time_range_selector'), 'this_month'); + await testUtils.fields.editInput($('.o_control_panel .o_time_range_selector'), 'this_month'); // check checkbox 'Compare To' - testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_comparison_checkbox')); + await testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_comparison_checkbox')); // Click on 'Apply' button - testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_apply_range')); + await testUtils.dom.click($('.o_control_panel .o_time_range_menu .o_apply_range')); // We are in comparison mode }, afterEach: function () { @@ -1831,11 +1830,11 @@ QUnit.module('Views', { this.actionManager.destroy(); }, }, function () { - QUnit.test('when clicking on measure, sort by "This Month" (period of interest)', function (assert) { + QUnit.test('when clicking on measure, sort by "This Month" (period of interest)', async function (assert) { assert.expect(1); // click on 'Foo' in column Total/Company - testUtils.dom.click($('.o_pivot_measure_row').eq(0)); + await testUtils.dom.click($('.o_pivot_measure_row').eq(0)); var results = [ "12", "17", "-29.41%", "1", "2", "-50%", "13", "19", "-31.58%", "12", "0", "100%", "12", "0" , "100%", @@ -1849,11 +1848,11 @@ QUnit.module('Views', { QUnit.test( 'click on a period of interest header sort according to the appropriate column ' + 'first in ascending order then in descending order', - function (assert) { + async function (assert) { assert.expect(2); // click on 'This Month' in column Total/Individual/Foo - testUtils.dom.click($('.o_pivot_measure_row').eq(6)); + await testUtils.dom.click($('.o_pivot_measure_row').eq(6)); var results = [ "12", "17", "-29.41%", "1", "2", "-50%", "13", "19", "-31.58%", "1", "0", "100%", "1" , "0", "100%", @@ -1865,7 +1864,7 @@ QUnit.module('Views', { assert.strictEqual(actualResult, results.join('')); // click once again on 'This Month' in column Total/Individual/Foo - testUtils.dom.click($('.o_pivot_measure_row').eq(6)); + await testUtils.dom.click($('.o_pivot_measure_row').eq(6)); results = [ "12", "17", "-29.41%", "1", "2", "-50%", "13", "19", "-31.58%", "12", "0", "100%", "12", "0" , "100%", @@ -1877,11 +1876,11 @@ QUnit.module('Views', { }); QUnit.test('click on a period of comparison header sort according to appropriate column', - function (assert) { + async function (assert) { assert.expect(1); // click on 'Previous Period' in column Total/Individual/Foo - testUtils.dom.click($('.o_pivot_measure_row').eq(7)); + await testUtils.dom.click($('.o_pivot_measure_row').eq(7)); var results = [ "12", "17", "-29.41%", "1", "2", "-50%", "13", "19", "-31.58%", "0", "2", "-100%", "0" , "2" , "-100%", @@ -1893,11 +1892,11 @@ QUnit.module('Views', { }); QUnit.test('click on a variation header sort according to appropriate column', - function (assert) { + async function (assert) { assert.expect(1); // click on 'Variation' in column Total/Individual/Foo - testUtils.dom.click($('.o_pivot_measure_row').eq(11)); + await testUtils.dom.click($('.o_pivot_measure_row').eq(11)); var results = [ "12", "17", "-29.41%", "1", "2", "-50%", "13", "19", "-31.58%", "12", "0", "100%", "12", "0" , "100%", diff --git a/addons/web/static/tests/views/qweb_tests.js b/addons/web/static/tests/views/qweb_tests.js index e9b7398fd09..c7872690e49 100644 --- a/addons/web/static/tests/views/qweb_tests.js +++ b/addons/web/static/tests/views/qweb_tests.js @@ -8,9 +8,9 @@ QUnit.module("Views", { }, function () { QUnit.module("QWeb"); - QUnit.test("basic", function (assert) { - assert.expect(13); - var am = utils.createActionManager({ + QUnit.test("basic", async function (assert) { + assert.expect(14); + var am = await utils.createActionManager({ data: { test: { fields: {}, @@ -27,7 +27,7 @@ QUnit.module("Views", { case 'test.qweb_render_view': assert.step('fetch'); assert.equal(args.kwargs.view_id, 5); - return $.when( + return Promise.resolve( '<div>foo' + '<div data-model="test" data-method="wheee" data-id="42" data-other="5">' + '<a type="toggle" class="fa fa-caret-right">Unfold</a>' + @@ -38,7 +38,7 @@ QUnit.module("Views", { assert.step('unfold'); assert.deepEqual(args.args, [42]); assert.deepEqual(args.kwargs, {other: 5}); - return $.when('<div id="sub">ok</div>'); + return Promise.resolve('<div id="sub">ok</div>'); } } return this._super.apply(this, arguments); @@ -51,15 +51,18 @@ QUnit.module("Views", { views: [[false, 'qweb']], res_model: 'test', }).then(function () { resolved = true; }); - assert.ok(resolved, "Action should have resolved synchronously"); + assert.ok(!resolved, "Action cannot be resolved synchronously"); + + await utils.nextTick(); + assert.ok(resolved, "Action is resolved asynchronously"); var $content = am.$('.o_content'); assert.ok(/^\s*foo/.test($content.text())); - utils.dom.click($content.find('[type=toggle]')); + await utils.dom.click($content.find('[type=toggle]')); assert.equal($content.find('div#sub').text(), 'ok', 'should have unfolded the sub-item'); - utils.dom.click($content.find('[type=toggle]')); + await utils.dom.click($content.find('[type=toggle]')); assert.equal($content.find('div#sub').length, 0, "should have removed the sub-item"); - utils.dom.click($content.find('[type=toggle]')); + await utils.dom.click($content.find('[type=toggle]')); assert.verifySteps(['fetch', 'unfold', 'unfold']); } finally { diff --git a/addons/web/static/tests/views/search_filter_menu_tests.js b/addons/web/static/tests/views/search_filter_menu_tests.js index 92d49cc23ae..62cfaccdc93 100644 --- a/addons/web/static/tests/views/search_filter_menu_tests.js +++ b/addons/web/static/tests/views/search_filter_menu_tests.js @@ -11,8 +11,9 @@ function createFilterMenu(filters, fields, params) { var target = params.debug ? document.body : $('#qunit-fixture'); var menu = new FilterMenu(null, filters, fields); testUtils.mock.addMockEnvironment(menu, params); - menu.appendTo(target); - return menu; + return menu.appendTo(target).then(function() { + return menu; + }); } QUnit.module('FilterMenu', { @@ -33,11 +34,11 @@ QUnit.module('FilterMenu', { }, }, function () { - QUnit.test('simple rendering with no filter', function (assert) { + QUnit.test('simple rendering with no filter', async function (assert) { assert.expect(3); - var filterMenu = createFilterMenu([], this.fields); - testUtils.dom.click(filterMenu.$('span.fa-filter')); + var filterMenu = await createFilterMenu([], this.fields); + await testUtils.dom.click(filterMenu.$('span.fa-filter')); assert.containsNone(filterMenu, '.dropdown-divider'); assert.containsNone(filterMenu, '.o_add_filter_menu'); assert.containsOnce(filterMenu, '.o_add_custom_filter', @@ -45,24 +46,24 @@ QUnit.module('FilterMenu', { filterMenu.destroy(); }); - QUnit.test('simple rendering with a filter', function (assert) { + QUnit.test('simple rendering with a filter', async function (assert) { assert.expect(2); - var filterMenu = createFilterMenu(this.filters, this.fields); + var filterMenu = await createFilterMenu(this.filters, this.fields); assert.containsN(filterMenu, '.dropdown-divider, .dropdown-item, .o_add_custom_filter', 4, 'should have 4 elements: a hidden separator, a filter, a separator, a add custom filter item'); assert.containsOnce(filterMenu, '.o_add_custom_filter'); filterMenu.destroy(); }); - QUnit.test('click on add custom filter opens the submenu', function (assert) { + QUnit.test('click on add custom filter opens the submenu', async function (assert) { assert.expect(3); - var filterMenu = createFilterMenu([], this.fields); + var filterMenu = await createFilterMenu([], this.fields); // open menu dropdown - testUtils.dom.click(filterMenu.$('span.fa-filter')); + await testUtils.dom.click(filterMenu.$('span.fa-filter')); // open add custom filter submenu - testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); + await testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); assert.containsNone(filterMenu, '.dropdown-divider'); assert.isVisible(filterMenu.$('.o_add_filter_menu')); assert.containsN(filterMenu, '.dropdown-item, .dropdown-item-text', 3, @@ -71,16 +72,16 @@ QUnit.module('FilterMenu', { filterMenu.destroy(); }); - QUnit.test('removing last prop disable the apply button', function (assert) { + QUnit.test('removing last prop disable the apply button', async function (assert) { assert.expect(2); - var filterMenu = createFilterMenu([], this.fields); + var filterMenu = await createFilterMenu([], this.fields); // open menu dropdown and custom filter submenu - testUtils.dom.click(filterMenu.$('span.fa-filter')); - testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); + await testUtils.dom.click(filterMenu.$('span.fa-filter')); + await testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); // remove the current unique proposition - testUtils.dom.click(filterMenu.$('.o_searchview_extended_delete_prop')); + await testUtils.dom.click(filterMenu.$('.o_searchview_extended_delete_prop')); assert.containsNone(filterMenu, '.dropdown-divider'); assert.containsN(filterMenu, '.dropdown-item, .dropdown-item-text', 2, @@ -89,26 +90,26 @@ QUnit.module('FilterMenu', { filterMenu.destroy(); }); - QUnit.test('readding a proposition reenable apply button', function (assert) { + QUnit.test('readding a proposition reenable apply button', async function (assert) { assert.expect(1); - var filterMenu = createFilterMenu([], this.fields); + var filterMenu = await createFilterMenu([], this.fields); // open menu dropdown and custom filter submenu, remove existing prop - testUtils.dom.click(filterMenu.$('span.fa-filter')); - testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); - testUtils.dom.click(filterMenu.$('.o_searchview_extended_delete_prop')); + await testUtils.dom.click(filterMenu.$('span.fa-filter')); + await testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); + await testUtils.dom.click(filterMenu.$('.o_searchview_extended_delete_prop')); // read a proposition - testUtils.dom.click(filterMenu.$('.o_add_condition')); + await testUtils.dom.click(filterMenu.$('.o_add_condition')); assert.ok(!filterMenu.$('.o_apply_filter').attr('disabled')); filterMenu.destroy(); }); - QUnit.test('adding a simple filter works', function (assert) { + QUnit.test('adding a simple filter works', async function (assert) { assert.expect(6); delete this.fields.date_field; - var filterMenu = createFilterMenu([], this.fields, { + var filterMenu = await createFilterMenu([], this.fields, { intercepts: { new_filters: function (ev) { var filter = ev.data.filters[0]; @@ -125,17 +126,17 @@ QUnit.module('FilterMenu', { }, }); // open menu dropdown and custom filter submenu, remove existing prop - testUtils.dom.click(filterMenu.$('span.fa-filter')); - testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); + await testUtils.dom.click(filterMenu.$('span.fa-filter')); + await testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); // click on apply to activate filter - testUtils.dom.click(filterMenu.$('.o_apply_filter')); + await testUtils.dom.click(filterMenu.$('.o_apply_filter')); assert.containsNone(filterMenu, '.o_filter_condition'); assert.containsN(filterMenu, '.dropdown-divider', 2); assert.isNotVisible(filterMenu.$('.dropdown-divider').eq(0)); filterMenu.destroy(); }); - QUnit.test('commit search with an extended proposition with field char does not cause a crash', function (assert) { + QUnit.test('commit search with an extended proposition with field char does not cause a crash', async function (assert) { assert.expect(6); this.fields = {many2one_field: {string: "Trululu", type: "many2one", relation: 'partner', selectable: true, searchable: true}}; @@ -149,7 +150,7 @@ QUnit.module('FilterMenu', { [['many2one_field','ilike', `\\`]], ]; - var filterMenu = createFilterMenu([], this.fields, { + var filterMenu = await createFilterMenu([], this.fields, { intercepts: { new_filters: function (ev) { var filter = ev.data.filters[0]; @@ -162,42 +163,42 @@ QUnit.module('FilterMenu', { }); // open menu dropdown and custom filter submenu, select trululu field and enter string `a`, then click apply - testUtils.dom.click(filterMenu.$('span.fa-filter')); - testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); - testUtils.fields.editSelect(filterMenu.$('.o_filter_condition select.o_input.o_searchview_extended_prop_field'), 'many2one_field'); - testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), `a`); - testUtils.dom.click(filterMenu.$('.o_apply_filter')); + await testUtils.dom.click(filterMenu.$('span.fa-filter')); + await testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); + await testUtils.fields.editSelect(filterMenu.$('.o_filter_condition select.o_input.o_searchview_extended_prop_field'), 'many2one_field'); + await testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), `a`); + await testUtils.dom.click(filterMenu.$('.o_apply_filter')); // open custom filter submenu, select trululu field and enter string `"a"`, then click apply - testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); - testUtils.fields.editSelect(filterMenu.$('.o_filter_condition select.o_input.o_searchview_extended_prop_field'), 'many2one_field'); - testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), `"a"`); - testUtils.dom.click(filterMenu.$('.o_apply_filter')); + await testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); + await testUtils.fields.editSelect(filterMenu.$('.o_filter_condition select.o_input.o_searchview_extended_prop_field'), 'many2one_field'); + await testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), `"a"`); + await testUtils.dom.click(filterMenu.$('.o_apply_filter')); // open custom filter submenu, select trululu field and enter string `'a'`, then click apply - testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); - testUtils.fields.editSelect(filterMenu.$('.o_filter_condition select.o_input.o_searchview_extended_prop_field'), 'many2one_field'); - testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), `'a'`); - testUtils.dom.click(filterMenu.$('.o_apply_filter')); + await testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); + await testUtils.fields.editSelect(filterMenu.$('.o_filter_condition select.o_input.o_searchview_extended_prop_field'), 'many2one_field'); + await testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), `'a'`); + await testUtils.dom.click(filterMenu.$('.o_apply_filter')); // open custom filter submenu, select trululu field and enter string `'`, then click apply - testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); - testUtils.fields.editSelect(filterMenu.$('.o_filter_condition select.o_input.o_searchview_extended_prop_field'), 'many2one_field'); - testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), `'`); - testUtils.dom.click(filterMenu.$('.o_apply_filter')); + await testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); + await testUtils.fields.editSelect(filterMenu.$('.o_filter_condition select.o_input.o_searchview_extended_prop_field'), 'many2one_field'); + await testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), `'`); + await testUtils.dom.click(filterMenu.$('.o_apply_filter')); // open custom filter submenu, select trululu field and enter string `"`, then click apply - testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); - testUtils.fields.editSelect(filterMenu.$('.o_filter_condition select.o_input.o_searchview_extended_prop_field'), 'many2one_field'); - testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), `"`); - testUtils.dom.click(filterMenu.$('.o_apply_filter')); + await testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); + await testUtils.fields.editSelect(filterMenu.$('.o_filter_condition select.o_input.o_searchview_extended_prop_field'), 'many2one_field'); + await testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), `"`); + await testUtils.dom.click(filterMenu.$('.o_apply_filter')); // open custom filter submenu, select trululu field and enter string `\`, then click apply - testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); - testUtils.fields.editSelect(filterMenu.$('.o_filter_condition select.o_input.o_searchview_extended_prop_field'), 'many2one_field'); - testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), `\\`); - testUtils.dom.click(filterMenu.$('.o_apply_filter')); + await testUtils.dom.click(filterMenu.$('.o_add_custom_filter')); + await testUtils.fields.editSelect(filterMenu.$('.o_filter_condition select.o_input.o_searchview_extended_prop_field'), 'many2one_field'); + await testUtils.fields.editInput(filterMenu.$('.o_filter_condition .o_searchview_extended_prop_value input'), `\\`); + await testUtils.dom.click(filterMenu.$('.o_apply_filter')); filterMenu.destroy(); }); diff --git a/addons/web/static/tests/views/search_groupby_menu_tests.js b/addons/web/static/tests/views/search_groupby_menu_tests.js index efeb74103d5..b25eae37e8f 100644 --- a/addons/web/static/tests/views/search_groupby_menu_tests.js +++ b/addons/web/static/tests/views/search_groupby_menu_tests.js @@ -11,8 +11,9 @@ function createGroupByMenu(groupBys, fields, params) { var target = params.debug ? document.body : $('#qunit-fixture'); var menu = new GroupByMenu(null, groupBys, fields); testUtils.mock.addMockEnvironment(menu, params); - menu.appendTo(target); - return menu; + return menu.appendTo(target).then(function() { + return menu; + }); } QUnit.module('GroupByMenu', { @@ -30,10 +31,10 @@ QUnit.module('GroupByMenu', { }, }, function () { - QUnit.test('simple rendering with no filter and no field', function (assert) { + QUnit.test('simple rendering with no filter and no field', async function (assert) { assert.expect(1); - var groupByMenu = createGroupByMenu([], {}); + var groupByMenu = await createGroupByMenu([], {}); // open groupBy menu testUtils.dom.click(groupByMenu.$('button:first')); assert.containsNone(groupByMenu, '.dropdown-divider, .dropdown-item, .dropdown-item-text', @@ -42,13 +43,12 @@ QUnit.module('GroupByMenu', { groupByMenu.destroy(); }); - QUnit.test('simple rendering', function (assert) { + QUnit.test('simple rendering', async function (assert) { assert.expect(2); - var groupByMenu = createGroupByMenu(this.groupBys, this.fields); + var groupByMenu = await createGroupByMenu(this.groupBys, this.fields); // open groupBy menu - testUtils.dom.click(groupByMenu.$('button:first')); - groupByMenu.$('button:first').click(); + await testUtils.dom.click(groupByMenu.$('button:first')); assert.containsN(groupByMenu, '.dropdown-divider, .dropdown-item', 4); assert.strictEqual(groupByMenu.$('.o_menu_item').text().trim(), 'some group by', 'should have proper filter name'); @@ -56,39 +56,40 @@ QUnit.module('GroupByMenu', { groupByMenu.destroy(); }); - QUnit.test('simple rendering with no filter but fields', function (assert) { + QUnit.test('simple rendering with no filter but fields', async function (assert) { assert.expect(1); - var groupByMenu = createGroupByMenu( + var groupByMenu = await createGroupByMenu( [], {fieldName: {sortable: true, string: 'Super Date', type: 'date'}} ); - testUtils.dom.click(groupByMenu.$('button:first')); + await testUtils.nextTick(); + await testUtils.dom.click(groupByMenu.$('button:first')); assert.containsOnce(groupByMenu, '.dropdown-divider, .dropdown-item, .dropdown-item-text', 'should have 1 element'); groupByMenu.destroy(); }); - QUnit.test('click on add custom group toggle group selector', function (assert) { + QUnit.test('click on add custom group toggle group selector', async function (assert) { assert.expect(2); - var groupByMenu = createGroupByMenu([], + var groupByMenu = await createGroupByMenu([], {fieldName: {sortable: true, string: 'Super Date', type: 'date'}} ); - testUtils.dom.click(groupByMenu.$('button:first')); + await testUtils.dom.click(groupByMenu.$('button:first')); var selector = groupByMenu.$('select.o_group_selector'); assert.ok(!selector.is(":visible"), 'should be invisible'); - testUtils.dom.click(groupByMenu.$('.o_add_custom_group')); + await testUtils.dom.click(groupByMenu.$('.o_add_custom_group')); selector = groupByMenu.$('select.o_group_selector'); assert.ok(selector.is(":visible"), 'should be visible'); groupByMenu.destroy(); }); - QUnit.test('select a groupBy of no date type in Add Custom Group menu add properly that groupBy to menu', function (assert) { + QUnit.test('select a groupBy of no date type in Add Custom Group menu add properly that groupBy to menu', async function (assert) { assert.expect(6); - var groupByMenu = createGroupByMenu( + var groupByMenu = await createGroupByMenu( [], { fieldName: {sortable: true, name: 'candlelight', string: 'Candlelight', type: 'boolean'}, @@ -109,23 +110,24 @@ QUnit.module('GroupByMenu', { }, } ); - testUtils.dom.click(groupByMenu.$('button:first')); - testUtils.dom.click(groupByMenu.$('.o_add_custom_group')); + await testUtils.nextTick(); + await testUtils.dom.click(groupByMenu.$('button:first')); + await testUtils.dom.click(groupByMenu.$('.o_add_custom_group')); assert.strictEqual(groupByMenu.$('select').val(), 'fieldName', 'the select value should be "fieldName"'); - testUtils.dom.click(groupByMenu.$('button.o_apply_group')); + await testUtils.dom.click(groupByMenu.$('button.o_apply_group')); assert.containsOnce(groupByMenu, '.o_menu_item > .dropdown-item.selected', 'there should be a groupby selected'); groupByMenu.destroy(); }); - QUnit.test('select a groupBy of date type in Add Custom Group menu add properly that groupBy to menu', function (assert) { + QUnit.test('select a groupBy of date type in Add Custom Group menu add properly that groupBy to menu', async function (assert) { assert.expect(13); INTERVAL_OPTIONS = INTERVAL_OPTIONS.map(function (option) { return _.extend(option, {description: option.description.toString()}); }); - var groupByMenu = createGroupByMenu( + var groupByMenu = await createGroupByMenu( [], this.fields, { @@ -153,20 +155,20 @@ QUnit.module('GroupByMenu', { } ); // open groupBy menu - testUtils.dom.click(groupByMenu.$('button:first')); + await testUtils.dom.click(groupByMenu.$('button:first')); // open Add Custom Group submenu - testUtils.dom.click(groupByMenu.$('.o_add_custom_group')); + await testUtils.dom.click(groupByMenu.$('.o_add_custom_group')); // select fieldName assert.strictEqual(groupByMenu.$('select').val(), 'fieldName', 'the select value should be "fieldName"'); // create new groupBy of type date - groupByMenu.$('button.o_apply_group').click(); + await testUtils.dom.click(groupByMenu.$('button.o_apply_group')); assert.strictEqual(groupByMenu.$('.o_menu_item > .dropdown-item.selected').length, 1, 'there should be a groupby selected'); assert.strictEqual(groupByMenu.$('.o_menu_item .o_submenu_switcher').length, 1, 'there should be options available'); // open options submenu - groupByMenu.$('.o_menu_item .o_submenu_switcher').click(); + await testUtils.dom.click(groupByMenu.$('.o_menu_item .o_submenu_switcher')); assert.strictEqual(groupByMenu.$('.o_item_option').length, 5, 'there should be five options available'); assert.strictEqual(groupByMenu.$('.o_add_custom_group').length, 0, diff --git a/addons/web/static/tests/views/search_panel_tests.js b/addons/web/static/tests/views/search_panel_tests.js index 9814a887b39..f564ccc0d6b 100644 --- a/addons/web/static/tests/views/search_panel_tests.js +++ b/addons/web/static/tests/views/search_panel_tests.js @@ -59,10 +59,10 @@ QUnit.module('Views', { QUnit.module('SearchPanel in Kanban views'); - QUnit.test('basic rendering', function (assert) { + QUnit.test('basic rendering', async function (assert) { assert.expect(17); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -115,10 +115,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('sections with custom icon and color', function (assert) { + QUnit.test('sections with custom icon and color', async function (assert) { assert.expect(4); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -144,12 +144,12 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('sections with attr invisible="1" are ignored', function (assert) { + QUnit.test('sections with attr invisible="1" are ignored', async function (assert) { // 'groups' attributes are converted server-side into invisible="1" when the user doesn't // belong to the given group assert.expect(4); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -181,10 +181,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('categories and filters order is kept', function (assert) { + QUnit.test('categories and filters order is kept', async function (assert) { assert.expect(4); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -214,10 +214,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('specify active category value in context', function (assert) { + QUnit.test('specify active category value in context', async function (assert) { assert.expect(1); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -248,10 +248,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('use category (on many2one) to refine search', function (assert) { + QUnit.test('use category (on many2one) to refine search', async function (assert) { assert.expect(14); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -276,21 +276,21 @@ QUnit.module('Views', { }); // select 'asustek' - testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(1) header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(1) header')); assert.containsOnce(kanban, '.o_search_panel_category_value .active'); assert.containsOnce(kanban, '.o_search_panel_category_value:nth(1) .active'); assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 2); // select 'agrolait' - testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(2) header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(2) header')); assert.containsOnce(kanban, '.o_search_panel_category_value .active'); assert.containsOnce(kanban, '.o_search_panel_category_value:nth(2) .active'); assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 1); // select 'All' - testUtils.dom.click(kanban.$('.o_search_panel_category_value:first header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:first header')); assert.containsOnce(kanban, '.o_search_panel_category_value .active'); assert.containsOnce(kanban, '.o_search_panel_category_value:first .active'); @@ -306,10 +306,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('use category (on selection) to refine search', function (assert) { + QUnit.test('use category (on selection) to refine search', async function (assert) { assert.expect(14); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -333,21 +333,21 @@ QUnit.module('Views', { }); // select 'abc' - testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(1) header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(1) header')); assert.containsOnce(kanban, '.o_search_panel_category_value .active'); assert.containsOnce(kanban, '.o_search_panel_category_value:nth(1) .active'); assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 1); // select 'ghi' - testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(3) header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(3) header')); assert.containsOnce(kanban, '.o_search_panel_category_value .active'); assert.containsOnce(kanban, '.o_search_panel_category_value:nth(3) .active'); assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 2); // select 'All' again - testUtils.dom.click(kanban.$('.o_search_panel_category_value:first header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:first header')); assert.containsOnce(kanban, '.o_search_panel_category_value .active'); assert.containsOnce(kanban, '.o_search_panel_category_value:first .active'); @@ -363,7 +363,7 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('store and retrieve active category value', function (assert) { + QUnit.test('store and retrieve active category value', async function (assert) { assert.expect(8); var Storage = RamStorage.extend({ @@ -380,7 +380,7 @@ QUnit.module('Views', { }); var expectedActiveId = 3; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -412,7 +412,7 @@ QUnit.module('Views', { // select 'agrolait' expectedActiveId = 5; - testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(2) header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(2) header')); assert.verifySteps([ 'getItem searchpanel_partner_company_id', @@ -422,8 +422,8 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('retrieved category value does not exist', function (assert) { - assert.expect(5); + QUnit.test('retrieved category value does not exist', async function (assert) { + assert.expect(6); var Storage = RamStorage.extend({ getItem: function (key) { @@ -435,7 +435,7 @@ QUnit.module('Views', { storage: new Storage(), }); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -465,13 +465,15 @@ QUnit.module('Views', { assert.containsOnce(kanban, '.o_search_panel_category_value:first .active'); assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 4); + assert.verifySteps(['getItem searchpanel_partner_company_id']); + kanban.destroy(); }); - QUnit.test('use two categories to refine search', function (assert) { + QUnit.test('use two categories to refine search', async function (assert) { assert.expect(14); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -500,23 +502,23 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 3); // select 'asustek' - testUtils.dom.click(kanban.$('.o_search_panel_category_value[data-id=3] header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value[data-id=3] header')); assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 2); // select 'abc' - testUtils.dom.click(kanban.$('.o_search_panel_category_value[data-id=abc] header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value[data-id=abc] header')); assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 1); // select 'ghi' - testUtils.dom.click(kanban.$('.o_search_panel_category_value[data-id=ghi] header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value[data-id=ghi] header')); assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 1); // select 'All' in first category (company_id) - testUtils.dom.click(kanban.$('.o_search_panel_section:first .o_search_panel_category_value:first header')); + await testUtils.dom.click(kanban.$('.o_search_panel_section:first .o_search_panel_category_value:first header')); assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 1); // select 'All' in second category (state) - testUtils.dom.click(kanban.$('.o_search_panel_section:nth(1) .o_search_panel_category_value:first header')); + await testUtils.dom.click(kanban.$('.o_search_panel_section:nth(1) .o_search_panel_category_value:first header')); assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 3); assert.verifySteps([ @@ -531,14 +533,14 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('category with parent_field', function (assert) { + QUnit.test('category with parent_field', async function (assert) { assert.expect(24); this.data.company.records.push({id: 40, name: 'child company 1', parent_id: 5}); this.data.company.records.push({id: 41, name: 'child company 2', parent_id: 5}); this.data.partner.records[1].company_id = 40; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -569,7 +571,7 @@ QUnit.module('Views', { assert.containsOnce(kanban, '.o_search_panel_category_value .o_toggle_fold'); // unfold parent category - testUtils.dom.click(kanban.$('.o_search_panel_category_value .o_toggle_fold')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value .o_toggle_fold')); assert.containsOnce(kanban, '.o_search_panel_category_value .active'); assert.containsOnce(kanban, '.o_search_panel_category_value:first .active'); @@ -578,21 +580,21 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_search_panel_category_value .o_search_panel_category_value', 2); // click on first child company - testUtils.dom.click(kanban.$('.o_search_panel_category_value .o_search_panel_category_value:first header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value .o_search_panel_category_value:first header')); assert.containsOnce(kanban, '.o_search_panel_category_value .active'); assert.containsOnce(kanban, '.o_search_panel_category_value .o_search_panel_category_value:first .active'); assert.containsOnce(kanban, '.o_kanban_record:not(.o_kanban_ghost)'); // click on parent company - testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(2) > header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(2) > header')); assert.containsOnce(kanban, '.o_search_panel_category_value .active'); assert.containsOnce(kanban, '.o_search_panel_category_value:nth(2) .active'); assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 1); // fold category with children - testUtils.dom.click(kanban.$('.o_search_panel_category_value .o_toggle_fold')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value .o_toggle_fold')); assert.containsOnce(kanban, '.o_search_panel_category_value .active'); assert.containsOnce(kanban, '.o_search_panel_category_value:nth(2) .active'); @@ -608,14 +610,14 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('can (un)fold parent category values', function (assert) { + QUnit.test('can (un)fold parent category values', async function (assert) { assert.expect(7); this.data.company.records.push({id: 40, name: 'child company 1', parent_id: 5}); this.data.company.records.push({id: 41, name: 'child company 2', parent_id: 5}); this.data.partner.records[1].company_id = 40; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -639,13 +641,13 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_search_panel_category_value', 3); // unfold agrolait - testUtils.dom.click(kanban.$('.o_search_panel_category_value:contains(agrolait) .o_toggle_fold')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:contains(agrolait) .o_toggle_fold')); assert.hasClass(kanban.$('.o_search_panel_category_value:contains(agrolait) .o_toggle_fold'), 'fa-caret-down', "'agrolait' should be open"); assert.containsN(kanban, '.o_search_panel_category_value', 5); // fold agrolait - testUtils.dom.click(kanban.$('.o_search_panel_category_value:contains(agrolait) .o_toggle_fold')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:contains(agrolait) .o_toggle_fold')); assert.hasClass(kanban.$('.o_search_panel_category_value:contains(agrolait) .o_toggle_fold'), 'fa-caret-left', "'agrolait' should be folded"); assert.containsN(kanban, '.o_search_panel_category_value', 3); @@ -653,14 +655,14 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('fold status is kept at reload', function (assert) { + QUnit.test('fold status is kept at reload', async function (assert) { assert.expect(4); this.data.company.records.push({id: 40, name: 'child company 1', parent_id: 5}); this.data.company.records.push({id: 41, name: 'child company 2', parent_id: 5}); this.data.partner.records[1].company_id = 40; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -678,12 +680,12 @@ QUnit.module('Views', { }); // unfold agrolait - testUtils.dom.click(kanban.$('.o_search_panel_category_value:contains(agrolait) .o_toggle_fold')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:contains(agrolait) .o_toggle_fold')); assert.hasClass(kanban.$('.o_search_panel_category_value:contains(agrolait) .o_toggle_fold'), 'fa-caret-down', "'agrolait' should be open"); assert.containsN(kanban, '.o_search_panel_category_value', 5); - kanban.reload({}); + await kanban.reload({}); assert.hasClass(kanban.$('.o_search_panel_category_value:contains(agrolait) .o_toggle_fold'), 'fa-caret-down', "'agrolait' should be open"); @@ -692,11 +694,11 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('concurrency: delayed search_reads', function (assert) { + QUnit.test('concurrency: delayed search_reads', async function (assert) { assert.expect(19); var def; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -704,7 +706,7 @@ QUnit.module('Views', { var result = this._super.apply(this, arguments); if (route === '/web/dataset/search_read') { assert.step(JSON.stringify(args.domain)); - return $.when(def).then(_.constant(result)); + return Promise.resolve(def).then(_.constant(result)); } return result; }, @@ -728,9 +730,9 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 3); // select 'asustek' (delay the reload) - def = $.Deferred(); + def = testUtils.makeTestPromise(); var asustekDef = def; - testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(1) header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(1) header')); // 'asustek' should be selected, but there should still be 3 records assert.containsOnce(kanban, '.o_search_panel_category_value .active'); @@ -738,9 +740,9 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 3); // select 'agrolait' (delay the reload) - def = $.Deferred(); + def = testUtils.makeTestPromise(); var agrolaitDef = def; - testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(2) header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(2) header')); // 'agrolait' should be selected, but there should still be 3 records assert.containsOnce(kanban, '.o_search_panel_category_value .active'); @@ -749,12 +751,14 @@ QUnit.module('Views', { // unlock asustek search (should be ignored, so there should still be 3 records) asustekDef.resolve(); + await testUtils.nextTick(); assert.containsOnce(kanban, '.o_search_panel_category_value .active'); assert.containsOnce(kanban, '.o_search_panel_category_value:nth(2) .active'); assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 3); // unlock agrolait search, there should now be 1 record agrolaitDef.resolve(); + await testUtils.nextTick(); assert.containsOnce(kanban, '.o_search_panel_category_value .active'); assert.containsOnce(kanban, '.o_search_panel_category_value:nth(2) .active'); assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 1); @@ -768,18 +772,18 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('concurrency: misordered get_filters', function (assert) { + QUnit.test('concurrency: misordered get_filters', async function (assert) { assert.expect(15); var def; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, mockRPC: function (route, args) { var result = this._super.apply(this, arguments); if (args.method === 'search_panel_select_multi_range') { - return $.when(def).then(_.constant(result)); + return Promise.resolve(def).then(_.constant(result)); } return result; }, @@ -803,9 +807,9 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 4); // select 'abc' (delay the reload) - def = $.Deferred(); + def = testUtils.makeTestPromise(); var abcDef = def; - testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(1) header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(1) header')); // 'All' should still be selected, and there should still be 4 records assert.containsOnce(kanban, '.o_search_panel_category_value .active'); @@ -813,9 +817,9 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 4); // select 'ghi' (delay the reload) - def = $.Deferred(); + def = testUtils.makeTestPromise(); var ghiDef = def; - testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(3) header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(3) header')); // 'All' should still be selected, and there should still be 4 records assert.containsOnce(kanban, '.o_search_panel_category_value .active'); @@ -824,12 +828,14 @@ QUnit.module('Views', { // unlock ghi search ghiDef.resolve(); + await testUtils.nextTick(); assert.containsOnce(kanban, '.o_search_panel_category_value .active'); assert.containsOnce(kanban, '.o_search_panel_category_value:nth(3) .active'); assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 2); // unlock abc search (should be ignored) abcDef.resolve(); + await testUtils.nextTick(); assert.containsOnce(kanban, '.o_search_panel_category_value .active'); assert.containsOnce(kanban, '.o_search_panel_category_value:nth(3) .active'); assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 2); @@ -837,18 +843,18 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('concurrency: delayed get_filter', function (assert) { + QUnit.test('concurrency: delayed get_filter', async function (assert) { assert.expect(3); var def; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, mockRPC: function (route, args) { var result = this._super.apply(this, arguments); if (args.method === 'search_panel_select_multi_range') { - return $.when(def).then(_.constant(result)); + return Promise.resolve(def).then(_.constant(result)); } return result; }, @@ -868,22 +874,24 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)', 4); // trigger a reload and delay the get_filter - def = $.Deferred(); + def = testUtils.makeTestPromise(); kanban.reload({domain: [['id', '=', 1]]}); + await testUtils.nextTick(); assert.containsN(kanban, '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)', 4); def.resolve(); + await testUtils.nextTick(); assert.containsOnce(kanban, '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)'); kanban.destroy(); }); - QUnit.test('use filter (on many2one) to refine search', function (assert) { + QUnit.test('use filter (on many2one) to refine search', async function (assert) { assert.expect(32); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -927,7 +935,7 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)', 3); // check 'asustek' - testUtils.dom.click(kanban.$('.o_search_panel_filter_value:first input')); + await testUtils.dom.click(kanban.$('.o_search_panel_filter_value:first input')); assert.containsOnce(kanban, '.o_search_panel_filter_value input:checked'); assert.strictEqual(kanban.$('.o_search_panel_filter_value').text().replace(/\s/g, ''), @@ -935,7 +943,7 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)', 2); // check 'agrolait' - testUtils.dom.click(kanban.$('.o_search_panel_filter_value:nth(1) input')); + await testUtils.dom.click(kanban.$('.o_search_panel_filter_value:nth(1) input')); assert.containsN(kanban, '.o_search_panel_filter_value input:checked', 2); assert.strictEqual(kanban.$('.o_search_panel_filter_value').text().replace(/\s/g, ''), @@ -943,7 +951,7 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)', 3); // uncheck 'asustek' - testUtils.dom.click(kanban.$('.o_search_panel_filter_value:first input')); + await testUtils.dom.click(kanban.$('.o_search_panel_filter_value:first input')); assert.containsOnce(kanban, '.o_search_panel_filter_value input:checked'); assert.strictEqual(kanban.$('.o_search_panel_filter_value').text().replace(/\s/g, ''), @@ -951,7 +959,7 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)', 1); // uncheck 'agrolait' - testUtils.dom.click(kanban.$('.o_search_panel_filter_value:nth(1) input')); + await testUtils.dom.click(kanban.$('.o_search_panel_filter_value:nth(1) input')); assert.containsNone(kanban, '.o_search_panel_filter_value input:checked'); assert.strictEqual(kanban.$('.o_search_panel_filter_value').text().replace(/\s/g, ''), @@ -963,26 +971,26 @@ QUnit.module('Views', { '[]', '[["bar","=",true]]', // 'asustek' checked - '[["bar","=",true],["company_id","in",[3]]]', '[["company_id","in",[3]]]', + '[["bar","=",true],["company_id","in",[3]]]', // 'asustek' and 'agrolait' checked - '[["bar","=",true],["company_id","in",[3,5]]]', '[["company_id","in",[3,5]]]', + '[["bar","=",true],["company_id","in",[3,5]]]', // 'agrolait' checked - '[["bar","=",true],["company_id","in",[5]]]', '[["company_id","in",[5]]]', + '[["bar","=",true],["company_id","in",[5]]]', // nothing checked - '[["bar","=",true]]', '[]', + '[["bar","=",true]]', ]); kanban.destroy(); }); - QUnit.test('use filter (on selection) to refine search', function (assert) { + QUnit.test('use filter (on selection) to refine search', async function (assert) { assert.expect(32); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1026,7 +1034,7 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)', 3); // check 'abc' - testUtils.dom.click(kanban.$('.o_search_panel_filter_value:first input')); + await testUtils.dom.click(kanban.$('.o_search_panel_filter_value:first input')); assert.containsOnce(kanban, '.o_search_panel_filter_value input:checked'); assert.strictEqual(kanban.$('.o_search_panel_filter_value').text().replace(/\s/g, ''), @@ -1034,7 +1042,7 @@ QUnit.module('Views', { assert.containsOnce(kanban, '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)', 1); // check 'def' - testUtils.dom.click(kanban.$('.o_search_panel_filter_value:nth(1) input')); + await testUtils.dom.click(kanban.$('.o_search_panel_filter_value:nth(1) input')); assert.containsN(kanban, '.o_search_panel_filter_value input:checked', 2); assert.strictEqual(kanban.$('.o_search_panel_filter_value').text().replace(/\s/g, ''), @@ -1042,7 +1050,7 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)', 2); // uncheck 'abc' - testUtils.dom.click(kanban.$('.o_search_panel_filter_value:first input')); + await testUtils.dom.click(kanban.$('.o_search_panel_filter_value:first input')); assert.containsOnce(kanban, '.o_search_panel_filter_value input:checked'); assert.strictEqual(kanban.$('.o_search_panel_filter_value').text().replace(/\s/g, ''), @@ -1050,7 +1058,7 @@ QUnit.module('Views', { assert.containsOnce(kanban, '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)'); // uncheck 'def' - testUtils.dom.click(kanban.$('.o_search_panel_filter_value:nth(1) input')); + await testUtils.dom.click(kanban.$('.o_search_panel_filter_value:nth(1) input')); assert.containsNone(kanban, '.o_search_panel_filter_value input:checked'); assert.strictEqual(kanban.$('.o_search_panel_filter_value').text().replace(/\s/g, ''), @@ -1062,26 +1070,26 @@ QUnit.module('Views', { '[]', '[["bar","=",true]]', // 'asustek' checked - '[["bar","=",true],["state","in",["abc"]]]', '[["state","in",["abc"]]]', + '[["bar","=",true],["state","in",["abc"]]]', // 'asustek' and 'agrolait' checked - '[["bar","=",true],["state","in",["abc","def"]]]', '[["state","in",["abc","def"]]]', + '[["bar","=",true],["state","in",["abc","def"]]]', // 'agrolait' checked - '[["bar","=",true],["state","in",["def"]]]', '[["state","in",["def"]]]', + '[["bar","=",true],["state","in",["def"]]]', // nothing checked - '[["bar","=",true]]', '[]', + '[["bar","=",true]]', ]); kanban.destroy(); }); - QUnit.test('only reload filters when domains change', function (assert) { + QUnit.test('only reload filters when domains change', async function (assert) { assert.expect(11); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1112,47 +1120,37 @@ QUnit.module('Views', { ]); // go to page 2 (the domain doesn't change, so the filters should not be reloaded) - testUtils.dom.click(kanban.$('.o_pager_next')); + await testUtils.dom.click(kanban.$('.o_pager_next')); assert.verifySteps([ - 'search_panel_select_multi_range', - '/web/dataset/search_read', '/web/dataset/search_read', ]); // reload with another domain, so the filters should be reloaded - kanban.reload({domain: [['id', '<', 5]]}); + await kanban.reload({domain: [['id', '<', 5]]}); assert.verifySteps([ 'search_panel_select_multi_range', '/web/dataset/search_read', - '/web/dataset/search_read', - '/web/dataset/search_read', - 'search_panel_select_multi_range', ]); // change category value, so the filters should be reloaded - testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(1) header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(1) header')); assert.verifySteps([ 'search_panel_select_multi_range', '/web/dataset/search_read', - '/web/dataset/search_read', - '/web/dataset/search_read', - 'search_panel_select_multi_range', - '/web/dataset/search_read', - 'search_panel_select_multi_range', ]); kanban.destroy(); }); - QUnit.test('filter with groupby', function (assert) { + QUnit.test('filter with groupby', async function (assert) { assert.expect(42); this.data.company.records.push({id: 11, name: 'camptocamp', category_id: 7}); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1200,7 +1198,7 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)', 3); // check 'asustek' - testUtils.dom.click(kanban.$('.o_search_panel_filter_value:first input')); + await testUtils.dom.click(kanban.$('.o_search_panel_filter_value:first input')); assert.containsOnce(kanban, '.o_search_panel_filter_value input:checked'); var firstGroupCheckbox = kanban.$('.o_search_panel_filter_group:first > div > input').get(0); @@ -1211,7 +1209,7 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)', 2); // check 'agrolait' - testUtils.dom.click(kanban.$('.o_search_panel_filter_value:nth(1) input')); + await testUtils.dom.click(kanban.$('.o_search_panel_filter_value:nth(1) input')); assert.containsN(kanban, '.o_search_panel_filter_value input:checked', 2); var secondGroupCheckbox = kanban.$('.o_search_panel_filter_group:nth(1) > div > input').get(0); @@ -1224,7 +1222,7 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)', 0); // check 'camptocamp' - testUtils.dom.click(kanban.$('.o_search_panel_filter_value:nth(2) input')); + await testUtils.dom.click(kanban.$('.o_search_panel_filter_value:nth(2) input')); assert.containsN(kanban, '.o_search_panel_filter_value input:checked', 3); secondGroupCheckbox = kanban.$('.o_search_panel_filter_group:nth(1) > div > input').get(0); @@ -1237,7 +1235,7 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)', 0); // uncheck second group - testUtils.dom.click(kanban.$('.o_search_panel_filter_group:nth(1) > div > input')); + await testUtils.dom.click(kanban.$('.o_search_panel_filter_group:nth(1) > div > input')); assert.containsOnce(kanban, '.o_search_panel_filter_value input:checked'); secondGroupCheckbox = kanban.$('.o_search_panel_filter_group:nth(1) > div > input').get(0); @@ -1254,28 +1252,28 @@ QUnit.module('Views', { '[]', '[["bar","=",true]]', // 'asustek' checked - '[["bar","=",true],["company_id","in",[3]]]', '[["company_id","in",[3]]]', + '[["bar","=",true],["company_id","in",[3]]]', // 'asustek' and 'agrolait' checked - '[["bar","=",true],["company_id","in",[3]],["company_id","in",[5]]]', '[["company_id","in",[3]],["company_id","in",[5]]]', + '[["bar","=",true],["company_id","in",[3]],["company_id","in",[5]]]', // 'asustek', 'agrolait' and 'camptocamp' checked - '[["bar","=",true],["company_id","in",[3]],["company_id","in",[5,11]]]', '[["company_id","in",[3]],["company_id","in",[5,11]]]', + '[["bar","=",true],["company_id","in",[3]],["company_id","in",[5,11]]]', // 'asustek' checked - '[["bar","=",true],["company_id","in",[3]]]', '[["company_id","in",[3]]]', + '[["bar","=",true],["company_id","in",[3]]]', ]); kanban.destroy(); }); - QUnit.test('filter with domain', function (assert) { + QUnit.test('filter with domain', async function (assert) { assert.expect(3); this.data.company.records.push({id: 40, name: 'child company 1', parent_id: 3}); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1313,12 +1311,12 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('(un)fold filter group', function (assert) { + QUnit.test('(un)fold filter group', async function (assert) { assert.expect(13); this.data.company.records.push({id: 11, name: 'camptocamp', category_id: 7}); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1340,13 +1338,13 @@ QUnit.module('Views', { assert.containsN(kanban, '.o_search_panel_filter_value', 3); // check 'agrolait' - testUtils.dom.click(kanban.$('.o_search_panel_filter_value:nth(1) input')); + await testUtils.dom.click(kanban.$('.o_search_panel_filter_value:nth(1) input')); var secondGroupCheckbox = kanban.$('.o_search_panel_filter_group:nth(1) > div > input').get(0); assert.strictEqual(secondGroupCheckbox.indeterminate, true); assert.hasAttrValue(kanban.$('.o_search_panel_filter_value:nth(1) input'), 'checked', 'checked'); // fold second group - testUtils.dom.click(kanban.$('.o_search_panel_filter_group:nth(1) .o_toggle_fold')); + await testUtils.dom.click(kanban.$('.o_search_panel_filter_group:nth(1) .o_toggle_fold')); assert.containsN(kanban, '.o_search_panel_filter_group', 2); assert.containsOnce(kanban, '.o_search_panel_filter_value'); @@ -1354,13 +1352,13 @@ QUnit.module('Views', { assert.strictEqual(secondGroupCheckbox.indeterminate, true); // fold first group - testUtils.dom.click(kanban.$('.o_search_panel_filter_group:first .o_toggle_fold')); + await testUtils.dom.click(kanban.$('.o_search_panel_filter_group:first .o_toggle_fold')); assert.containsN(kanban, '.o_search_panel_filter_group', 2); assert.containsNone(kanban, '.o_search_panel_filter_value'); // unfold second group - testUtils.dom.click(kanban.$('.o_search_panel_filter_group:nth(1) .o_toggle_fold')); + await testUtils.dom.click(kanban.$('.o_search_panel_filter_group:nth(1) .o_toggle_fold')); assert.containsN(kanban, '.o_search_panel_filter_group', 2); assert.containsN(kanban, '.o_search_panel_filter_value', 2); @@ -1371,10 +1369,10 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('filter with domain depending on category', function (assert) { + QUnit.test('filter with domain depending on category', async function (assert) { assert.expect(22); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1408,7 +1406,7 @@ QUnit.module('Views', { }); // select 'gold' category - testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(1) header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(1) header')); assert.containsOnce(kanban, '.o_search_panel_category_value .active'); assert.containsOnce(kanban, '.o_search_panel_category_value:nth(1) .active'); @@ -1417,7 +1415,7 @@ QUnit.module('Views', { "asustek1"); // select 'silver' category - testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(2) header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:nth(2) header')); assert.containsOnce(kanban, '.o_search_panel_category_value:nth(2) .active'); assert.containsOnce(kanban, '.o_search_panel_filter_value'); @@ -1425,7 +1423,7 @@ QUnit.module('Views', { "agrolait2"); // select All - testUtils.dom.click(kanban.$('.o_search_panel_category_value:first header')); + await testUtils.dom.click(kanban.$('.o_search_panel_category_value:first header')); assert.containsOnce(kanban, '.o_search_panel_category_value:first .active'); assert.containsNone(kanban, '.o_search_panel_filter_value'); @@ -1444,14 +1442,14 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('specify active filter values in context', function (assert) { + QUnit.test('specify active filter values in context', async function (assert) { assert.expect(4); var expectedDomain = [ ['company_id', 'in', [5]], ['state', 'in', ['abc', 'ghi']], ]; - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1483,17 +1481,17 @@ QUnit.module('Views', { // manually untick a default value expectedDomain = [['state', 'in', ['abc', 'ghi']]]; - testUtils.dom.click(kanban.$('.o_search_panel_filter:first .o_search_panel_filter_value:nth(1) input')); + await testUtils.dom.click(kanban.$('.o_search_panel_filter:first .o_search_panel_filter_value:nth(1) input')); assert.containsN(kanban, '.o_search_panel_filter_value input:checked', 2); kanban.destroy(); }); - QUnit.test('retrieved filter value from context does not exist', function (assert) { + QUnit.test('retrieved filter value from context does not exist', async function (assert) { assert.expect(1); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, @@ -1522,12 +1520,12 @@ QUnit.module('Views', { kanban.destroy(); }); - QUnit.test('filter with groupby and default values in context', function (assert) { + QUnit.test('filter with groupby and default values in context', async function (assert) { assert.expect(2); this.data.company.records.push({id: 11, name: 'camptocamp', category_id: 7}); - var kanban = createView({ + var kanban = await createView({ View: KanbanView, model: 'partner', data: this.data, diff --git a/addons/web/static/tests/views/search_view_tests.js b/addons/web/static/tests/views/search_view_tests.js index 4916834e1bc..e57194ef19c 100644 --- a/addons/web/static/tests/views/search_view_tests.js +++ b/addons/web/static/tests/views/search_view_tests.js @@ -266,34 +266,34 @@ QUnit.module('Search View', { ]; }, }, function () { - QUnit.test('basic rendering', function (assert) { + QUnit.test('basic rendering', async function (assert) { assert.expect(1); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(1); + await actionManager.doAction(1); assert.strictEqual($('.o_searchview input.o_searchview_input')[0], document.activeElement, "searchview input should be focused"); actionManager.destroy(); }); - QUnit.test('navigation with facets', function (assert) { + QUnit.test('navigation with facets', async function (assert) { assert.expect(4); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(1); + await actionManager.doAction(1); // add a facet - testUtils.dom.click(actionManager.$('.o_dropdown_toggler_btn:contains(Group By)')); - testUtils.dom.click(actionManager.$('.o_menu_item a')); + await testUtils.dom.click(actionManager.$('.o_dropdown_toggler_btn:contains(Group By)')); + await testUtils.dom.click(actionManager.$('.o_menu_item a')); assert.strictEqual(actionManager.$('.o_searchview .o_searchview_facet').length, 1, "there should be one facet"); assert.strictEqual(actionManager.$('.o_searchview input.o_searchview_input')[0], document.activeElement, @@ -320,110 +320,111 @@ QUnit.module('Search View', { QUnit.module('GroupByMenu'); - QUnit.test('click on groupby filter adds a facet', function (assert) { + QUnit.test('click on groupby filter adds a facet', async function (assert) { assert.expect(1); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(1); - testUtils.dom.click($('.o_dropdown_toggler_btn:contains(Group By)')); - testUtils.dom.click($('.o_menu_item a')); + + await actionManager.doAction(1); + await testUtils.dom.click($('.o_dropdown_toggler_btn:contains(Group By)')); + await testUtils.dom.click($('.o_menu_item a')); assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').text().trim(), 'candle', 'should have a facet with candle name'); actionManager.destroy(); }); - QUnit.test('remove a "Group By" facet properly unchecks groupbys in groupby menu', function (assert) { + QUnit.test('remove a "Group By" facet properly unchecks groupbys in groupby menu', async function (assert) { assert.expect(2); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(1); - testUtils.dom.click($('.o_dropdown_toggler_btn:contains(Group By)')); - testUtils.dom.click($('.o_menu_item a')); + await actionManager.doAction(1); + await testUtils.dom.click($('.o_dropdown_toggler_btn:contains(Group By)')); + await testUtils.dom.click($('.o_menu_item a')); assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').text().trim(), 'candle', 'should have a facet with candle name'); - testUtils.dom.click($('.o_facet_remove:first')); + await testUtils.dom.click($('.o_facet_remove:first')); assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').length, 0, 'there should be no facet'); actionManager.destroy(); }); - QUnit.test('change option of a "Group By" does not remove groupy in facet "Group By"', function (assert) { + QUnit.test('change option of a "Group By" does not remove groupy in facet "Group By"', async function (assert) { assert.expect(3); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(2); - testUtils.dom.click($('span.fa-bars')); - testUtils.dom.click($('.o_submenu_switcher')); + await actionManager.doAction(2); + await testUtils.dom.click($('span.fa-bars')); + await testUtils.dom.click($('.o_submenu_switcher')); // Don't forget there is a hidden li.divider element at first place among children - testUtils.dom.click($('.o_item_option:nth-child(2)')); + await testUtils.dom.click($('.o_item_option:nth-child(2)')); assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').length, 1, 'should have a facet'); - testUtils.dom.click($('.o_item_option:nth-child(3)')); + await testUtils.dom.click($('.o_item_option:nth-child(3)')); assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').length, 1, 'should have a facet'); - testUtils.dom.click($('.o_item_option:nth-child(3)')); + await testUtils.dom.click($('.o_item_option:nth-child(3)')); assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').length, 0, 'should have no facet'); actionManager.destroy(); }); - QUnit.test('select and unselect quickly groupby does not crash', function (assert) { + QUnit.test('select and unselect quickly groupby does not crash', async function (assert) { assert.expect(1); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(3); - testUtils.dom.click($('span.fa-bars')); - testUtils.dom.click($('.o_menu_item:first')); - testUtils.dom.click($('.o_menu_item:first')); + await actionManager.doAction(3); + await testUtils.dom.click($('span.fa-bars')); + await testUtils.dom.click($('.o_menu_item:first')); + await testUtils.dom.click($('.o_menu_item:first')); assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').length, 0, 'should have a facet'); actionManager.destroy(); }); - QUnit.test('groupby selected within graph subview are not deleted when modifying search view content', function (assert) { + QUnit.test('groupby selected within graph subview are not deleted when modifying search view content', async function (assert) { assert.expect(2); this.actions[3].flags = {isEmbedded: true}; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(4); - testUtils.dom.click($('.o_graph_controller .o_control_panel .o_cp_buttons button').eq(1)); - testUtils.dom.click($('.o_graph_controller .o_group_by_menu .o_menu_item').eq(1)); - testUtils.dom.click($('.o_graph_controller .o_group_by_menu .o_menu_item .o_item_option > .dropdown-item').first()); + await actionManager.doAction(4); + await testUtils.dom.click($('.o_graph_controller .o_control_panel .o_cp_buttons button').eq(1)); + await testUtils.dom.click($('.o_graph_controller .o_group_by_menu .o_menu_item').eq(1)); + await testUtils.dom.click($('.o_graph_controller .o_group_by_menu .o_menu_item .o_item_option > .dropdown-item').first()); assert.doesNotHaveClass($('.o_graph_controller .o_group_by_menu .o_menu_item > .dropdown-item').eq(1), 'selected', 'groupby should be unselected'); - testUtils.dom.click($('.o_search_options button span.fa-filter')); - testUtils.dom.click($('.o_filters_menu .o_menu_item a').first()); + await testUtils.dom.click($('.o_search_options button span.fa-filter')); + await testUtils.dom.click($('.o_filters_menu .o_menu_item a').first()); assert.doesNotHaveClass($('.o_graph_controller .o_group_by_menu .o_menu_item > .dropdown-item').eq(1), 'selected', 'groupby should be still unselected'); actionManager.destroy(); }); - QUnit.test('group by a date field using interval works', function (assert) { + QUnit.test('group by a date field using interval works', async function (assert) { assert.expect(13); @@ -434,7 +435,7 @@ QUnit.module('Search View', { ["birthday:year"], ]; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -446,67 +447,67 @@ QUnit.module('Search View', { }, }); - actionManager.doAction(5); + await actionManager.doAction(5); // open menu 'Group By' - testUtils.dom.click($('.o_search_options .fa-bars')); + await testUtils.dom.click($('.o_search_options .fa-bars')); // Activate the groupby 'Date' - testUtils.dom.click($('.o_group_by_menu .o_menu_item')); + await testUtils.dom.click($('.o_group_by_menu .o_menu_item')); // // select option 'day' - testUtils.dom.click($('.o_group_by_menu .o_menu_item .o_item_option[data-option_id="day"]')); + await testUtils.dom.click($('.o_group_by_menu .o_menu_item .o_item_option[data-option_id="day"]')); assert.strictEqual($('div.o_facet_values span').text().trim(),'Date: Day'); assert.strictEqual($('.o_content tr.o_group_header').length, 5); // // select option 'month' - testUtils.dom.click($('.o_group_by_menu .o_menu_item .o_item_option[data-option_id="month"]')); + await testUtils.dom.click($('.o_group_by_menu .o_menu_item .o_item_option[data-option_id="month"]')); // // data should be grouped by the field 'Date' using the interval 'month' assert.strictEqual($('div.o_facet_values span').text().trim(),'Date: Month'); assert.strictEqual($('.o_content tr.o_group_header').length, 3); // // deactivate option 'month' - testUtils.dom.click($('.o_group_by_menu .o_menu_item .o_item_option[data-option_id="month"]')); + await testUtils.dom.click($('.o_group_by_menu .o_menu_item .o_item_option[data-option_id="month"]')); // // no groupby is applied assert.strictEqual($('div.o_facet_values span').length, 0); // // open 'Add custom Groupby' menu - testUtils.dom.click($('.o_group_by_menu .o_add_custom_group')); + await testUtils.dom.click($('.o_group_by_menu .o_add_custom_group')); // // click on 'Apply' button - testUtils.dom.click($('.o_group_by_menu .o_generator_menu button')); + await testUtils.dom.click($('.o_group_by_menu .o_generator_menu button')); // // data should be grouped by the field 'Birthday' using the interval 'month' assert.strictEqual($('div.o_facet_values span').text().trim(),'Birthday: Month'); assert.strictEqual($('.o_content tr.o_group_header').length, 5); // // open submenu with interval options - testUtils.dom.click($('.o_group_by_menu .o_menu_item .o_submenu_switcher').eq(1)); + await testUtils.dom.click($('.o_group_by_menu .o_menu_item .o_submenu_switcher').eq(1)); // // select option 'year' - testUtils.dom.click($('.o_group_by_menu .o_menu_item .o_item_option').eq(9)); + await testUtils.dom.click($('.o_group_by_menu .o_menu_item .o_item_option').eq(9)); // // data should be grouped by the field 'Birthday' using the interval 'year' assert.strictEqual($('div.o_facet_values span').text().trim(),'Birthday: Year'); assert.strictEqual($('.o_content tr.o_group_header').length, 4); actionManager.destroy(); }); - QUnit.test('a separator in groupbys does not cause problems', function (assert) { + QUnit.test('a separator in groupbys does not cause problems', async function (assert) { assert.expect(6); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(7); + await actionManager.doAction(7); // open menu 'Group By' - testUtils.dom.click($('span.fa-bars')); + await testUtils.dom.click($('span.fa-bars')); // open options menu - testUtils.dom.click($('.o_group_by_menu .o_menu_item a:first')); + await testUtils.dom.click($('.o_group_by_menu .o_menu_item a:first')); // activate groupby with 'day' option - testUtils.dom.click($('.o_group_by_menu .o_menu_item .o_item_option[data-option_id="day"]')); + await testUtils.dom.click($('.o_group_by_menu .o_menu_item .o_item_option[data-option_id="day"]')); // activate the second groupby - testUtils.dom.click($('.o_group_by_menu .o_menu_item > a').eq(1)); + await testUtils.dom.click($('.o_group_by_menu .o_menu_item > a').eq(1)); assert.strictEqual($('.o_group_by_menu .o_menu_item').length, 2); assert.strictEqual($('.o_group_by_menu .o_menu_item > .dropdown-item.selected').length, 2); // deactivate second groupby - testUtils.dom.click($('.o_group_by_menu .o_menu_item > a').eq(1)); + await testUtils.dom.click($('.o_group_by_menu .o_menu_item > a').eq(1)); assert.hasClass($('.o_group_by_menu .o_menu_item > .dropdown-item').eq(0), 'selected'); assert.doesNotHaveClass($('.o_group_by_menu .o_menu_item > .dropdown-item').eq(1), 'selected'); // remove facet - testUtils.dom.click($('.o_facet_remove')); + await testUtils.dom.click($('.o_facet_remove')); assert.doesNotHaveClass($('.o_group_by_menu .o_menu_item > .dropdown-item').eq(0), 'selected'); assert.doesNotHaveClass($('.o_group_by_menu .o_menu_item > .dropdown-item').eq(1), 'selected'); actionManager.destroy(); @@ -514,22 +515,24 @@ QUnit.module('Search View', { QUnit.module('FilterMenu'); - QUnit.test('Search date and datetime fields. Support of timezones', function (assert) { + QUnit.test('Search date and datetime fields. Support of timezones', async function (assert) { assert.expect(4); this.data.partner.fields.birth_datetime = {string: "Birth DateTime", type: "datetime", store: true, sortable: true}; this.data.partner.records = this.data.partner.records.slice(0,-1); // exclude wrong date record - function stringToEvent ($element, string) { + async function stringToEvent ($element, string) { for (var i = 0; i < string.length; i++) { var keyAscii = string.charCodeAt(i); $element.val($element.val()+string[i]); + await testUtils.nextTick(); $element.trigger($.Event('keyup', {which: keyAscii, keyCode:keyAscii})); + await testUtils.nextTick(); } } var searchReadSequence = 0; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: [{ id: 11, name: 'Partners Action 11', @@ -571,31 +574,23 @@ QUnit.module('Search View', { }, }); - actionManager.doAction(11); + await actionManager.doAction(11); // Date case var $autocomplete = $('.o_searchview_input'); - stringToEvent($autocomplete, '07/15/1983'); - - $autocomplete.trigger($.Event('keyup', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); + await stringToEvent($autocomplete, '07/15/1983'); + await testUtils.fields.triggerKey('up', $autocomplete, 'enter'); assert.equal($('.o_searchview_facet .o_facet_values').text().trim(), '07/15/1983', 'The format of the date in the facet should be in locale'); // Close Facet - $('.o_searchview_facet .o_facet_remove').click(); + await testUtils.dom.click($('.o_searchview_facet .o_facet_remove')); // DateTime case $autocomplete = $('.o_searchview_input'); - stringToEvent($autocomplete, '07/15/1983 00:00:00'); - - $autocomplete.trigger($.Event('keyup', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); + await stringToEvent($autocomplete, '07/15/1983 00:00:00'); + await testUtils.fields.triggerKey('up', $autocomplete, 'enter'); assert.equal($('.o_searchview_facet .o_facet_values').text().trim(), '07/15/1983 00:00:00', 'The format of the datetime in the facet should be in locale'); @@ -603,44 +598,44 @@ QUnit.module('Search View', { actionManager.destroy(); }); - QUnit.test('add a custom filter works', function (assert) { + QUnit.test('add a custom filter works', async function (assert) { assert.expect(1); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(1); - testUtils.dom.click($('span.fa-filter')); - testUtils.dom.click($('.o_add_custom_filter')); - testUtils.dom.click($('.o_apply_filter')); + await actionManager.doAction(1); + await testUtils.dom.click($('span.fa-filter')); + await testUtils.dom.click($('.o_add_custom_filter')); + await testUtils.dom.click($('.o_apply_filter')); assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').text().trim(), 'ID is \"0\"', 'should have a facet with candle name'); actionManager.destroy(); }); - QUnit.test('deactivate a new custom filter works', function (assert) { + QUnit.test('deactivate a new custom filter works', async function (assert) { assert.expect(1); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(1); - testUtils.dom.click($('span.fa-filter')); - testUtils.dom.click($('.o_add_custom_filter')); - testUtils.dom.click($('.o_apply_filter')); - testUtils.dom.click($('.o_menu_item')); + await actionManager.doAction(1); + await testUtils.dom.click($('span.fa-filter')); + await testUtils.dom.click($('.o_add_custom_filter')); + await testUtils.dom.click($('.o_apply_filter')); + await testUtils.dom.click($('.o_menu_item')); assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').length, 0, 'no facet should be in the search view'); actionManager.destroy(); }); - QUnit.test('filter by a date field using period works', function (assert) { + QUnit.test('filter by a date field using period works', async function (assert) { assert.expect(15); var self = this; @@ -659,7 +654,7 @@ QUnit.module('Search View', { return new Date(2017,2,22); }; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -671,12 +666,12 @@ QUnit.module('Search View', { }, }); - actionManager.doAction(5); + await actionManager.doAction(5); // open menu 'Filter' - testUtils.dom.click($('.o_search_options .fa-filter')); + await testUtils.dom.click($('.o_search_options .fa-filter')); // open menu options - testUtils.dom.click($('.o_menu_item')); + await testUtils.dom.click($('.o_menu_item')); var periodOptions = $('.o_menu_item .o_item_option').map(function () { return $(this).data('option_id'); @@ -685,33 +680,33 @@ QUnit.module('Search View', { assert.deepEqual(periodOptions, PERIOD_OPTIONS_IDS, "13 period options should be available:"); - testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="last_7_days"]')); - testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="last_30_days"]')); - testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="last_365_days"]')); - testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="last_5_years"]')); - testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="today"]')); - testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="this_week"]')); - testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="this_month"]')); - testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="this_quarter"]')); - testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="this_year"]')); - testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="yesterday"]')); - testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="last_week"]')); - testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="last_month"]')); - testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="last_quarter"]')); - testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="last_year"]')); + await testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="last_7_days"]')); + await testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="last_30_days"]')); + await testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="last_365_days"]')); + await testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="last_5_years"]')); + await testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="today"]')); + await testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="this_week"]')); + await testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="this_month"]')); + await testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="this_quarter"]')); + await testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="this_year"]')); + await testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="yesterday"]')); + await testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="last_week"]')); + await testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="last_month"]')); + await testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="last_quarter"]')); + await testUtils.dom.click($('.o_menu_item .o_item_option[data-option_id="last_year"]')); actionManager.destroy(); window.Date = RealDate; }); - QUnit.test('`context` key in <filter> is used', function (assert) { + QUnit.test('`context` key in <filter> is used', async function (assert) { assert.expect(3); this.archs['partner,4,search'] = '<search>'+ '<filter string="AAA" name="some_filter" context="{\'coucou_1\': 1}"></filter>' + '</search>'; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -723,11 +718,11 @@ QUnit.module('Search View', { }, }); - actionManager.doAction(5); + await actionManager.doAction(5); // select filter - testUtils.dom.click($('.o_search_options .fa-filter')); - testUtils.dom.click($('.o_menu_item:contains(AAA)')); + await testUtils.dom.click($('.o_search_options .fa-filter')); + await testUtils.dom.click($('.o_menu_item:contains(AAA)')); assert.verifySteps([ "{}", @@ -739,10 +734,10 @@ QUnit.module('Search View', { QUnit.module('Favorites Menu'); - QUnit.test('dynamic filters are saved dynamic', function (assert) { + QUnit.test('dynamic filters are saved dynamic', async function (assert) { assert.expect(1); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -758,18 +753,18 @@ QUnit.module('Search View', { }, }); - actionManager.doAction(6); - testUtils.dom.click($('span.fa-filter')); - testUtils.dom.click($('.o_filters_menu .o_menu_item a')); - testUtils.dom.click($('.o_item_option[data-option_id="today"]')); - testUtils.dom.click($('span.fa-star')); - testUtils.dom.click($('.o_favorites_menu .o_add_favorite')); - testUtils.fields.editInput($('div.o_favorite_name input'), 'name for favorite'); - testUtils.dom.click($('.o_favorites_menu .o_save_favorite button')); + await actionManager.doAction(6); + await testUtils.dom.click($('span.fa-filter')); + await testUtils.dom.click($('.o_filters_menu .o_menu_item a')); + await testUtils.dom.click($('.o_item_option[data-option_id="today"]')); + await testUtils.dom.click($('span.fa-star')); + await testUtils.dom.click($('.o_favorites_menu .o_add_favorite')); + await testUtils.fields.editInput($('div.o_favorite_name input'), 'name for favorite'); + await testUtils.dom.click($('.o_favorites_menu .o_save_favorite button')); actionManager.destroy(); }); - QUnit.test('save search filter in modal', function (assert) { + QUnit.test('save search filter in modal', async function (assert) { assert.expect(5); this.data.partner.records.push({ id: 7, @@ -785,7 +780,7 @@ QUnit.module('Search View', { display_name: "Partner 9", }); this.data.partner.fields.date_field.searchable = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -803,39 +798,39 @@ QUnit.module('Search View', { res_id: 1, }); - testUtils.form.clickEdit(form); + await testUtils.form.clickEdit(form); - testUtils.fields.many2one.clickOpenDropdown('bar'); - testUtils.fields.many2one.clickItem('bar','Search'); + await testUtils.fields.many2one.clickOpenDropdown('bar'); + await testUtils.fields.many2one.clickItem('bar','Search'); assert.strictEqual($('tr.o_data_row').length, 9, "should display 9 records"); - testUtils.dom.click($('button:contains(Filters)')); - testUtils.dom.click($('.o_add_custom_filter:visible')); + await testUtils.dom.click($('button:contains(Filters)')); + await testUtils.dom.click($('.o_add_custom_filter:visible')); assert.strictEqual($('.o_filter_condition select.o_searchview_extended_prop_field').val(), 'date_field', "date field should be selected"); - testUtils.dom.click($('.o_apply_filter')); + await testUtils.dom.click($('.o_apply_filter')); assert.strictEqual($('tr.o_data_row').length, 0, "should display 0 records"); // Save this search - testUtils.mock.intercept(form, 'create_filter', function (event) { + await testUtils.mock.intercept(form, 'create_filter', function (event) { assert.strictEqual(event.data.filter.name, "Awesome Test Customer Filter", "filter name should be correct"); }); - testUtils.dom.click($('button:contains(Favorites)')); - testUtils.dom.click($('.o_add_favorite')); + await testUtils.dom.click($('button:contains(Favorites)')); + await testUtils.dom.click($('.o_add_favorite')); var filterNameInput = $('.o_favorite_name .o_input[type="text"]:visible'); assert.strictEqual(filterNameInput.length, 1, "should display an input field for the filter name"); - testUtils.fields.editInput(filterNameInput, 'Awesome Test Customer Filter'); - testUtils.dom.click($('.o_save_favorite button')); + await testUtils.fields.editInput(filterNameInput, 'Awesome Test Customer Filter'); + await testUtils.dom.click($('.o_save_favorite button')); form.destroy(); }); - QUnit.test('save filters created via autocompletion works', function (assert) { + QUnit.test('save filters created via autocompletion works', async function (assert) { assert.expect(2); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -846,37 +841,40 @@ QUnit.module('Search View', { }, }); - actionManager.doAction(10); + await actionManager.doAction(10); $('.o_searchview_input').trigger($.Event('keypress', { which: 97, })); - + await testUtils.nextTick(); $('.o_searchview_input').trigger($.Event('keyup', { which: $.ui.keyCode.ENTER, keyCode: $.ui.keyCode.ENTER, })); + await testUtils.nextTick(); + + await testUtils.nextTick(); assert.strictEqual($('.o_searchview_input_container .o_facet_values span').text().trim(), "a"); - testUtils.dom.click($('button .fa-star')); - testUtils.dom.click($('.o_favorites_menu .o_add_favorite')); - testUtils.fields.editInput($('div.o_favorite_name input'), 'name for favorite'); - testUtils.dom.click($('.o_favorites_menu div.o_save_favorite button')); + await testUtils.dom.click($('button .fa-star')); + await testUtils.dom.click($('.o_favorites_menu .o_add_favorite')); + await testUtils.fields.editInput($('div.o_favorite_name input'), 'name for favorite'); + await testUtils.dom.click($('.o_favorites_menu div.o_save_favorite button')); actionManager.destroy(); }); - QUnit.test('delete an active favorite remove it both in list of favorite and in search bar', function (assert) { + QUnit.test('delete an active favorite remove it both in list of favorite and in search bar', async function (assert) { assert.expect(2); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, intercepts: { load_filters: function (event) { - return $.when([{ + return Promise.resolve([{ context: "{}", domain: "[]", id: 7, @@ -892,11 +890,11 @@ QUnit.module('Search View', { }, }); - actionManager.doAction(6); - testUtils.dom.click(actionManager.$('.o_control_panel .o_search_options button.o_favorites_menu_button')); + await actionManager.doAction(6); + await testUtils.dom.click(actionManager.$('.o_control_panel .o_search_options button.o_favorites_menu_button')); assert.containsOnce(actionManager, '.o_control_panel .o_searchview_input_container .o_facet_values'); - testUtils.dom.click(actionManager.$('.o_control_panel .o_search_options .o_favorites_menu span.o_trash_button')); - testUtils.modal.clickButton('Ok'); + await testUtils.dom.click(actionManager.$('.o_control_panel .o_search_options .o_favorites_menu span.o_trash_button')); + await testUtils.modal.clickButton('Ok'); assert.containsNone(actionManager, '.o_control_panel .o_searchview_input_container .o_facet_values'); actionManager.destroy(); }); @@ -910,7 +908,7 @@ QUnit.module('Search View', { data: this.data, intercepts: { load_filters: function (event) { - return $.when([{ + return Promise.resolve([{ context: "{}", domain: "[]", id: 7, @@ -932,17 +930,17 @@ QUnit.module('Search View', { QUnit.module('Search Arch'); - QUnit.test('arch order of groups of filters preserved', function (assert) { + QUnit.test('arch order of groups of filters preserved', async function (assert) { assert.expect(12); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(8); - testUtils.dom.click($('span.fa-filter')); + await actionManager.doAction(8); + await testUtils.dom.click($('span.fa-filter')); assert.strictEqual($('.o_filters_menu .o_menu_item').length, 11); for (var i = 0; i < 11; i++) { assert.strictEqual($('.o_filters_menu .o_menu_item').eq(i).text().trim(), (i+1).toString()); @@ -952,34 +950,21 @@ QUnit.module('Search View', { QUnit.module('Autocompletion'); - QUnit.test('selection via autocompletion modifies appropriately submenus', function (assert) { + QUnit.test('selection via autocompletion modifies appropriately submenus', async function (assert) { assert.expect(4); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(9); + await actionManager.doAction(9); - $('.o_searchview_input').trigger($.Event('keypress', { - which: 97, - })); - - $('.o_searchview_input').trigger($.Event('keyup', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); - - $('.o_searchview_input').trigger($.Event('keypress', { - which: 103, - })); - - $('.o_searchview_input').trigger($.Event('keyup', { - which: $.ui.keyCode.ENTER, - keyCode: $.ui.keyCode.ENTER, - })); + await testUtils.fields.triggerKey('press', $('.o_searchview_input'), 97); + await testUtils.fields.triggerKey('up', $('.o_searchview_input'), 'enter'); + await testUtils.fields.triggerKey('press', $('.o_searchview_input'), 103); + await testUtils.fields.triggerKey('up', $('.o_searchview_input'), 'enter'); assert.strictEqual($('.o_searchview_input_container .o_facet_values').eq(0).text().trim(), "Date Field Filter: This Month", @@ -988,28 +973,27 @@ QUnit.module('Search View', { "Date Field Groupby: Day", "There should be a filter facet with label 'Date Field Groupby: Day'"); - testUtils.dom.click($('button .fa-filter')); - testUtils.dom.click($('.o_filters_menu .o_menu_item').eq(0)); + await testUtils.dom.click($('button .fa-filter')); + await testUtils.dom.click($('.o_filters_menu .o_menu_item').eq(0)); assert.strictEqual($('.o_filters_menu .o_item_option a.selected').text().trim(), "This Month", "The item 'This Month' should be selected in the filters menu"); - testUtils.dom.click($('button .fa-bars')); - testUtils.dom.click($('.o_group_by_menu .o_menu_item').eq(0)); + await testUtils.dom.click($('button .fa-bars')); + await testUtils.dom.click($('.o_group_by_menu .o_menu_item').eq(0)); assert.strictEqual($('.o_group_by_menu .o_item_option a.selected').text().trim(), "Day", "The item 'Day' should be selected in the groupby menu"); actionManager.destroy(); }); - QUnit.test('select an autocomplete field with `context` key', function (assert) { + QUnit.test('select an autocomplete field with `context` key', async function (assert) { assert.expect(9); var searchRead = 0; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, - debug: 1, mockRPC: function (route, args) { if (route === '/web/dataset/search_read') { if (searchRead === 1) { @@ -1025,17 +1009,24 @@ QUnit.module('Search View', { }, }); - actionManager.doAction(11); + await actionManager.doAction(11); + await testUtils.nextTick(); assert.strictEqual(searchRead, 1, "there should be 1 search_read"); // 'r' key to filter on bar "First Record" $('.o_searchview_input').val('r'); $('.o_searchview_input').trigger($.Event('keypress', { which: 82, keyCode: 82 })); + await testUtils.nextTick(); $('.o_searchview_input').trigger($.Event('keydown', { which: $.ui.keyCode.DOWN, keyCode: $.ui.keyCode.DOWN })); + await testUtils.nextTick(); $('.o_searchview_input').trigger($.Event('keydown', { which: $.ui.keyCode.RIGHT, keyCode: $.ui.keyCode.RIGHT })); + await testUtils.nextTick(); $('.o_searchview_input').trigger($.Event('keydown', { which: $.ui.keyCode.DOWN, keyCode: $.ui.keyCode.DOWN })); + await testUtils.nextTick(); $('.o_searchview_input').trigger($.Event('keydown', { which: $.ui.keyCode.ENTER, keyCode: $.ui.keyCode.ENTER })); + await testUtils.nextTick(); + await testUtils.nextTick(); assert.strictEqual($('.o_searchview_input_container .o_facet_values').eq(0).text().trim(), "First record", "the autocompletion facet should be correct"); assert.strictEqual(searchRead, 2, "there should be 2 search_read"); @@ -1043,11 +1034,16 @@ QUnit.module('Search View', { // 'r' key to filter on bar "Second Record" $('.o_searchview_input').val('r'); $('.o_searchview_input').trigger($.Event('keypress', { which: 82, keyCode: 82 })); + await testUtils.nextTick(); $('.o_searchview_input').trigger($.Event('keydown', { which: $.ui.keyCode.DOWN, keyCode: $.ui.keyCode.DOWN })); + await testUtils.nextTick(); $('.o_searchview_input').trigger($.Event('keydown', { which: $.ui.keyCode.RIGHT, keyCode: $.ui.keyCode.RIGHT })); + await testUtils.nextTick(); $('.o_searchview_input').trigger($.Event('keydown', { which: $.ui.keyCode.DOWN, keyCode: $.ui.keyCode.DOWN })); + await testUtils.nextTick(); $('.o_searchview_input').trigger($.Event('keydown', { which: $.ui.keyCode.DOWN, keyCode: $.ui.keyCode.DOWN })); $('.o_searchview_input').trigger($.Event('keydown', { which: $.ui.keyCode.ENTER, keyCode: $.ui.keyCode.ENTER })); + await testUtils.nextTick(); assert.strictEqual($('.o_searchview_input_container .o_facet_values').eq(0).text().trim(), "First record or Second record", "the autocompletion facet should be correct"); @@ -1056,10 +1052,10 @@ QUnit.module('Search View', { actionManager.destroy(); }); - QUnit.test('no search text triggers a reload', function (assert) { + QUnit.test('no search text triggers a reload', async function (assert) { assert.expect(2); var rpcs = 0; - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -1069,16 +1065,19 @@ QUnit.module('Search View', { }, }); - actionManager.doAction(10); + await actionManager.doAction(10); rpcs = 0; $('.o_searchview_input').trigger($.Event('keydown', { which: $.ui.keyCode.ENTER, keyCode: $.ui.keyCode.ENTER, })); + await testUtils.nextTick(); + $('.o_searchview_input').trigger($.Event('keyup', { which: $.ui.keyCode.ENTER, keyCode: $.ui.keyCode.ENTER, })); + await testUtils.nextTick(); assert.containsNone(actionManager, '.o_searchview_facet_label'); assert.strictEqual(rpcs, 2, "should have reloaded"); @@ -1088,16 +1087,16 @@ QUnit.module('Search View', { QUnit.module('TimeRangeMenu'); - QUnit.test('time range menu stays hidden', function (assert) { + QUnit.test('time range menu stays hidden', async function (assert) { assert.expect(4); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, }); - actionManager.doAction(1); + await actionManager.doAction(1); // check that there is no time range menu assert.containsNone(actionManager, '.o_control_panel .o_search_options .o_time_range_menu'); @@ -1105,8 +1104,8 @@ QUnit.module('Search View', { assert.strictEqual($('.o_facet_values').length, 0); // activate groupby - testUtils.dom.click($('button .fa-bars')); - testUtils.dom.click($('.o_menu_item a').eq(0)); + await testUtils.dom.click($('button .fa-bars')); + await testUtils.dom.click($('.o_menu_item a').eq(0)); // check that there is a facet assert.strictEqual($('div.o_facet_values').length, 1); // check that there is still no time range menu @@ -1114,7 +1113,7 @@ QUnit.module('Search View', { actionManager.destroy(); }); - QUnit.test('time range menu in comparison mode', function (assert) { + QUnit.test('time range menu in comparison mode', async function (assert) { assert.expect(46); var self = this; @@ -1123,7 +1122,7 @@ QUnit.module('Search View', { var periodOptionText, periodOptionValue; var unpatchDate = patchDate(2017, 2, 22, 1, 0, 0); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -1149,7 +1148,7 @@ QUnit.module('Search View', { }, }); // time range menu should be available in graph view - actionManager.doAction(4); + await actionManager.doAction(4); var $timeRangeMenu = $('.o_time_range_menu'); assert.strictEqual($timeRangeMenu.not('.o_hidden').length, 1, @@ -1164,35 +1163,34 @@ QUnit.module('Search View', { assert.deepEqual(periodOptions, PERIOD_OPTIONS_IDS, "13 period options should be available:"); - $periodOptions.each(function () { - periodOptionText = $(this).text().trim(); - periodOptionValue = $(this).val(); + for (var option of $periodOptions) { + periodOptionText = $(option).text().trim(); + periodOptionValue = $(option).val(); // opens time range menu dropdown - testUtils.dom.click($('.o_time_range_menu_button')); + await testUtils.dom.click($('.o_time_range_menu_button')); var $timeRangeMenu = $('.o_time_range_menu'); // comparison is not checked by default if (!$timeRangeMenu.find('.o_comparison_checkbox').is(':checked')) { - testUtils.dom.click($timeRangeMenu.find('.o_comparison_checkbox')); + await testUtils.dom.click($timeRangeMenu.find('.o_comparison_checkbox')); assert.strictEqual($('.o_comparison_time_range_selector:visible').length, 1, "Comparison has to be checked (only at the first time)"); } // select one period option to test it $timeRangeMenu.find('.o_time_range_selector').val(periodOptionValue); // apply - testUtils.dom.click($timeRangeMenu.find('.o_apply_range')); + await testUtils.dom.click($timeRangeMenu.find('.o_apply_range')); assert.strictEqual($('.o_facet_values').text().trim(), "Date: " + periodOptionText + " / Previous Period", "Facet should be updated with this period: " + periodOptionValue); - }); - + } unpatchDate(); actionManager.destroy(); }); - QUnit.test('a default time range only in context is taken into account', function (assert) { + QUnit.test('a default time range only in context is taken into account', async function (assert) { assert.expect(2); - var actionManager = createActionManager({ + var actionManager = await createActionManager({ actions: this.actions, archs: this.archs, data: this.data, @@ -1206,7 +1204,7 @@ QUnit.module('Search View', { }, }); - actionManager.doAction({ + await actionManager.doAction({ res_model: 'partner', type: 'ir.actions.act_window', views: [[false, 'pivot']], @@ -1215,7 +1213,7 @@ QUnit.module('Search View', { actionManager.destroy(); }); - QUnit.test('Customizing filter does not close the filter dropdown', function (assert) { + QUnit.test('Customizing filter does not close the filter dropdown', async function (assert) { assert.expect(3); var self = this; @@ -1225,7 +1223,7 @@ QUnit.module('Search View', { }); this.data.partner.fields.date_field.searchable = true; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -1242,25 +1240,26 @@ QUnit.module('Search View', { res_id: 1, }); - testUtils.fields.many2one.clickOpenDropdown('bar'); - testUtils.fields.many2one.clickItem('bar', 'Search More'); + await testUtils.fields.many2one.clickOpenDropdown('bar'); + await testUtils.fields.many2one.clickItem('bar', 'Search More'); assert.containsOnce(document.body, '.modal'); - testUtils.dom.click($('.modal .o_filters_menu_button')); + await testUtils.dom.click($('.modal .o_filters_menu_button')); var $filterDropdown = $('.modal .o_filters_menu'); - testUtils.dom.click($filterDropdown.find('.o_add_custom_filter')); + await testUtils.dom.click($filterDropdown.find('.o_add_custom_filter')); assert.containsN($filterDropdown, '.o_input', 3); // We really are interested in the click event // We do it twice on each input to make sure // the parent dropdown doesn't react to any of it - _.each($filterDropdown.find('input'), function (input) { + _.each($filterDropdown.find('input'), async function (input) { var $input = $(input); $input.click(); $input.click(); + await testUtils.nextTick(); }); assert.isVisible($filterDropdown); diff --git a/addons/web/static/tests/views/view_dialogs_tests.js b/addons/web/static/tests/views/view_dialogs_tests.js index 27275e7fd80..b17112c8978 100644 --- a/addons/web/static/tests/views/view_dialogs_tests.js +++ b/addons/web/static/tests/views/view_dialogs_tests.js @@ -52,7 +52,6 @@ QUnit.module('Views', { }, }; }, - }, function () { QUnit.module('view_dialogs'); @@ -64,7 +63,7 @@ QUnit.module('Views', { return widget; } - QUnit.test('formviewdialog buttons in footer are positioned properly', function (assert) { + QUnit.test('formviewdialog buttons in footer are positioned properly', async function (assert) { assert.expect(2); var parent = createParent({ @@ -84,6 +83,7 @@ QUnit.module('Views', { res_model: 'partner', res_id: 1, }).open(); + await testUtils.nextTick(); assert.notOk($('.modal-body button').length, "should not have any button in body"); @@ -92,7 +92,7 @@ QUnit.module('Views', { parent.destroy(); }); - QUnit.test('formviewdialog buttons in footer are not duplicated', function (assert) { + QUnit.test('formviewdialog buttons in footer are not duplicated', async function (assert) { assert.expect(2); this.data.partner.fields.poney_ids = {string: "Poneys", type: "one2many", relation: 'partner'}; this.data.partner.records[0].poney_ids = []; @@ -112,22 +112,20 @@ QUnit.module('Views', { res_model: 'partner', res_id: 1, }).open(); + await testUtils.nextTick(); assert.strictEqual($('.modal button.btn-primary').length, 1, "should have 1 buttons in modal"); - testUtils.dom.click($('.o_field_x2many_list_row_add a')); - $('input.o_input').trigger($.Event('keydown', { - which: $.ui.keyCode.ESCAPE, - keyCode: $.ui.keyCode.ESCAPE, - })); + await testUtils.dom.click($('.o_field_x2many_list_row_add a')); + await testUtils.fields.triggerKeydown($('input.o_input'), 'escape'); assert.strictEqual($('.modal button.btn-primary').length, 1, "should still have 1 buttons in modal"); parent.destroy(); }); - QUnit.test('SelectCreateDialog use domain, group_by and search default', function (assert) { + QUnit.test('SelectCreateDialog use domain, group_by and search default', async function (assert) { assert.expect(3); var search = 0; @@ -183,7 +181,8 @@ QUnit.module('Views', { }, }); - var dialog = new dialogs.SelectCreateDialog(parent, { + var dialog; + new dialogs.SelectCreateDialog(parent, { no_create: true, readonly: true, res_model: 'partner', @@ -192,15 +191,18 @@ QUnit.module('Views', { search_default_groupby_bar: true, search_default_foo: 'piou', }, - }).open(); + }).open().then(function (result) { + dialog = result; + }); + await testUtils.nextTick(); - testUtils.dom.click(dialog.$('.o_searchview_facet:contains(groupby_bar) .o_facet_remove')); - testUtils.dom.click(dialog.$('.o_searchview_facet .o_facet_remove')); + await testUtils.dom.click(dialog.$('.o_searchview_facet:contains(groupby_bar) .o_facet_remove')); + await testUtils.dom.click(dialog.$('.o_searchview_facet .o_facet_remove')); parent.destroy(); }); - QUnit.test('SelectCreateDialog correctly evaluates domains', function (assert) { + QUnit.test('SelectCreateDialog correctly evaluates domains', async function (assert) { assert.expect(1); var parent = createParent({ @@ -234,11 +236,12 @@ QUnit.module('Views', { res_model: 'partner', domain: "[['id', '=', uid]]", }).open(); + await testUtils.nextTick(); parent.destroy(); }); - QUnit.test('SelectCreateDialog list view in readonly', function (assert) { + QUnit.test('SelectCreateDialog list view in readonly', async function (assert) { assert.expect(1); var parent = createParent({ @@ -254,12 +257,16 @@ QUnit.module('Views', { }, }); - var dialog = new dialogs.SelectCreateDialog(parent, { + var dialog; + new dialogs.SelectCreateDialog(parent, { res_model: 'partner', - }).open(); + }).open().then(function (result) { + dialog = result; + }); + await testUtils.nextTick(); // click on the first row to see if the list is editable - testUtils.dom.click(dialog.$('.o_list_view tbody tr:first td:not(.o_list_record_selector):first')); + await testUtils.dom.click(dialog.$('.o_list_view tbody tr:first td:not(.o_list_record_selector):first')); assert.equal(dialog.$('.o_list_view tbody tr:first td:not(.o_list_record_selector):first input').length, 0, "list view should not be editable in a SelectCreateDialog"); @@ -267,10 +274,10 @@ QUnit.module('Views', { parent.destroy(); }); - QUnit.test('SelectCreateDialog cascade x2many in create mode', function (assert) { + QUnit.test('SelectCreateDialog cascade x2many in create mode', async function (assert) { assert.expect(5); - var form = createView({ + var form = await createView({ View: FormView, model: 'product', data: this.data, @@ -310,31 +317,31 @@ QUnit.module('Views', { mockRPC: function(route, args) { if (route === '/web/dataset/call_kw/partner/get_formview_id') { - return $.when(false); + return Promise.resolve(false); } if (route === '/web/dataset/call_kw/instrument/get_formview_id') { - return $.when(false); + return Promise.resolve(false); } if (route === '/web/dataset/call_kw/instrument/create') { assert.deepEqual(args.args, [{badassery: [[6, false, [1]]], name: false}], 'The method create should have been called with the right arguments'); - return $.when(false); + return Promise.resolve(false); } return this._super(route, args); }, }); - testUtils.form.clickEdit(form); - testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); - testUtils.dom.click(form.$('.o_field_widget .o_field_many2one[name=instrument] input')); - testUtils.dom.click($('ul.ui-autocomplete.ui-front.ui-menu.ui-widget.ui-widget-content li.o_m2o_dropdown_option').first()); + await testUtils.form.clickEdit(form); + await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')); + await testUtils.dom.click(form.$('.o_field_widget .o_field_many2one[name=instrument] input')); + await testUtils.dom.click($('ul.ui-autocomplete.ui-front.ui-menu.ui-widget.ui-widget-content li.o_m2o_dropdown_option').first()); var $modal = $('.modal-lg'); assert.equal($modal.length, 1, 'There should be one modal'); - testUtils.dom.click($modal.find('.o_field_x2many_list_row_add a')); + await testUtils.dom.click($modal.find('.o_field_x2many_list_row_add a')); var $modals = $('.modal-lg'); @@ -342,9 +349,9 @@ QUnit.module('Views', { 'There should be two modals'); var $second_modal = $modals.not($modal); - testUtils.dom.click($second_modal.find('.o_list_view.table.table-sm.table-striped.o_list_view_ungrouped .o_data_row input[type=checkbox]')); + await testUtils.dom.click($second_modal.find('.o_list_view.table.table-sm.table-striped.o_list_view_ungrouped .o_data_row input[type=checkbox]')); - testUtils.dom.click($second_modal.find('.o_select_button')); + await testUtils.dom.click($second_modal.find('.o_select_button')); $modal = $('.modal-lg'); @@ -354,18 +361,18 @@ QUnit.module('Views', { assert.equal($modal.find('.o_data_cell').text(), 'Awsome', 'There should be one item in the list of the modal'); - testUtils.dom.click($modal.find('.btn.btn-primary')); + await testUtils.dom.click($modal.find('.btn.btn-primary')); form.destroy(); }); - QUnit.test('Form dialog and subview with _view_ref contexts', function (assert) { + QUnit.test('Form dialog and subview with _view_ref contexts', async function (assert) { assert.expect(2); this.data.instrument.records = [{id: 1, name: 'Tromblon', badassery: [1]}]; this.data.partner.records[0].instrument = 1; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -390,7 +397,7 @@ QUnit.module('Views', { mockRPC: function(route, args) { if (args.method === 'get_formview_id') { - return $.when(false); + return Promise.resolve(false); } return this._super(route, args); }, @@ -410,11 +417,11 @@ QUnit.module('Views', { }, }); - form.$('.o_field_widget[name="instrument"] button.o_external_button').click(); + await testUtils.dom.click(form.$('.o_field_widget[name="instrument"] button.o_external_button')); form.destroy(); }); - QUnit.test('SelectCreateDialog: save current search', function (assert) { + QUnit.test('SelectCreateDialog: save current search', async function (assert) { assert.expect(4); testUtils.mock.patch(ListController, { @@ -451,32 +458,34 @@ QUnit.module('Views', { }, }); - var dialog = new dialogs.SelectCreateDialog(parent, { + var dialog; + new dialogs.SelectCreateDialog(parent, { context: {shouldNotBeInFilterContext: false}, res_model: 'partner', - }).open(); + }).open().then(function (result) { + dialog = result; + }); + await testUtils.nextTick(); - assert.containsN(dialog, '.o_data_row', 3, - "should contain 3 records"); + assert.containsN(dialog, '.o_data_row', 3, "should contain 3 records"); // filter on bar - testUtils.dom.click(dialog.$('.o_dropdown_toggler_btn:contains(Filters)')); - testUtils.dom.click(dialog.$('.o_filters_menu a:contains(Bar)')); + await testUtils.dom.click(dialog.$('.o_dropdown_toggler_btn:contains(Filters)')); + await testUtils.dom.click(dialog.$('.o_filters_menu a:contains(Bar)')); - assert.containsN(dialog, '.o_data_row', 2, - "should contain 2 records"); + assert.containsN(dialog, '.o_data_row', 2, "should contain 2 records"); // save filter - testUtils.dom.click(dialog.$('.o_dropdown_toggler_btn:contains(Favorites)')); - testUtils.dom.click(dialog.$('.o_add_favorite')); - dialog.$('.o_favorite_name input[type=text]').val('some name'); // name the filter - testUtils.dom.click(dialog.$('.o_save_favorite button')); + await testUtils.dom.click(dialog.$('.o_dropdown_toggler_btn:contains(Favorites)')); + await testUtils.dom.click(dialog.$('.o_add_favorite')); + await testUtils.fields.editInput(dialog.$('.o_favorite_name input[type=text]'), 'some name'); // name the filter + await testUtils.dom.click(dialog.$('.o_save_favorite button')); testUtils.mock.unpatch(ListController); parent.destroy(); }); - QUnit.test('propagate can_create onto the search popup o2m', function (assert) { + QUnit.test('propagate can_create onto the search popup o2m', async function (assert) { assert.expect(3); this.data.instrument.records = [ @@ -490,7 +499,7 @@ QUnit.module('Views', { {id: 8, name: 'Tromblon8'}, ]; - var form = createView({ + var form = await createView({ View: FormView, model: 'partner', data: this.data, @@ -513,18 +522,19 @@ QUnit.module('Views', { mockRPC: function(route, args) { if (args.method === 'get_formview_id') { - return $.when(false); + return Promise.resolve(false); } return this._super(route, args); }, }); - form.$('.o_field_widget[name="instrument"] .o_input').click(); + await testUtils.dom.click(form.$('.o_field_widget[name="instrument"] .o_input')); assert.notOk($('.ui-autocomplete a:contains(Create and Edit)').length, 'Create and edit not present in dropdown'); - $('.ui-autocomplete a:contains(Search More)').trigger('mouseenter').trigger('click'); + await testUtils.dom.triggerEvents($('.ui-autocomplete a:contains(Search More)'), + ['mouseenter', 'click']); var $modal = $('.modal-dialog.modal-lg'); diff --git a/addons/web/static/tests/widgets/data_export_tests.js b/addons/web/static/tests/widgets/data_export_tests.js index 51c82ef1ded..d9453d70405 100644 --- a/addons/web/static/tests/widgets/data_export_tests.js +++ b/addons/web/static/tests/widgets/data_export_tests.js @@ -35,7 +35,7 @@ QUnit.module('widgets', { QUnit.module('Data Export'); - QUnit.test('exporting all data in list view', function (assert) { + QUnit.test('exporting all data in list view', async function (assert) { assert.expect(6); var blockUI = framework.blockUI; @@ -47,7 +47,7 @@ QUnit.module('widgets', { assert.step('unblock UI'); }; - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -57,13 +57,13 @@ QUnit.module('widgets', { }, mockRPC: function (route) { if (route === '/web/export/formats') { - return $.when([ + return Promise.resolve([ {tag: 'csv', label: 'CSV'}, {tag: 'xls', label: 'Excel'}, ]); } if (route === '/web/export/get_fields') { - return $.when([ + return Promise.resolve([ { field_type: "one2many", string: "Activities", @@ -94,19 +94,19 @@ QUnit.module('widgets', { }, }); - testUtils.dom.click(list.$('thead th.o_list_record_selector input')); - testUtils.dom.click(list.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); - testUtils.dom.click(list.sidebar.$('a:contains(Export)')); + await testUtils.dom.click(list.$('thead th.o_list_record_selector input')); + await testUtils.dom.click(list.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + await testUtils.dom.click(list.sidebar.$('a:contains(Export)')); assert.strictEqual($('.modal').length, 1, "a modal dialog should be open"); assert.strictEqual($('span.o_tree_column:contains(Activities)').length, 1, "the Activities field should be in the list of exportable fields"); // select the field Description, click on add, then export and close - testUtils.dom.click($('.modal span:contains(Description)')); - testUtils.dom.click($('.modal .o_add_field')); - testUtils.dom.click($('.modal span:contains(Export To File)')); - testUtils.dom.click($('.modal span:contains(Close)')); + await testUtils.dom.click($('.modal span:contains(Description)')); + await testUtils.dom.click($('.modal .o_add_field')); + await testUtils.dom.click($('.modal span:contains(Export To File)')); + await testUtils.dom.click($('.modal span:contains(Close)')); list.destroy(); framework.blockUI = blockUI; @@ -118,17 +118,17 @@ QUnit.module('widgets', { ]); }); - QUnit.test('saving fields list when exporting data', function (assert) { + QUnit.test('saving fields list when exporting data', async function (assert) { assert.expect(5); var create = data.DataSet.prototype.create; data.DataSet.prototype.create = function (data, options) { assert.step('create'); - return $.when([]); + return Promise.resolve([]); }; - var list = createView({ + var list = await createView({ View: ListView, model: 'partner', data: this.data, @@ -138,13 +138,13 @@ QUnit.module('widgets', { }, mockRPC: function (route) { if (route === '/web/export/formats') { - return $.when([ + return Promise.resolve([ {tag: 'csv', label: 'CSV'}, {tag: 'xls', label: 'Excel'}, ]); } if (route === '/web/export/get_fields') { - return $.when([ + return Promise.resolve([ { field_type: "one2many", string: "Activities", @@ -162,29 +162,29 @@ QUnit.module('widgets', { }); // Open the export modal - testUtils.dom.click(list.$('thead th.o_list_record_selector input')); - testUtils.dom.click(list.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); - testUtils.dom.click(list.sidebar.$('a:contains(Export)')); + await testUtils.dom.click(list.$('thead th.o_list_record_selector input')); + await testUtils.dom.click(list.sidebar.$('.o_dropdown_toggler_btn:contains(Action)')); + await testUtils.dom.click(list.sidebar.$('a:contains(Export)')); assert.strictEqual($('.modal').length, 1, "a modal dialog should be open"); // Select 'Activities' in fields to export assert.strictEqual($('.modal select.o_fields_list option').length, 0, "the fields list should be empty"); - testUtils.dom.click($('.modal .o_export_tree_item:contains(Activities)')); - testUtils.dom.click($('.modal button:contains(Add)')); + await testUtils.dom.click($('.modal .o_export_tree_item:contains(Activities)')); + await testUtils.dom.click($('.modal button:contains(Add)')); assert.strictEqual($('.modal select.o_fields_list option').length, 1, "there should be one item in the fields list"); // Save fields list - testUtils.dom.click($('.modal a:contains(Save fields list)')); - testUtils.fields.editInput($('.modal .o_save_list > input'), 'fields list'); - testUtils.dom.click($('.modal .o_save_list > button')); + await testUtils.dom.click($('.modal a:contains(Save fields list)')); + await testUtils.fields.editInput($('.modal .o_save_list > input'), 'fields list'); + await testUtils.dom.click($('.modal .o_save_list > button')); assert.verifySteps(['create'], "create should have been called"); // Close the modal and destroy list - testUtils.dom.click($('.modal button span:contains(Close)')); + await testUtils.dom.click($('.modal button span:contains(Close)')); list.destroy(); // restore create function diff --git a/addons/web/static/tests/widgets/domain_selector_tests.js b/addons/web/static/tests/widgets/domain_selector_tests.js index 53f1d9cff26..305315b72c6 100644 --- a/addons/web/static/tests/widgets/domain_selector_tests.js +++ b/addons/web/static/tests/widgets/domain_selector_tests.js @@ -50,7 +50,7 @@ QUnit.module('DomainSelector', { }, }, function () { - QUnit.test("creating a domain from scratch", function (assert) { + QUnit.test("creating a domain from scratch", async function (assert) { assert.expect(13); var $target = $("#qunit-fixture"); @@ -61,7 +61,7 @@ QUnit.module('DomainSelector', { debugMode: true, }); testUtils.mock.addMockEnvironment(domainSelector, {data: this.data}); - domainSelector.appendTo($target); + await domainSelector.appendTo($target); // As we gave an empty domain, there should be a visible button to add // the first domain part @@ -71,13 +71,14 @@ QUnit.module('DomainSelector', { // Clicking on the button should add a visible field selector in the // widget so that the user can change the field chain - testUtils.dom.click($domainAddFirstNodeButton); + await testUtils.dom.click($domainAddFirstNodeButton); var $fieldSelector = domainSelector.$(".o_field_selector:visible"); assert.strictEqual($fieldSelector.length, 1, "there should be a field selector"); // Focusing the field selector input should open a field selector popover $fieldSelector.trigger('focusin'); + await testUtils.nextTick(); var $fieldSelectorPopover = $fieldSelector.find(".o_field_selector_popover:visible"); assert.strictEqual($fieldSelectorPopover.length, 1, "field selector popover should be visible"); @@ -97,7 +98,7 @@ QUnit.module('DomainSelector', { // Clicking the "Bar" field should change the internal domain and this // should be displayed in the debug input - testUtils.dom.click($barLi); + await testUtils.dom.click($barLi); assert.strictEqual( domainSelector.$(".o_domain_debug_input").val(), '[["bar","=",True]]', @@ -108,7 +109,7 @@ QUnit.module('DomainSelector', { // should add the default "['id', '=', 1]" domain var $plus = domainSelector.$(".fa-plus-circle"); assert.strictEqual($plus.length, 1, "there should be a '+' button"); - testUtils.dom.click($plus); + await testUtils.dom.click($plus); assert.strictEqual( domainSelector.$(".o_domain_debug_input").val(), '["&",["bar","=",True],["id","=",1]]', @@ -119,7 +120,7 @@ QUnit.module('DomainSelector', { // domains and the "|" operator var $dots = domainSelector.$(".fa-ellipsis-h"); assert.strictEqual($dots.length, 2, "there should be two '...' buttons"); - testUtils.dom.click($dots.first()); + await testUtils.dom.click($dots.first()); assert.strictEqual( domainSelector.$(".o_domain_debug_input").val(), '["&","&",["bar","=",True],"|",["id","=",1],["id","=",1],["id","=",1]]', @@ -130,6 +131,7 @@ QUnit.module('DomainSelector', { // field instead of "id" should rerender the widget and adapt the // widget suggestions domainSelector.$(".o_domain_debug_input").val('["&","&",["bar","=",True],"|",["foo","=","hello"],["id","=",1],["id","=",1]]').change(); + await testUtils.nextTick(); assert.strictEqual(domainSelector.$(".o_field_selector").eq(1).find("input.o_field_selector_debug").val(), "foo", "the second field selector should now contain the 'foo' value"); assert.ok(domainSelector.$(".o_domain_leaf_operator_select").eq(1).html().indexOf("contains") >= 0, @@ -140,8 +142,8 @@ QUnit.module('DomainSelector', { // "foo" fields, with the initial "&" operator var $minus = domainSelector.$(".o_domain_delete_node_button"); assert.strictEqual($minus.length, 5, "there should be five 'x' buttons"); - testUtils.dom.click($minus.last()); - testUtils.dom.click(domainSelector.$(".o_domain_delete_node_button").last()); + await testUtils.dom.click($minus.last()); + await testUtils.dom.click(domainSelector.$(".o_domain_delete_node_button").last()); assert.strictEqual( domainSelector.$(".o_domain_debug_input").val(), '["&",["bar","=",True],["foo","=","hello"]]', @@ -150,7 +152,7 @@ QUnit.module('DomainSelector', { domainSelector.destroy(); }); - QUnit.test("building a domain with a datetime", function (assert) { + QUnit.test("building a domain with a datetime", async function (assert) { assert.expect(2); var $target = $("#qunit-fixture"); @@ -160,7 +162,7 @@ QUnit.module('DomainSelector', { readonly: false, }); testUtils.mock.addMockEnvironment(domainSelector, {data: this.data}); - domainSelector.appendTo($target); + await domainSelector.appendTo($target); // Check that there is a datepicker to choose the date var $datepicker = domainSelector.$(".o_datepicker:visible"); @@ -168,15 +170,15 @@ QUnit.module('DomainSelector', { "there should be a datepicker"); var val = $datepicker.find('input').val(); - testUtils.dom.openDatepicker($datepicker); - testUtils.dom.clickFirst($('.bootstrap-datetimepicker-widget :not(.today)[data-action="selectDay"]')); + await testUtils.dom.openDatepicker($datepicker); + await testUtils.dom.clickFirst($('.bootstrap-datetimepicker-widget :not(.today)[data-action="selectDay"]')); assert.notEqual(domainSelector.$(".o_datepicker:visible input").val(), val, "datepicker value should have changed"); domainSelector.destroy(); }); - QUnit.test("building a domain with a m2o without following the relation", function (assert) { + QUnit.test("building a domain with a m2o without following the relation", async function (assert) { assert.expect(1); var $target = $("#qunit-fixture"); @@ -187,16 +189,17 @@ QUnit.module('DomainSelector', { readonly: false, }); testUtils.mock.addMockEnvironment(domainSelector, {data: this.data}); - domainSelector.appendTo($target); + await domainSelector.appendTo($target); - testUtils.fields.editAndTrigger(domainSelector.$('.o_domain_leaf_value_input'), 'pad', ['input', 'change']); + await testUtils.fields.editAndTrigger(domainSelector.$('.o_domain_leaf_value_input'), + 'pad', ['input', 'change']); assert.strictEqual(domainSelector.$('.o_domain_debug_input').val(), '[["product_id","ilike","pad"]]', "string should have been allowed as m2o value"); domainSelector.destroy(); }); - QUnit.test("editing a domain with `parent` key", function (assert) { + QUnit.test("editing a domain with `parent` key", async function (assert) { assert.expect(1); var $target = $("#qunit-fixture"); @@ -207,7 +210,7 @@ QUnit.module('DomainSelector', { readonly: false, }); testUtils.mock.addMockEnvironment(domainSelector, {data: this.data}); - domainSelector.appendTo($target); + await domainSelector.appendTo($target); assert.strictEqual(domainSelector.$el.text(), "This domain is not supported.", "an error message should be displayed because of the `parent` key"); @@ -215,7 +218,7 @@ QUnit.module('DomainSelector', { domainSelector.destroy(); }); - QUnit.test("creating a domain with a default option", function (assert) { + QUnit.test("creating a domain with a default option", async function (assert) { assert.expect(1); var $target = $("#qunit-fixture"); @@ -227,11 +230,11 @@ QUnit.module('DomainSelector', { default: [["foo","=","kikou"]], }); testUtils.mock.addMockEnvironment(domainSelector, {data: this.data}); - domainSelector.appendTo($target); + await domainSelector.appendTo($target); // Clicking on the button should add a visible field selector in the // widget so that the user can change the field chain - testUtils.dom.click(domainSelector.$(".o_domain_add_first_node_button:visible")); + await testUtils.dom.click(domainSelector.$(".o_domain_add_first_node_button:visible")); assert.strictEqual( domainSelector.$(".o_domain_debug_input").val(), diff --git a/addons/web/static/tests/widgets/dropdown_menu_tests.js b/addons/web/static/tests/widgets/dropdown_menu_tests.js index 709651c7cb1..c632d1b7307 100644 --- a/addons/web/static/tests/widgets/dropdown_menu_tests.js +++ b/addons/web/static/tests/widgets/dropdown_menu_tests.js @@ -9,8 +9,9 @@ function createDropdownMenu(items, params) { var target = params.debug ? document.body : $('#qunit-fixture'); var menu = new DropdownMenu(null, items); testUtils.mock.addMockEnvironment(menu, params); - menu.appendTo(target); - return menu; + return menu.appendTo(target).then(function() { + return menu; + }); } QUnit.module('Web', { @@ -35,10 +36,10 @@ QUnit.module('Web', { }, function () { QUnit.module('DropdownMenu'); - QUnit.test('simple rendering', function (assert) { + QUnit.test('simple rendering', async function (assert) { assert.expect(4); - var dropdownMenu = createDropdownMenu(this.items); + var dropdownMenu = await createDropdownMenu(this.items); assert.containsN(dropdownMenu, '.dropdown-divider, .dropdown-item, .dropdown-item-text', 4, 'should have 4 elements counting the dividers'); assert.strictEqual(dropdownMenu.$('.o_menu_item').first().text().trim(), 'Some Item', @@ -51,11 +52,11 @@ QUnit.module('Web', { dropdownMenu.destroy(); }); - QUnit.test('click on an item without options should toggle it', function (assert) { + QUnit.test('click on an item without options should toggle it', async function (assert) { assert.expect(7); var self = this; - var dropdownMenu = createDropdownMenu(this.items, { + var dropdownMenu = await createDropdownMenu(this.items, { intercepts: { menu_item_clicked: function (ev) { assert.strictEqual(ev.data.id, 1); @@ -64,34 +65,34 @@ QUnit.module('Web', { }, }, }); - testUtils.dom.click(dropdownMenu.$('button:first')); + await testUtils.dom.click(dropdownMenu.$('button:first')); assert.doesNotHaveClass(dropdownMenu.$('.o_menu_item:first > .dropdown-item').first(), 'selected'); - testUtils.dom.click(dropdownMenu.$('.o_menu_item a').first()); + await testUtils.dom.click(dropdownMenu.$('.o_menu_item a').first()); assert.hasClass(dropdownMenu.$('.o_menu_item:first > .dropdown-item'), 'selected'); assert.ok(dropdownMenu.$('.o_menu_item:first').is(':visible'), 'item should still be visible'); - testUtils.dom.click(dropdownMenu.$('.o_menu_item a').first()); + await testUtils.dom.click(dropdownMenu.$('.o_menu_item a').first()); assert.doesNotHaveClass(dropdownMenu.$('.o_menu_item:first > .dropdown-item').first(), 'selected'); assert.ok(dropdownMenu.$('.o_menu_item:first').is(':visible'), 'item should still be visible'); dropdownMenu.destroy(); }); - QUnit.test('click on an item should not change url', function (assert) { + QUnit.test('click on an item should not change url', async function (assert) { assert.expect(0); - var dropdownMenu = createDropdownMenu(this.items); - testUtils.dom.click(dropdownMenu.$('.o_dropdown_toggler_btn')); + var dropdownMenu = await createDropdownMenu(this.items); + await testUtils.dom.click(dropdownMenu.$('.o_dropdown_toggler_btn')); dropdownMenu.$el.click(function () { // we do not want a click to get out and change the url, for example throw new Error('No click should get out of the dropdown menu'); }); - testUtils.dom.click(dropdownMenu.$('.o_menu_item a').first()); + await testUtils.dom.click(dropdownMenu.$('.o_menu_item a').first()); dropdownMenu.destroy(); }); - QUnit.test('options rendering', function (assert) { + QUnit.test('options rendering', async function (assert) { assert.expect(3); this.items[0].hasOptions = true; @@ -99,20 +100,20 @@ QUnit.module('Web', { {optionId: 1, description: "First Option", groupId: 1}, {optionId: 2, description: "Second Option", groupId: 1} ]; - var dropdownMenu = createDropdownMenu(this.items); + var dropdownMenu = await createDropdownMenu(this.items); testUtils.dom.click(dropdownMenu.$('button:first')); assert.containsN(dropdownMenu, '.dropdown-divider, .dropdown-item, .dropdown-item-text', 4); // open options menu - testUtils.dom.click(dropdownMenu.$('span.fa-caret-right')); + await testUtils.dom.click(dropdownMenu.$('span.fa-caret-right')); assert.containsN(dropdownMenu, '.dropdown-divider, .dropdown-item, .dropdown-item-text', 7); // close options menu - testUtils.dom.click(dropdownMenu.$('span.fa-caret-down')); + await testUtils.dom.click(dropdownMenu.$('span.fa-caret-down')); assert.containsN(dropdownMenu, '.dropdown-divider, .dropdown-item, .dropdown-item-text', 4); dropdownMenu.destroy(); }); - QUnit.test('close menu closes also submenus', function (assert) { + QUnit.test('close menu closes also submenus', async function (assert) { assert.expect(2); this.items[0].hasOptions = true; @@ -120,20 +121,20 @@ QUnit.module('Web', { {optionId: 1, description: "First Option"}, {optionId: 2, description: "Second Option"} ]; - var dropdownMenu = createDropdownMenu(this.items); + var dropdownMenu = await createDropdownMenu(this.items); // open dropdown menu testUtils.dom.click(dropdownMenu.$('button:first')); // open options menu - testUtils.dom.click(dropdownMenu.$('span.fa-caret-right')); + await testUtils.dom.click(dropdownMenu.$('span.fa-caret-right')); assert.containsN(dropdownMenu, '.dropdown-divider, .dropdown-item, .dropdown-item-text', 7); - testUtils.dom.click(dropdownMenu.$('button:first')); - testUtils.dom.click(dropdownMenu.$('button:first')); + await testUtils.dom.click(dropdownMenu.$('button:first')); + await testUtils.dom.click(dropdownMenu.$('button:first')); assert.containsN(dropdownMenu, '.dropdown-divider, .dropdown-item, .dropdown-item-text', 4); dropdownMenu.destroy(); }); - QUnit.test('click on an option should trigger the event "item_option_clicked" with appropriate data', function (assert) { + QUnit.test('click on an option should trigger the event "item_option_clicked" with appropriate data', async function (assert) { assert.expect(16); var self = this; @@ -143,7 +144,7 @@ QUnit.module('Web', { {optionId: 1, description: "First Option"}, {optionId: 2, description: "Second Option"} ]; - var dropdownMenu = createDropdownMenu(this.items, { + var dropdownMenu = await createDropdownMenu(this.items, { intercepts: { item_option_clicked: function (ev) { eventNumber++; @@ -166,22 +167,23 @@ QUnit.module('Web', { }, }, }); + await testUtils.nextTick(); // open dropdown menu - testUtils.dom.click(dropdownMenu.$('button:first')); + await testUtils.dom.click(dropdownMenu.$('button:first')); assert.containsN(dropdownMenu, '.dropdown-divider, .o_menu_item', 4); - testUtils.dom.clickFirst(dropdownMenu.$('.o_menu_item')); + await testUtils.dom.clickFirst(dropdownMenu.$('.o_menu_item')); // click on first option - testUtils.dom.click(dropdownMenu.$('.o_item_option > .dropdown-item').eq(0)); + await testUtils.dom.click(dropdownMenu.$('.o_item_option > .dropdown-item').eq(0)); assert.hasClass(dropdownMenu.$('.o_menu_item > .dropdown-item').first(), 'selected'); assert.hasClass(dropdownMenu.$('.o_item_option> .dropdown-item').eq(0),'selected'); assert.doesNotHaveClass(dropdownMenu.$('.o_item_option > .dropdown-item').eq(1), 'selected'); // click on second option - testUtils.dom.click($('.o_item_option > .dropdown-item').eq(1)); + await testUtils.dom.click($('.o_item_option > .dropdown-item').eq(1)); assert.hasClass(dropdownMenu.$('.o_menu_item > .dropdown-item').first(), 'selected'); assert.doesNotHaveClass(dropdownMenu.$('.o_item_option > .dropdown-item').eq(0), 'selected'); assert.hasClass(dropdownMenu.$('.o_item_option > .dropdown-item').eq(1), 'selected'); // click again on second option - testUtils.dom.click($('.o_item_option > .dropdown-item').eq(1)); + await testUtils.dom.click($('.o_item_option > .dropdown-item').eq(1)); assert.doesNotHaveClass(dropdownMenu.$('.o_menu_item > .dropdown-item').first(), 'selected'); assert.doesNotHaveClass(dropdownMenu.$('.o_item_option > .dropdown-item').eq(0), 'selected'); assert.doesNotHaveClass(dropdownMenu.$('.o_item_option > .dropdown-item').eq(1), 'selected'); diff --git a/addons/web/static/tests/widgets/model_field_selector_tests.js b/addons/web/static/tests/widgets/model_field_selector_tests.js index 2510912e678..76c18b91f5b 100644 --- a/addons/web/static/tests/widgets/model_field_selector_tests.js +++ b/addons/web/static/tests/widgets/model_field_selector_tests.js @@ -49,7 +49,7 @@ QUnit.module('ModelFieldSelector', { }, }, function () { - QUnit.test("creating a field chain from scratch", function (assert) { + QUnit.test("creating a field chain from scratch", async function (assert) { assert.expect(14); var $target = $("#qunit-fixture"); @@ -60,7 +60,7 @@ QUnit.module('ModelFieldSelector', { debugMode: true, }); testUtils.mock.addMockEnvironment(fieldSelector, {data: this.data}); - fieldSelector.appendTo($target); + await fieldSelector.appendTo($target); var $value = fieldSelector.$("> .o_field_selector_value"); // Focusing the field selector input should open a field selector popover @@ -84,7 +84,7 @@ QUnit.module('ModelFieldSelector', { // Clicking the "Bar" field should close the popover and set the field // chain to "bar" as it is a basic field - testUtils.dom.click($barLi); + await testUtils.dom.click($barLi); assert.notOk($fieldSelectorPopover.is("visible"), "field selector popover should be closed now"); assert.strictEqual(getValueFromDOM($value), "Bar", @@ -100,6 +100,7 @@ QUnit.module('ModelFieldSelector', { // Focusing the input again should open the same popover fieldSelector.$el.trigger('focusin'); + await testUtils.nextTick(); assert.ok($fieldSelectorPopover.is(":visible"), "field selector popover should be visible"); @@ -118,7 +119,7 @@ QUnit.module('ModelFieldSelector', { // Clicking on the "Product" field should update the popover to show // the product fields (so only "Product Name" should be there) - testUtils.dom.click($productLi); + await testUtils.dom.click($productLi); $lis = $fieldSelectorPopover.find("li"); assert.strictEqual($lis.length, 1, "there should be only one field proposition for 'product' model"); @@ -127,7 +128,7 @@ QUnit.module('ModelFieldSelector', { // Clicking on "Product Name" should close the popover and set the chain // to "product_id.name" - testUtils.dom.click($lis.first()); + await testUtils.dom.click($lis.first()); assert.notOk($fieldSelectorPopover.is("visible"), "field selector popover should be closed now"); assert.strictEqual(getValueFromDOM($value), "Product -> Product Name", @@ -135,10 +136,12 @@ QUnit.module('ModelFieldSelector', { // Remove the current selection and recreate it again fieldSelector.$el.trigger('focusin'); - testUtils.dom.click(fieldSelector.$('.o_field_selector_prev_page')); - testUtils.dom.click(fieldSelector.$('.o_field_selector_close')); + await testUtils.nextTick(); + await testUtils.dom.click(fieldSelector.$('.o_field_selector_prev_page')); + await testUtils.dom.click(fieldSelector.$('.o_field_selector_close')); fieldSelector.$el.trigger('focusin'); + await testUtils.nextTick(); $fieldSelectorPopover = fieldSelector.$(".o_field_selector_popover:visible"); $lis = $fieldSelectorPopover.find("li"); $productLi = $(); @@ -151,9 +154,9 @@ QUnit.module('ModelFieldSelector', { assert.strictEqual($productLi.length, 1, "field selector popover should contain the 'Product' field"); - testUtils.dom.click($productLi); + await testUtils.dom.click($productLi); $lis = $fieldSelectorPopover.find("li"); - testUtils.dom.click($lis.first()); + await testUtils.dom.click($lis.first()); assert.notOk($fieldSelectorPopover.is("visible"), "field selector popover should be closed now"); assert.strictEqual(getValueFromDOM($value), "Product -> Product Name", @@ -168,7 +171,7 @@ QUnit.module('ModelFieldSelector', { } }); - QUnit.test("use the filter option", function (assert) { + QUnit.test("use the filter option", async function (assert) { assert.expect(2); var $target = $("#qunit-fixture"); @@ -181,9 +184,10 @@ QUnit.module('ModelFieldSelector', { }, }); testUtils.mock.addMockEnvironment(fieldSelector, {data: this.data}); - fieldSelector.appendTo($target); + await fieldSelector.appendTo($target); fieldSelector.$el.trigger('focusin'); + await testUtils.nextTick(); var $fieldSelectorPopover = fieldSelector.$(".o_field_selector_popover:visible"); var $lis = $fieldSelectorPopover.find("li"); assert.strictEqual($lis.length, 1, "there should only be one element"); @@ -192,7 +196,7 @@ QUnit.module('ModelFieldSelector', { fieldSelector.destroy(); }); - QUnit.test("default `showSearchInput` option", function (assert) { + QUnit.test("default `showSearchInput` option", async function (assert) { assert.expect(6); var $target = $("#qunit-fixture"); @@ -202,9 +206,10 @@ QUnit.module('ModelFieldSelector', { readonly: false, }); testUtils.mock.addMockEnvironment(fieldSelector, {data: this.data}); - fieldSelector.appendTo($target); + await fieldSelector.appendTo($target); fieldSelector.$el.trigger('focusin'); + await testUtils.nextTick(); var $fieldSelectorPopover = fieldSelector.$(".o_field_selector_popover:visible"); var $searchInput = $fieldSelectorPopover.find(".o_field_selector_search input"); assert.strictEqual($searchInput.length, 1, "there should be a search input"); @@ -212,17 +217,17 @@ QUnit.module('ModelFieldSelector', { // without search assert.strictEqual($fieldSelectorPopover.find("li").length, 3, "there should be three available fields"); assert.strictEqual($fieldSelectorPopover.find("li").text().trim().replace(/\s+/g, ' '), "Bar Foo Product", "the available field should be correct"); - $searchInput.val('xx').trigger('keyup'); + await testUtils.fields.editAndTrigger($searchInput, 'xx', 'keyup'); assert.strictEqual($fieldSelectorPopover.find("li").length, 0, "there shouldn't be any element"); - $searchInput.val('Pro').trigger('keyup'); + await testUtils.fields.editAndTrigger($searchInput, 'Pro', 'keyup'); assert.strictEqual($fieldSelectorPopover.find("li").length, 1, "there should only be one element"); assert.strictEqual($fieldSelectorPopover.find("li").text().trim().replace(/\s+/g, ' '), "Product", "the available field should be the Product"); fieldSelector.destroy(); }); - QUnit.test("false `showSearchInput` option", function (assert) { + QUnit.test("false `showSearchInput` option", async function (assert) { assert.expect(1); var $target = $("#qunit-fixture"); @@ -233,9 +238,10 @@ QUnit.module('ModelFieldSelector', { showSearchInput: false, }); testUtils.mock.addMockEnvironment(fieldSelector, { data: this.data }); - fieldSelector.appendTo($target); + await fieldSelector.appendTo($target); fieldSelector.$el.trigger('focusin'); + await testUtils.nextTick(); var $fieldSelectorPopover = fieldSelector.$(".o_field_selector_popover:visible"); var $searchInput = $fieldSelectorPopover.find(".o_field_selector_search input"); assert.strictEqual($searchInput.length, 0, "there should be no search input"); |
