diff options
| author | Christophe Simonis <chs@odoo.com> | 2017-06-09 17:11:47 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2017-06-09 17:11:47 +0200 |
| commit | ab02bf34b0c1d3fa3752ca1f50b098d04dc5b137 (patch) | |
| tree | a29591cae8777390cbeb35d65e23c9f8ae143135 /addons/web/static/tests | |
| parent | b350c29e44b4ed1df0c428308c396771b22d73e8 (diff) | |
| parent | cc38b93ab2a6daf9c5badbc387cff20d1a01ddee (diff) | |
[MERGE] forward port branch saas-16 up to cc38b93ab2a
Diffstat (limited to 'addons/web/static/tests')
| -rw-r--r-- | addons/web/static/tests/fields/relational_fields_tests.js | 286 | ||||
| -rw-r--r-- | addons/web/static/tests/helpers/test_utils.js | 73 | ||||
| -rw-r--r-- | addons/web/static/tests/views/basic_model_tests.js | 49 | ||||
| -rw-r--r-- | addons/web/static/tests/views/form_tests.js | 97 | ||||
| -rw-r--r-- | addons/web/static/tests/views/list_tests.js | 23 |
5 files changed, 493 insertions, 35 deletions
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index 4f326e14f98..127d5761fd2 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -482,6 +482,183 @@ QUnit.module('relational_fields', { // assert.strictEqual(form.$('input').eq(1).val(), 'xphone', "onchange should have been applied"); // }); + QUnit.test('form: quick create then save directly', function (assert) { + var done = assert.async(); + assert.expect(5); + + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + + var def = $.Deferred(); + var newRecordID; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form>' + + '<field name="trululu"/>' + + '</form>', + mockRPC: function (route, args) { + var result = this._super.apply(this, arguments); + if (args.method === 'name_create') { + assert.step('name_create'); + return def.then(_.constant(result)).then(function (nameGet) { + newRecordID = nameGet[0]; + return nameGet; + }); + } + if (args.method === 'create') { + assert.step('create'); + assert.strictEqual(args.args[0].trululu, newRecordID, + "should create with the correct m2o id"); + } + return result; + }, + }); + + var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); + form.$('.o_field_many2one input').val('b').trigger('keydown'); + concurrency.delay(0).then(function () { + $dropdown.find('li:first()').click(); // quick create 'b' + form.$buttons.find('.o_form_button_save').click(); + + assert.verifySteps(['name_create'], + "should wait for the name_create before creating the record"); + + def.resolve(); + + assert.verifySteps(['name_create', 'create']); + + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + form.destroy(); + done(); + }); + }); + + QUnit.test('list: quick create then save directly', function (assert) { + var done = assert.async(); + assert.expect(8); + + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + + var def = $.Deferred(); + var newRecordID; + var list = createView({ + View: ListView, + model: 'partner', + data: this.data, + arch: '<tree editable="top">' + + '<field name="trululu"/>' + + '</tree>', + mockRPC: function (route, args) { + var result = this._super.apply(this, arguments); + if (args.method === 'name_create') { + assert.step('name_create'); + return def.then(_.constant(result)).then(function (nameGet) { + newRecordID = nameGet[0]; + return nameGet; + }); + } + if (args.method === 'create') { + assert.step('create'); + assert.strictEqual(args.args[0].trululu, newRecordID, + "should create with the correct m2o id"); + } + return result; + }, + }); + + list.$buttons.find('.o_list_button_add').click(); + + var $dropdown = list.$('.o_field_many2one input').autocomplete('widget'); + list.$('.o_field_many2one input').val('b').trigger('keydown'); + concurrency.delay(0).then(function () { + $dropdown.find('li:first()').click(); // quick create 'b' + list.$buttons.find('.o_list_button_add').click(); + + assert.verifySteps(['name_create'], + "should wait for the name_create before creating the record"); + assert.strictEqual(list.$('.o_data_row').length, 4, + "should wait for the name_create before adding the new row"); + + def.resolve(); + + assert.verifySteps(['name_create', 'create']); + assert.strictEqual(list.$('.o_data_row:nth(1) .o_data_cell').text(), 'b', + "created row should have the correct m2o value"); + assert.strictEqual(list.$('.o_data_row').length, 5, + "should have added the fifth row"); + + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + list.destroy(); + done(); + }); + }); + + QUnit.test('list in form: quick create then save directly', function (assert) { + var done = assert.async(); + assert.expect(6); + + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + + var def = $.Deferred(); + var newRecordID; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form>' + + '<sheet>' + + '<field name="p">' + + '<tree editable="bottom">' + + '<field name="trululu"/>' + + '</tree>' + + '</field>' + + '</sheet>' + + '</form>', + mockRPC: function (route, args) { + var result = this._super.apply(this, arguments); + if (args.method === 'name_create') { + assert.step('name_create'); + return def.then(_.constant(result)).then(function (nameGet) { + newRecordID = nameGet[0]; + return nameGet; + }); + } + if (args.method === 'create') { + assert.step('create'); + assert.strictEqual(args.args[0].p[0][2].trululu, newRecordID, + "should create with the correct m2o id"); + } + return result; + }, + }); + + form.$('.o_field_x2many_list_row_add a').click(); + + var $dropdown = form.$('.o_field_many2one input').autocomplete('widget'); + form.$('.o_field_many2one input').val('b').trigger('keydown'); + concurrency.delay(0).then(function () { + $dropdown.find('li:first()').click(); // quick create 'b' + form.$buttons.find('.o_form_button_save').click(); + + assert.verifySteps(['name_create'], + "should wait for the name_create before creating the record"); + + def.resolve(); + + assert.verifySteps(['name_create', 'create']); + assert.strictEqual(form.$('.o_data_row:first .o_data_cell').text(), 'b', + "first row should have the correct m2o value"); + + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + form.destroy(); + done(); + }); + }); + QUnit.test('autocompletion in a many2one, in form view with a domain', function (assert) { assert.expect(1); @@ -3852,6 +4029,115 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('editable list: value reset by an onchange', 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 + // overrides the value of the one2many (commands 5 and 0). The handler + // that switches the edited row to readonly is then called after the + // new value of the one2many field is applied (the one returned by the + // onchange), so the row that must go to readonly doesn't exist anymore. + assert.expect(2); + + this.data.partner.onchanges = { + datetime: function (obj) { + obj.turtles = [[5], [0, 0, {display_name: 'new'}]]; + }, + }; + + var def; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<field name="datetime"/>' + + '<field name="turtles">' + + '<tree editable="bottom">' + + '<field name="display_name"/>' + + '</tree>' + + '</field>' + + '</form>', + mockRPC: function (route, args) { + var result = this._super.apply(this, arguments); + if (args.method === 'onchange') { + return $.when(def).then(_.constant(result)); + } + return result; + }, + }); + + // trigger the two onchanges + form.$('.o_field_x2many_list_row_add a').click(); + form.$('.o_data_row .o_field_widget').val('a name').trigger('input'); + def = $.Deferred(); + form.$('.o_datepicker_input').click(); // focusout o2m and set value to today + + // resolve the onchange def + def.resolve(); + + assert.strictEqual(form.$('.o_data_row').length, 1, + "should have one record in the o2m"); + assert.strictEqual(form.$('.o_data_row .o_data_cell').text(), 'new', + "should be the record created by the onchange"); + + form.destroy(); + }); + + QUnit.test('editable list: onchange that returns a warning', function (assert) { + assert.expect(5); + + this.data.turtle.onchanges = { + display_name: function () {}, + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<field name="turtles">' + + '<tree editable="bottom">' + + '<field name="display_name"/>' + + '</tree>' + + '</field>' + + '</form>', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'onchange') { + assert.step(args.method); + return $.when({ + value: {}, + warning: { + title: "Warning", + message: "You must first select a partner" + }, + }); + } + return this._super.apply(this, arguments); + }, + viewOptions: { + mode: 'edit', + }, + intercepts: { + warning: function () { + assert.step('warning'); + }, + }, + }); + + // add a line (this should trigger an onchange and a warning) + form.$('.o_field_x2many_list_row_add a').click(); + + // check if 'Add an item' still works (this should trigger an onchange + // and a warning again) + form.$('.o_field_x2many_list_row_add a').click(); + + assert.verifySteps(['onchange', 'warning', 'onchange', 'warning']); + + form.destroy(); + }); + QUnit.module('FieldMany2Many'); QUnit.test('many2many kanban: edition', function (assert) { diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js index eae31e68e80..bc2cf05f360 100644 --- a/addons/web/static/tests/helpers/test_utils.js +++ b/addons/web/static/tests/helpers/test_utils.js @@ -13,6 +13,7 @@ odoo.define('web.test_utils', function (require) { var basic_fields = require('web.basic_fields'); var config = require('web.config'); var core = require('web.core'); +var dom = require('web.dom'); var session = require('web.session'); var MockServer = require('web.MockServer'); var Widget = require('web.Widget'); @@ -172,7 +173,14 @@ function createAsyncView(params) { widget.destroy(); $('#qunit-fixture').off('DOMNodeInserted.removeSRC'); }; - return view.appendTo($view_manager).then(function () { + // render the view in a fragment as they must be able to render correctly + // without being in the DOM + var fragment = document.createDocumentFragment(); + return view.appendTo(fragment).then(function () { + dom.append($view_manager, fragment, { + callbacks: [{widget: view}], + in_DOM: true, + }); view.$el.on('click', 'a', function (ev) { ev.preventDefault(); }); @@ -244,52 +252,53 @@ function addMockEnvironment(widget, params) { logLevel: params.logLevel, currentDate: params.currentDate, }); - var widgetDestroy; - // make sure the debounce value for input fields is set to 0 var initialDebounce = DebouncedField.prototype.DEBOUNCE; DebouncedField.prototype.DEBOUNCE = params.fieldDebounce || 0; - + var initialSession, initialConfig, initialParameters; if ('session' in params) { - var initialSession = _.extend({}, session); + initialSession = _.extend({}, session); _.extend(session, params.session); - widgetDestroy = widget.destroy; - widget.destroy = function () { - for (var key in session) { - delete session[key]; - } - _.extend(session, initialSession); - widgetDestroy.call(this); - }; } - if ('config' in params) { - var initialConfig = _.extend({}, config); + initialConfig = _.extend({}, config); _.extend(config, params.config); - widgetDestroy = widget.destroy; - widget.destroy = function () { - for (var key in config) { - delete config[key]; - } - _.extend(config, initialConfig); - DebouncedField.prototype.DEBOUNCE = initialDebounce; - widgetDestroy.call(this); - }; } - if ('translateParameters' in params) { - var initialParameters = _.extend({}, core._t.database.parameters); + initialParameters = _.extend({}, core._t.database.parameters); _.extend(core._t.database.parameters, params.translateParameters); - var oldDestroy = widget.destroy; - widget.destroy = function () { - for (var key in core._t.database.parameters) { + } + + var widgetDestroy = widget.destroy; + widget.destroy = function () { + // clear the caches (e.g. data_manager, ModelFieldSelector) when the + // widget is destroyed, at the end of each test to avoid collisions + core.bus.trigger('clear_cache'); + + DebouncedField.prototype.DEBOUNCE = initialDebounce; + + var key; + if ('session' in params) { + for (key in session) { + delete session[key]; + } + _.extend(session, initialSession); + } + if ('config' in params) { + for (key in config) { + delete config[key]; + } + _.extend(config, initialConfig); + } + if ('translateParameters' in params) { + for (key in core._t.database.parameters) { delete core._t.database.parameters[key]; } _.extend(core._t.database.parameters, initialParameters); - oldDestroy.call(this); - }; - } + } + widgetDestroy.call(this); + }; intercept(widget, 'call_service', function (event) { if (event.data.service === 'ajax') { diff --git a/addons/web/static/tests/views/basic_model_tests.js b/addons/web/static/tests/views/basic_model_tests.js index 582e06ba62b..fefc7231e5b 100644 --- a/addons/web/static/tests/views/basic_model_tests.js +++ b/addons/web/static/tests/views/basic_model_tests.js @@ -1165,6 +1165,54 @@ QUnit.module('Views', { model.destroy(); }); + QUnit.test('default_get: fetch x2manys inside x2manys', function (assert) { + assert.expect(3); + + this.data.partner.fields.o2m = { + string: "O2M", type: 'one2many', relation: 'partner', default: [[6, 0, [1]]], + }; + + 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: {} }, + }, + }, + }, + viewType: 'list', + }, + }, + }, + modelName: 'partner', + type: 'record', + viewType: 'form', + }; + + model.load(params).then(function (resultID) { + 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; + assert.strictEqual(categoryList.count, 1, + "category field should contain 1 record"); + assert.strictEqual(categoryList.data[0].data.display_name, + 'gold', "category records should have been fetched"); + }); + + model.destroy(); + }); + QUnit.test('contexts and domains can be properly fetched', function (assert) { assert.expect(8); @@ -1448,6 +1496,7 @@ QUnit.module('Views', { fieldsInfo: { form: { product_ids: { + relatedFields: this.data.product.fields, fieldsInfo: { list: { name: {}, date: {} } }, viewType: 'list', } diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index a07f022eb74..3f829a654b7 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -837,6 +837,28 @@ QUnit.module('Views', { form.destroy(); }); + QUnit.test('invisible attrs on separators', function (assert) { + assert.expect(1); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<sheet>' + + '<group>' + + '<separator string="Geolocation" attrs=\'{"invisible": [["bar", "=", True]]}\'/>'+ + '<field name="bar"/>' + + '</group>' + + '</sheet>' + + '</form>', + res_id: 1, + }); + assert.strictEqual(form.$('div.o_horizontal_separator').hasClass('o_invisible_modifier'), true, + "separator div should be hidden"); + form.destroy(); + }); + QUnit.test('buttons in form view', function (assert) { assert.expect(7); @@ -3770,7 +3792,7 @@ QUnit.module('Views', { }); form.$buttons.find('.o_form_button_edit').click(); - assert.strictEqual(form.$('input[name="foo"]')[0], document.activeElement, + assert.strictEqual(document.activeElement, form.$('input[name="foo"]')[0], "foo field should have focus"); assert.strictEqual(form.$('input[name="foo"]')[0].selectionStart, 3, "cursor should be at the end"); @@ -3792,7 +3814,25 @@ QUnit.module('Views', { res_id: 1, }); form.$buttons.find('.o_form_button_edit').click(); - assert.strictEqual(form.$('input[name="foo"]')[0], document.activeElement, + 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) { + assert.expect(1); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<field name="int_field"/>' + + '<field name="foo" default_focus="1"/>' + + '</form>', + }); + assert.strictEqual(document.activeElement, form.$('input[name="foo"]')[0], "foo field should have focus"); form.destroy(); @@ -3821,6 +3861,24 @@ QUnit.module('Views', { form.destroy(); }); + QUnit.test('autofocus first visible field', function (assert) { + assert.expect(1); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<field name="int_field" invisible="1"/>' + + '<field name="foo"/>' + + '</form>', + }); + assert.strictEqual(document.activeElement, form.$('input[name="foo"]')[0], + "foo field should have focus"); + + form.destroy(); + }); + QUnit.test('open one2many form containing many2many_tags', function (assert) { assert.expect(4); @@ -4893,5 +4951,40 @@ QUnit.module('Views', { form.destroy(); }); + QUnit.test('wow effect', function (assert) { + assert.expect(3); + + this.data.partner.fields.model_name = { string: "Model name", type: "char" }; + + var clickCount = 0; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<button name="test" string="Mark Won" type="object" class="o_wow"/>' + + '</form>', + intercepts: { + execute_action: function (event) { + if (clickCount === 0) { + event.data.on_success(); + } else { + event.data.on_fail(); + } + clickCount++; + }, + show_wow: function () { + assert.step('wow'); + }, + }, + }); + + form.$('button.o_wow').click(); + assert.verifySteps(['wow']); + form.$('button.o_wow').click(); + assert.verifySteps(['wow']); + + form.destroy(); + }); }); }); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index e1cfef2b23b..332ba6687f9 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -84,7 +84,7 @@ QUnit.module('Views', { QUnit.module('ListView'); QUnit.test('simple readonly list', function (assert) { - assert.expect(9); + assert.expect(10); var list = createView({ View: ListView, @@ -93,6 +93,9 @@ QUnit.module('Views', { arch: '<tree><field name="foo"/><field name="int_field"/></tree>', }); + assert.notOk(list.$el.hasClass('o_cannot_create'), + "should not have className 'o_cannot_create'"); + // 3 th (1 for checkbox, 2 for columns) assert.strictEqual(list.$('th').length, 3, "should have 3 columns"); @@ -114,6 +117,24 @@ QUnit.module('Views', { list.destroy(); }); + QUnit.test('list with create="0"', function (assert) { + assert.expect(2); + + var list = createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '<tree create="0"><field name="foo"/></tree>', + }); + + assert.ok(list.$el.hasClass('o_cannot_create'), + "should have className 'o_cannot_create'"); + assert.strictEqual(list.$buttons.find('.o_list_button_add').length, 0, + "should not have the 'Create' button"); + + list.destroy(); + }); + QUnit.test('simple editable rendering', function (assert) { assert.expect(9); |
