summaryrefslogtreecommitdiff
path: root/addons/web/static/tests
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2017-06-09 17:11:47 +0200
committerChristophe Simonis <chs@odoo.com>2017-06-09 17:11:47 +0200
commitab02bf34b0c1d3fa3752ca1f50b098d04dc5b137 (patch)
treea29591cae8777390cbeb35d65e23c9f8ae143135 /addons/web/static/tests
parentb350c29e44b4ed1df0c428308c396771b22d73e8 (diff)
parentcc38b93ab2a6daf9c5badbc387cff20d1a01ddee (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.js286
-rw-r--r--addons/web/static/tests/helpers/test_utils.js73
-rw-r--r--addons/web/static/tests/views/basic_model_tests.js49
-rw-r--r--addons/web/static/tests/views/form_tests.js97
-rw-r--r--addons/web/static/tests/views/list_tests.js23
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);