When the adapation without jQuery promise was done for 12.3 (inbfed574255) the hack for always saving a pad URL was added in the deferrence of the start method. This caused that in the following conditions: - pad URL is set - form is in edit mode - a change in the form change the readonly status of the pad => we would have a mutex lock (of the form change) waiting for the pad that is being rerendered to finish its start but that can only be done once the mutex is unlocked (because setValue of the pad is protected by the same mutex) => so we have deadlock and interface does not allow to save or do any other change. This happened for example if we had a project.project A without collaborative pad, project.project B with collaborative pad, and if we moved a task from project B to project A then back to project B. With this change, we get back to the behavior beforebfed57425of not waiting for the fake "setValue" in `start` of Pad. Without the change, added test fails with: Expected 1 assertions, but 0 were run because interface is deadlocked so write does not happen. opw-2150827 closes #41346 closes odoo/odoo#41406 X-original-commit: cc73abb218e17756b21824a192f4461eb8537c0e Signed-off-by: Nicolas Lempereur (nle) <nle@odoo.com>
311 lines
11 KiB
JavaScript
311 lines
11 KiB
JavaScript
odoo.define('pad.pad_tests', function (require) {
|
|
"use strict";
|
|
|
|
var FieldPad = require('pad.pad');
|
|
var FormView = require('web.FormView');
|
|
var testUtils = require('web.test_utils');
|
|
|
|
var createView = testUtils.createView;
|
|
|
|
QUnit.module('pad widget', {
|
|
beforeEach: function () {
|
|
this.data = {
|
|
task: {
|
|
fields: {
|
|
description: {string: "Description", type: "char"},
|
|
use_pad: {string: "Use pad", type: "boolean"},
|
|
},
|
|
records: [
|
|
{id: 1, description: false},
|
|
{id: 2, description: "https://pad.odoo.pad/p/test-03AK6RCJT"},
|
|
],
|
|
pad_is_configured: function () {
|
|
return true;
|
|
},
|
|
pad_generate_url: function (route, args) {
|
|
return {
|
|
url:'https://pad.odoo.pad/p/test/' + args.context.object_id
|
|
};
|
|
},
|
|
pad_get_content: function () {
|
|
return "we should rewrite this server in haskell";
|
|
},
|
|
},
|
|
};
|
|
},
|
|
});
|
|
|
|
QUnit.test('pad widget display help if server not configured', async function (assert) {
|
|
assert.expect(4);
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'task',
|
|
data: this.data,
|
|
arch:'<form>' +
|
|
'<sheet>' +
|
|
'<group>' +
|
|
'<field name="description" widget="pad"/>' +
|
|
'</group>' +
|
|
'</sheet>' +
|
|
'</form>',
|
|
res_id: 1,
|
|
mockRPC: function (route, args) {
|
|
if (args.method === 'pad_is_configured') {
|
|
return Promise.resolve(false);
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
});
|
|
assert.isVisible(form.$('p.oe_unconfigured'),
|
|
"help message should be visible");
|
|
assert.containsNone(form, 'p.oe_pad_content',
|
|
"content should not be displayed");
|
|
await testUtils.form.clickEdit(form);
|
|
assert.isVisible(form.$('p.oe_unconfigured'),
|
|
"help message should be visible");
|
|
assert.containsNone(form, 'p.oe_pad_content',
|
|
"content should not be displayed");
|
|
form.destroy();
|
|
delete FieldPad.prototype.isPadConfigured;
|
|
});
|
|
|
|
QUnit.test('pad widget works, basic case', async function (assert) {
|
|
assert.expect(5);
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'task',
|
|
data: this.data,
|
|
arch:'<form>' +
|
|
'<sheet>' +
|
|
'<group>' +
|
|
'<field name="description" widget="pad"/>' +
|
|
'</group>' +
|
|
'</sheet>' +
|
|
'</form>',
|
|
res_id: 1,
|
|
mockRPC: function (route, args) {
|
|
if (route === 'https://pad.odoo.pad/p/test/1?showChat=false&userName=batman') {
|
|
assert.ok(true, "should have an iframe with correct src");
|
|
return Promise.resolve(true);
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
session: {
|
|
userName: "batman",
|
|
},
|
|
});
|
|
assert.isNotVisible(form.$('p.oe_unconfigured'),
|
|
"help message should not be visible");
|
|
assert.isVisible(form.$('.oe_pad_content'),
|
|
"content should be visible");
|
|
assert.containsOnce(form, '.oe_pad_content:contains(This pad will be)',
|
|
"content should display a message when not initialized");
|
|
|
|
await testUtils.form.clickEdit(form);
|
|
|
|
assert.containsOnce(form, '.oe_pad_content iframe',
|
|
"should have an iframe");
|
|
|
|
form.destroy();
|
|
delete FieldPad.prototype.isPadConfigured;
|
|
});
|
|
|
|
QUnit.test('pad widget works, with existing data', async function (assert) {
|
|
assert.expect(3);
|
|
|
|
var contentDef = testUtils.makeTestPromise();
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'task',
|
|
data: this.data,
|
|
arch:'<form>' +
|
|
'<sheet>' +
|
|
'<group>' +
|
|
'<field name="description" widget="pad"/>' +
|
|
'</group>' +
|
|
'</sheet>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
mockRPC: function (route, args) {
|
|
if (_.str.startsWith(route, 'http')) {
|
|
return Promise.resolve(true);
|
|
}
|
|
var result = this._super.apply(this, arguments);
|
|
if (args.method === 'pad_get_content') {
|
|
return contentDef.then(_.constant(result));
|
|
}
|
|
if (args.method === 'write') {
|
|
assert.ok('description' in args.args[1],
|
|
"should always send the description value");
|
|
}
|
|
return result;
|
|
},
|
|
session: {
|
|
userName: "batman",
|
|
},
|
|
});
|
|
assert.strictEqual(form.$('.oe_pad_content').text(), "Loading",
|
|
"should display loading message");
|
|
contentDef.resolve();
|
|
await testUtils.nextTick();
|
|
assert.strictEqual(form.$('.oe_pad_content').text(), "we should rewrite this server in haskell",
|
|
"should display proper value");
|
|
|
|
await testUtils.form.clickEdit(form);
|
|
await testUtils.form.clickSave(form);
|
|
form.destroy();
|
|
delete FieldPad.prototype.isPadConfigured;
|
|
});
|
|
|
|
QUnit.test('pad widget is not considered dirty at creation', async function (assert) {
|
|
assert.expect(2);
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'task',
|
|
data: this.data,
|
|
arch:'<form>' +
|
|
'<sheet>' +
|
|
'<group>' +
|
|
'<field name="description" widget="pad"/>' +
|
|
'</group>' +
|
|
'</sheet>' +
|
|
'</form>',
|
|
mockRPC: function (route, args) {
|
|
if (!args.method) {
|
|
return Promise.resolve(true);
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
session: {
|
|
userName: "batman",
|
|
},
|
|
});
|
|
var def = form.canBeDiscarded();
|
|
var defState = 'unresolved';
|
|
def.then(function () {
|
|
defState = 'resolved';
|
|
});
|
|
|
|
assert.strictEqual($('.modal').length, 0,
|
|
"should have no confirmation modal opened");
|
|
await testUtils.nextTick();
|
|
assert.strictEqual(defState, 'resolved',
|
|
"can be discarded was successfully resolved");
|
|
form.destroy();
|
|
delete FieldPad.prototype.isPadConfigured;
|
|
});
|
|
|
|
QUnit.test('pad widget is not considered dirty at edition', async function (assert) {
|
|
assert.expect(2);
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'task',
|
|
data: this.data,
|
|
arch:'<form>' +
|
|
'<sheet>' +
|
|
'<group>' +
|
|
'<field name="description" widget="pad"/>' +
|
|
'</group>' +
|
|
'</sheet>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
mockRPC: function (route, args) {
|
|
if (!args.method) {
|
|
return Promise.resolve(true);
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
session: {
|
|
userName: "batman",
|
|
},
|
|
});
|
|
await testUtils.form.clickEdit(form);
|
|
var def = form.canBeDiscarded();
|
|
var defState = 'unresolved';
|
|
def.then(function () {
|
|
defState = 'resolved';
|
|
});
|
|
|
|
assert.strictEqual($('.modal').length, 0,
|
|
"should have no confirmation modal opened");
|
|
await testUtils.nextTick();
|
|
assert.strictEqual(defState, 'resolved',
|
|
"can be discarded was successfully resolved");
|
|
form.destroy();
|
|
delete FieldPad.prototype.isPadConfigured;
|
|
});
|
|
|
|
QUnit.test('record should be discarded properly even if only pad has changed', async function (assert) {
|
|
assert.expect(1);
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'task',
|
|
data: this.data,
|
|
arch:'<form>' +
|
|
'<sheet>' +
|
|
'<group>' +
|
|
'<field name="description" widget="pad"/>' +
|
|
'</group>' +
|
|
'</sheet>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
mockRPC: function (route, args) {
|
|
if (!args.method) {
|
|
return Promise.resolve(true);
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
session: {
|
|
userName: "batman",
|
|
},
|
|
});
|
|
await testUtils.form.clickEdit(form);
|
|
await testUtils.form.clickDiscard(form);
|
|
assert.strictEqual(form.$('.oe_pad_readonly').text(), this.data.task.pad_get_content(),
|
|
"pad content should not have changed");
|
|
form.destroy();
|
|
delete FieldPad.prototype.isPadConfigured;
|
|
});
|
|
|
|
QUnit.test('no pad deadlock on form change modifying pad readonly modifier', async function (assert) {
|
|
assert.expect(1);
|
|
|
|
var form = await createView({
|
|
View: FormView,
|
|
model: 'task',
|
|
data: this.data,
|
|
arch:'<form>' +
|
|
'<sheet>' +
|
|
'<group>' +
|
|
'<field name="use_pad" widget="toggle_button"/>' +
|
|
'<field name="description" widget="pad" attrs="{\'readonly\': [(\'use_pad\', \'=\', False)]}"/>' +
|
|
'</group>' +
|
|
'</sheet>' +
|
|
'</form>',
|
|
res_id: 2,
|
|
mockRPC: function (route, args) {
|
|
if (!args.method) {
|
|
return Promise.resolve(true);
|
|
}
|
|
if (args.method === "write") {
|
|
assert.strictEqual(args.args[1].description,
|
|
"https://pad.odoo.pad/p/test-03AK6RCJT");
|
|
}
|
|
return this._super.apply(this, arguments);
|
|
},
|
|
});
|
|
await testUtils.form.clickEdit(form);
|
|
await testUtils.dom.click(form.$('.o_field_widget[name="use_pad"]'));
|
|
await testUtils.form.clickSave(form);
|
|
form.destroy();
|
|
delete FieldPad.prototype.isPadConfigured;
|
|
});
|
|
|
|
});
|