mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-19 08:12:01 +02:00
Initial commit: Core packages
This commit is contained in:
commit
12c29a983b
9512 changed files with 8379910 additions and 0 deletions
|
|
@ -0,0 +1,172 @@
|
|||
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");
|
||||
const AbstractRenderer = require('web.AbstractRendererOwl');
|
||||
const RendererWrapper = require('web.RendererWrapper');
|
||||
|
||||
const { xml, onMounted, onWillUnmount, onWillDestroy } = require("@odoo/owl");
|
||||
|
||||
function getHtmlRenderer(html) {
|
||||
return BasicRenderer.extend({
|
||||
start: function () {
|
||||
this.$el.html(html);
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getOwlView(owlRenderer, viewType) {
|
||||
viewType = viewType || "test";
|
||||
return BasicView.extend({
|
||||
viewType: viewType,
|
||||
config: _.extend({}, BasicView.prototype.config, {
|
||||
Renderer: owlRenderer,
|
||||
}),
|
||||
getRenderer() {
|
||||
return new RendererWrapper(null, this.config.Renderer, {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getHtmlView(html, viewType) {
|
||||
viewType = viewType || "test";
|
||||
return BasicView.extend({
|
||||
viewType: viewType,
|
||||
config: _.extend({}, BasicView.prototype.config, {
|
||||
Renderer: getHtmlRenderer(html)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
QUnit.module("LegacyViews", {
|
||||
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', async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
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>";
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
QUnit.test('OWL Renderer correctly destroyed', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
class Renderer extends AbstractRenderer {
|
||||
setup() {
|
||||
onWillDestroy(() => {
|
||||
assert.step("destroy");
|
||||
});
|
||||
}
|
||||
}
|
||||
Renderer.template = xml`<div>Test</div>`;
|
||||
|
||||
var view = await createView({
|
||||
View: getOwlView(Renderer, "test"),
|
||||
data: this.data,
|
||||
model: "test_model",
|
||||
arch: "<test/>",
|
||||
});
|
||||
view.destroy();
|
||||
|
||||
assert.verifySteps(["destroy"]);
|
||||
|
||||
});
|
||||
|
||||
QUnit.test('Correctly set focus to search panel with Owl Renderer', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
class Renderer extends AbstractRenderer { }
|
||||
Renderer.template = xml`<div>Test</div>`;
|
||||
|
||||
var view = await createView({
|
||||
View: getOwlView(Renderer, "test"),
|
||||
data: this.data,
|
||||
model: "test_model",
|
||||
arch: "<test/>",
|
||||
});
|
||||
assert.hasClass(document.activeElement, "o_searchview_input");
|
||||
view.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('Owl Renderer mounted/willUnmount hooks are properly called', async function (assert) {
|
||||
// This test could be removed as soon as controllers and renderers will
|
||||
// both be converted in Owl.
|
||||
assert.expect(3);
|
||||
|
||||
class Renderer extends AbstractRenderer {
|
||||
setup() {
|
||||
onMounted(() => {
|
||||
assert.step("mounted");
|
||||
});
|
||||
onWillUnmount(() => {
|
||||
assert.step("unmounted");
|
||||
});
|
||||
}
|
||||
}
|
||||
Renderer.template = xml`<div>Test</div>`;
|
||||
|
||||
const view = await createView({
|
||||
View: getOwlView(Renderer, "test"),
|
||||
data: this.data,
|
||||
model: "test_model",
|
||||
arch: "<test/>",
|
||||
});
|
||||
|
||||
view.destroy();
|
||||
|
||||
assert.verifySteps([
|
||||
"mounted",
|
||||
"unmounted",
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
odoo.define('web.abstract_model_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
const AbstractModel = require('web.AbstractModel');
|
||||
const Domain = require('web.Domain');
|
||||
|
||||
QUnit.module('LegacyViews', {}, function () {
|
||||
QUnit.module('AbstractModel');
|
||||
|
||||
QUnit.test('leave sample mode when unknown route is called on sample server', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const Model = AbstractModel.extend({
|
||||
_isEmpty() {
|
||||
return true;
|
||||
},
|
||||
async __load() {
|
||||
if (this.isSampleModel) {
|
||||
await this._rpc({ model: 'partner', method: 'unknown' });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const model = new Model(null, {
|
||||
modelName: 'partner',
|
||||
fields: {},
|
||||
useSampleModel: true,
|
||||
SampleModel: Model,
|
||||
});
|
||||
|
||||
assert.ok(model.useSampleModel);
|
||||
assert.notOk(model._isInSampleMode);
|
||||
|
||||
await model.load({});
|
||||
|
||||
assert.notOk(model.useSampleModel);
|
||||
assert.notOk(model._isInSampleMode);
|
||||
|
||||
model.destroy();
|
||||
});
|
||||
|
||||
QUnit.test("don't cath general error on sample server in sample mode", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
const error = new Error();
|
||||
|
||||
const Model = AbstractModel.extend({
|
||||
_isEmpty() {
|
||||
return true;
|
||||
},
|
||||
async __reload() {
|
||||
if (this.isSampleModel) {
|
||||
await this._rpc({ model: 'partner', method: 'read_group' });
|
||||
}
|
||||
},
|
||||
async _rpc() {
|
||||
throw error;
|
||||
},
|
||||
});
|
||||
|
||||
const model = new Model(null, {
|
||||
modelName: 'partner',
|
||||
fields: {},
|
||||
useSampleModel: true,
|
||||
SampleModel: Model,
|
||||
});
|
||||
|
||||
assert.ok(model.useSampleModel);
|
||||
assert.notOk(model._isInSampleMode);
|
||||
|
||||
await model.load({});
|
||||
|
||||
assert.ok(model.useSampleModel);
|
||||
assert.ok(model._isInSampleMode);
|
||||
|
||||
async function reloadModel() {
|
||||
try {
|
||||
await model.reload();
|
||||
} catch (e) {
|
||||
assert.strictEqual(e, error);
|
||||
}
|
||||
}
|
||||
|
||||
await reloadModel();
|
||||
|
||||
model.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('fetch sample data: concurrency', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const Model = AbstractModel.extend({
|
||||
_isEmpty() {
|
||||
return true;
|
||||
},
|
||||
__get() {
|
||||
return { isSample: !!this.isSampleModel };
|
||||
},
|
||||
});
|
||||
|
||||
const model = new Model(null, {
|
||||
modelName: 'partner',
|
||||
fields: {},
|
||||
useSampleModel: true,
|
||||
SampleModel: Model,
|
||||
});
|
||||
|
||||
await model.load({ domain: Domain.FALSE_DOMAIN, });
|
||||
|
||||
const beforeReload = model.get(null, { withSampleData: true });
|
||||
|
||||
const reloaded = model.reload(null, { domain: Domain.TRUE_DOMAIN });
|
||||
const duringReload = model.get(null, { withSampleData: true });
|
||||
|
||||
await reloaded;
|
||||
|
||||
const afterReload = model.get(null, { withSampleData: true });
|
||||
|
||||
assert.strictEqual(beforeReload.isSample, true,
|
||||
"Sample data flag must be true before reload"
|
||||
);
|
||||
assert.strictEqual(duringReload.isSample, true,
|
||||
"Sample data flag must be true during reload"
|
||||
);
|
||||
assert.strictEqual(afterReload.isSample, false,
|
||||
"Sample data flag must be true after reload"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
odoo.define('web.abstract_view_banner_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var AbstractRenderer = require('web.AbstractRenderer');
|
||||
var AbstractView = require('web.AbstractView');
|
||||
|
||||
var testUtils = require('web.test_utils');
|
||||
var createView = testUtils.createView;
|
||||
|
||||
var TestRenderer = AbstractRenderer.extend({
|
||||
_renderView: function () {
|
||||
this.$el.addClass('test_content');
|
||||
return this._super();
|
||||
},
|
||||
});
|
||||
|
||||
var TestView = AbstractView.extend({
|
||||
type: 'test',
|
||||
config: _.extend({}, AbstractView.prototype.config, {
|
||||
Renderer: TestRenderer
|
||||
}),
|
||||
});
|
||||
|
||||
var test_css_url = '/test_assetsbundle/static/src/css/test_cssfile1.css';
|
||||
|
||||
QUnit.module('LegacyViews', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
test_model: {
|
||||
fields: {},
|
||||
records: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
afterEach: function () {
|
||||
$('head link[href$="' + test_css_url + '"]').remove();
|
||||
}
|
||||
}, function () {
|
||||
QUnit.module('BasicRenderer');
|
||||
|
||||
QUnit.test("The banner should be fetched from the route", function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(6);
|
||||
|
||||
var banner_html =`
|
||||
<div class="modal o_onboarding_modal o_technical_modal" tabindex="-1" role="dialog"
|
||||
data-bs-backdrop="false">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-footer">
|
||||
<a type="action" class="btn btn-primary" data-bs-dismiss="modal"
|
||||
data-o-hide-banner="true">
|
||||
Remove
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_onboarding_container collapse show">
|
||||
<div class="o_onboarding_wrap">
|
||||
<a href="#" data-bs-toggle="modal" data-bs-target=".o_onboarding_modal"
|
||||
class="float-end o_onboarding_btn_close">
|
||||
<i class="fa fa-times" title="Close the onboarding panel" />
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<link type="text/css" href="` + test_css_url + `" rel="stylesheet">
|
||||
<div class="hello_banner">Here is the banner</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
createView({
|
||||
View: TestView,
|
||||
model: 'test_model',
|
||||
data: this.data,
|
||||
arch: '<test banner_route="/module/hello_banner"/>',
|
||||
mockRPC: function (route, args) {
|
||||
if (route === '/module/hello_banner') {
|
||||
assert.step(route);
|
||||
return Promise.resolve({html: banner_html});
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
}).then(async function (view) {
|
||||
var $banner = view.$('.hello_banner');
|
||||
assert.strictEqual($banner.length, 1,
|
||||
"The view should contain the response from the controller.");
|
||||
assert.verifySteps(['/module/hello_banner'], "The banner should be fetched.");
|
||||
|
||||
var $head_link = $('head link[href$="' + test_css_url + '"]');
|
||||
assert.strictEqual($head_link.length, 1,
|
||||
"The stylesheet should have been added to head.");
|
||||
|
||||
var $banner_link = $('link[href$="' + test_css_url + '"]', $banner);
|
||||
assert.strictEqual($banner_link.length, 0,
|
||||
"The stylesheet should have been removed from the banner.");
|
||||
|
||||
await testUtils.dom.click(view.$('.o_onboarding_btn_close')); // click on close to remove banner
|
||||
await testUtils.dom.click(view.$('.o_technical_modal .btn-primary:contains("Remove")')); // click on button remove from techinal modal
|
||||
assert.strictEqual(view.$('.o_onboarding_container.show').length, 0,
|
||||
"Banner should be removed from the view");
|
||||
|
||||
view.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
odoo.define('web.abstract_view_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
const { registry } = require('@web/core/registry');
|
||||
const legacyViewRegistry = require('web.view_registry');
|
||||
var ListView = require('web.ListView');
|
||||
|
||||
const { createWebClient, doAction } = require('@web/../tests/webclient/helpers');
|
||||
|
||||
QUnit.module('LegacyViews', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
fake_model: {
|
||||
fields: {},
|
||||
record: [],
|
||||
},
|
||||
foo: {
|
||||
fields: {
|
||||
foo: {string: "Foo", type: "char"},
|
||||
bar: {string: "Bar", type: "boolean"},
|
||||
},
|
||||
records: [
|
||||
{id: 1, bar: true, foo: "yop"},
|
||||
{id: 2, bar: true, foo: "blip"},
|
||||
]
|
||||
},
|
||||
};
|
||||
},
|
||||
}, function () {
|
||||
|
||||
QUnit.module('AbstractView');
|
||||
|
||||
QUnit.test('group_by from context can be a string, instead of a list of strings', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
registry.category("views").remove("list"); // remove new list from registry
|
||||
legacyViewRegistry.add("list", ListView); // add legacy list -> will be wrapped and added to new registry
|
||||
|
||||
const serverData = {
|
||||
actions: {
|
||||
1: {
|
||||
id: 1,
|
||||
name: 'Foo',
|
||||
res_model: 'foo',
|
||||
type: 'ir.actions.act_window',
|
||||
views: [[false, 'list']],
|
||||
context: {
|
||||
group_by: 'bar',
|
||||
},
|
||||
}
|
||||
},
|
||||
views: {
|
||||
'foo,false,list': '<tree><field name="foo"/><field name="bar"/></tree>',
|
||||
'foo,false,search': '<search></search>',
|
||||
},
|
||||
models: this.data
|
||||
};
|
||||
|
||||
const mockRPC = (route, args) => {
|
||||
if (args.method === 'web_read_group') {
|
||||
assert.deepEqual(args.kwargs.groupby, ['bar']);
|
||||
}
|
||||
};
|
||||
const webClient = await createWebClient({ serverData, mockRPC });
|
||||
await doAction(webClient, 1);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,80 @@
|
|||
odoo.define('web.basic_view_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
const BasicView = require('web.BasicView');
|
||||
const BasicRenderer = require("web.BasicRenderer");
|
||||
const testUtils = require('web.test_utils');
|
||||
const widgetRegistryOwl = require('web.widgetRegistry');
|
||||
const { LegacyComponent } = require("@web/legacy/legacy_component");
|
||||
|
||||
const { xml } = owl;
|
||||
|
||||
const createView = testUtils.createView;
|
||||
|
||||
QUnit.module('LegacyViews', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
fake_model: {
|
||||
fields: {},
|
||||
record: [],
|
||||
},
|
||||
foo: {
|
||||
fields: {
|
||||
foo: { string: "Foo", type: "char" },
|
||||
bar: { string: "Bar", type: "boolean" },
|
||||
},
|
||||
records: [
|
||||
{ id: 1, bar: true, foo: "yop" },
|
||||
{ id: 2, bar: true, foo: "blip" },
|
||||
]
|
||||
},
|
||||
};
|
||||
},
|
||||
}, function () {
|
||||
|
||||
QUnit.module('BasicView');
|
||||
|
||||
QUnit.test('fields given in fieldDependencies of custom widget are loaded', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const basicView = BasicView.extend({
|
||||
viewType: "test",
|
||||
config: Object.assign({}, BasicView.prototype.config, {
|
||||
Renderer: BasicRenderer,
|
||||
})
|
||||
});
|
||||
|
||||
class MyWidget extends LegacyComponent {}
|
||||
MyWidget.fieldDependencies = {
|
||||
foo: { type: 'char' },
|
||||
bar: { type: 'boolean' },
|
||||
};
|
||||
MyWidget.template = xml/* xml */`
|
||||
<div class="custom-widget">Hello World!</div>
|
||||
`;
|
||||
widgetRegistryOwl.add('testWidget', MyWidget);
|
||||
|
||||
const view = await createView({
|
||||
View: basicView,
|
||||
data: this.data,
|
||||
model: "foo",
|
||||
arch:
|
||||
`<test>
|
||||
<widget name="testWidget"/>
|
||||
</test>`,
|
||||
mockRPC: function (route, args) {
|
||||
if (route === "/web/dataset/search_read") {
|
||||
assert.deepEqual(args.fields, ["foo", "bar"],
|
||||
"search_read should be called with dependent fields");
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
|
||||
view.destroy();
|
||||
delete widgetRegistryOwl.map.testWidget;
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
/* global Benchmark */
|
||||
odoo.define('web.form_benchmarks', function (require) {
|
||||
"use strict";
|
||||
|
||||
const FormView = require('web.FormView');
|
||||
const testUtils = require('web.test_utils');
|
||||
|
||||
const { createView } = testUtils;
|
||||
|
||||
QUnit.module('Form View', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
foo: {
|
||||
fields: {
|
||||
foo: {string: "Foo", type: "char"},
|
||||
many2many: { string: "bar", type: "many2many", relation: 'bar'},
|
||||
},
|
||||
records: [
|
||||
{ id: 1, foo: "bar", many2many: []},
|
||||
],
|
||||
onchanges: {}
|
||||
},
|
||||
bar: {
|
||||
fields: {
|
||||
char: {string: "char", type: "char"},
|
||||
many2many: { string: "pokemon", type: "many2many", relation: 'pokemon'},
|
||||
},
|
||||
records: [],
|
||||
onchanges: {}
|
||||
},
|
||||
pokemon: {
|
||||
fields: {
|
||||
name: {string: "Name", type: "char"},
|
||||
},
|
||||
records: [],
|
||||
onchanges: {}
|
||||
},
|
||||
};
|
||||
this.arch = null;
|
||||
this.run = function (assert, viewParams, cb) {
|
||||
const data = this.data;
|
||||
const arch = this.arch;
|
||||
return new Promise(resolve => {
|
||||
new Benchmark.Suite({})
|
||||
.add('form', {
|
||||
defer: true,
|
||||
fn: async (deferred) => {
|
||||
const form = await createView(Object.assign({
|
||||
View: FormView,
|
||||
model: 'foo',
|
||||
data,
|
||||
arch,
|
||||
}, viewParams));
|
||||
if (cb) {
|
||||
await cb(form);
|
||||
}
|
||||
form.destroy();
|
||||
deferred.resolve();
|
||||
},
|
||||
})
|
||||
.on('cycle', event => {
|
||||
assert.ok(true, String(event.target));
|
||||
})
|
||||
.on('complete', resolve)
|
||||
.run({ async: true });
|
||||
});
|
||||
};
|
||||
}
|
||||
}, function () {
|
||||
QUnit.test('x2many with 250 rows, 2 fields (with many2many_tags, and modifiers), onchanges, and edition', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data.foo.onchanges.many2many = function (obj) {
|
||||
obj.many2many = [5].concat(obj.many2many);
|
||||
};
|
||||
for (let i = 2; i < 500; i++) {
|
||||
this.data.bar.records.push({
|
||||
id: i,
|
||||
char: "automated data",
|
||||
});
|
||||
this.data.foo.records[0].many2many.push(i);
|
||||
}
|
||||
this.arch = `
|
||||
<form>
|
||||
<field name="many2many">
|
||||
<tree editable="top" limit="250">
|
||||
<field name="char"/>
|
||||
<field name="many2many" widget="many2many_tags" attrs="{'readonly': [('char', '==', 'toto')]}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`;
|
||||
return this.run(assert, { res_id: 1 }, async form => {
|
||||
await testUtils.form.clickEdit(form);
|
||||
await testUtils.dom.click(form.$('.o_data_cell:first'));
|
||||
await testUtils.fields.editInput(form.$('input:first'), "tralala");
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('form view with 100 fields, half of them being invisible', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.arch = `
|
||||
<form>
|
||||
${[...Array(100)].map((_, i) => '<field name="foo"' + (i % 2 ? ' invisible="1"' : '') + '/>').join('')}
|
||||
</form>`;
|
||||
return this.run(assert);
|
||||
});
|
||||
});
|
||||
});
|
||||
12065
odoo-bringout-oca-ocb-web/web/static/tests/legacy/views/form_tests.js
Normal file
12065
odoo-bringout-oca-ocb-web/web/static/tests/legacy/views/form_tests.js
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,93 @@
|
|||
/* global Benchmark */
|
||||
odoo.define('web.kanban_benchmarks', function (require) {
|
||||
"use strict";
|
||||
|
||||
const KanbanView = require('web.KanbanView');
|
||||
const { createView } = require('web.test_utils');
|
||||
|
||||
QUnit.module('Kanban View', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
foo: {
|
||||
fields: {
|
||||
foo: {string: "Foo", type: "char"},
|
||||
bar: {string: "Bar", type: "boolean"},
|
||||
int_field: {string: "int_field", type: "integer", sortable: true},
|
||||
qux: {string: "my float", type: "float"},
|
||||
},
|
||||
records: [
|
||||
{ id: 1, bar: true, foo: "yop", int_field: 10, qux: 0.4},
|
||||
{id: 2, bar: true, foo: "blip", int_field: 9, qux: 13},
|
||||
]
|
||||
},
|
||||
};
|
||||
this.arch = null;
|
||||
this.run = function (assert) {
|
||||
const data = this.data;
|
||||
const arch = this.arch;
|
||||
return new Promise(resolve => {
|
||||
new Benchmark.Suite({})
|
||||
.add('kanban', {
|
||||
defer: true,
|
||||
fn: async (deferred) => {
|
||||
const kanban = await createView({
|
||||
View: KanbanView,
|
||||
model: 'foo',
|
||||
data,
|
||||
arch,
|
||||
});
|
||||
kanban.destroy();
|
||||
deferred.resolve();
|
||||
},
|
||||
})
|
||||
.on('cycle', event => {
|
||||
assert.ok(true, String(event.target));
|
||||
})
|
||||
.on('complete', resolve)
|
||||
.run({ async: true });
|
||||
});
|
||||
};
|
||||
}
|
||||
}, function () {
|
||||
QUnit.test('simple kanban view with 2 records', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.arch = `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<t t-esc="record.foo.value"/>
|
||||
<field name="foo"/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`;
|
||||
return this.run(assert);
|
||||
});
|
||||
|
||||
QUnit.test('simple kanban view with 200 records', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
for (let i = 2; i < 200; i++) {
|
||||
this.data.foo.records.push({
|
||||
id: i,
|
||||
foo: `automated data ${i}`,
|
||||
});
|
||||
}
|
||||
|
||||
this.arch = `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<t t-esc="record.foo.value"/>
|
||||
<field name="foo"/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`;
|
||||
return this.run(assert);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,380 @@
|
|||
odoo.define('web.kanban_model_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var KanbanModel = require('web.KanbanModel');
|
||||
var testUtils = require('web.test_utils');
|
||||
|
||||
var createModel = testUtils.createModel;
|
||||
|
||||
QUnit.module('LegacyViews', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
partner: {
|
||||
fields: {
|
||||
active: {string: "Active", type: "boolean", default: true},
|
||||
display_name: {string: "STRING", type: 'char'},
|
||||
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'},
|
||||
},
|
||||
records: [
|
||||
{id: 1, foo: 'blip', bar: 1, product_id: 37, category: [12], display_name: "first partner"},
|
||||
{id: 2, foo: 'gnap', bar: 2, product_id: 41, display_name: "second partner"},
|
||||
],
|
||||
onchanges: {},
|
||||
},
|
||||
product: {
|
||||
fields: {
|
||||
name: {string: "Product Name", type: "char"}
|
||||
},
|
||||
records: [
|
||||
{id: 37, display_name: "xphone"},
|
||||
{id: 41, display_name: "xpad"}
|
||||
]
|
||||
},
|
||||
partner_type: {
|
||||
fields: {
|
||||
display_name: {string: "Partner Type", type: "char"}
|
||||
},
|
||||
records: [
|
||||
{id: 12, display_name: "gold"},
|
||||
{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 = {
|
||||
fields: this.data.partner.fields,
|
||||
limit: 40,
|
||||
modelName: 'partner',
|
||||
openGroupByDefault: true,
|
||||
viewType: 'kanban',
|
||||
};
|
||||
},
|
||||
}, function () {
|
||||
|
||||
QUnit.module('KanbanModel (legacy)');
|
||||
|
||||
QUnit.test('load grouped + add a new group', async function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(22);
|
||||
|
||||
var calledRoutes = {};
|
||||
var model = await createModel({
|
||||
Model: KanbanModel,
|
||||
data: this.data,
|
||||
mockRPC: function (route) {
|
||||
if (!(route in calledRoutes)) {
|
||||
calledRoutes[route] = 1;
|
||||
} else {
|
||||
calledRoutes[route]++;
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
var params = _.extend(this.params, {
|
||||
groupedBy: ['product_id'],
|
||||
fieldNames: ['foo'],
|
||||
});
|
||||
|
||||
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"');
|
||||
assert.strictEqual(state.data.length, 2, 'should have found 2 groups');
|
||||
assert.strictEqual(state.count, 2, 'both groups contain one record');
|
||||
var xphoneGroup = _.findWhere(state.data, {res_id: 37});
|
||||
assert.strictEqual(xphoneGroup.model, 'partner', 'group should have correct model');
|
||||
assert.ok(xphoneGroup, 'should have a group for res_id 37');
|
||||
assert.ok(xphoneGroup.isOpen, '"xphone" group should be open');
|
||||
assert.strictEqual(xphoneGroup.value, 'xphone', 'group 37 should be "xphone"');
|
||||
assert.strictEqual(xphoneGroup.count, 1, '"xphone" group should have one record');
|
||||
assert.strictEqual(xphoneGroup.data.length, 1, 'should have fetched the records in the group');
|
||||
assert.ok(_.isEqual(xphoneGroup.domain[0], ['product_id', '=', 37]),
|
||||
'domain should be correct');
|
||||
assert.strictEqual(xphoneGroup.limit, 40, 'limit in a group should be 40');
|
||||
|
||||
// add a new group
|
||||
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');
|
||||
var xpodGroup = _.findWhere(state.data, {value: 'xpod'});
|
||||
assert.strictEqual(xpodGroup.model, 'partner', 'new group should have correct model');
|
||||
assert.ok(xpodGroup, 'should have an "xpod" group');
|
||||
assert.ok(xpodGroup.isOpen, 'new group should be open');
|
||||
assert.strictEqual(xpodGroup.count, 0, 'new group should contain no record');
|
||||
assert.ok(_.isEqual(xpodGroup.domain[0], ['product_id', '=', xpodGroup.res_id]),
|
||||
'new group should have correct domain');
|
||||
|
||||
// check the rpcs done
|
||||
assert.strictEqual(Object.keys(calledRoutes).length, 3, 'three different routes have been called');
|
||||
var nbReadGroups = calledRoutes['/web/dataset/call_kw/partner/web_read_group'];
|
||||
var nbSearchRead = calledRoutes['/web/dataset/search_read'];
|
||||
var nbNameCreate = calledRoutes['/web/dataset/call_kw/product/name_create'];
|
||||
assert.strictEqual(nbReadGroups, 1, 'should have done 1 read_group');
|
||||
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', async function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(4);
|
||||
|
||||
var model = await createModel({
|
||||
Model: KanbanModel,
|
||||
data: this.data,
|
||||
mockRPC: function (route, args) {
|
||||
if (route === '/web/dataset/call_kw/partner/action_archive') {
|
||||
this.data.partner.records[0].active = false;
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
var params = _.extend(this.params, {
|
||||
groupedBy: ['product_id'],
|
||||
fieldNames: ['foo'],
|
||||
});
|
||||
|
||||
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});
|
||||
assert.strictEqual(xphoneGroup.count, 1, 'xphone group has one record');
|
||||
assert.strictEqual(xpadGroup.count, 1, 'xpad group has one record');
|
||||
|
||||
// archive the column 'xphone'
|
||||
var recordIDs = xphoneGroup.data.map(record => record.res_id);
|
||||
await model.actionArchive(recordIDs, 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', async function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(2);
|
||||
|
||||
var model = await createModel({
|
||||
Model: KanbanModel,
|
||||
data: this.data,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'web_read_group') {
|
||||
assert.deepEqual(args.kwargs.groupby, ['product_id'],
|
||||
"the second level of groupBy should have been removed");
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
var params = _.extend(this.params, {
|
||||
groupedBy: ['product_id', 'qux'],
|
||||
fieldNames: ['foo'],
|
||||
});
|
||||
|
||||
model.load(params).then(function (resultID) {
|
||||
var state = model.get(resultID);
|
||||
|
||||
assert.deepEqual(state.groupedBy, ['product_id'],
|
||||
"the second level of groupBy should have been removed");
|
||||
|
||||
model.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('resequence columns and records', async function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(8);
|
||||
|
||||
this.data.product.fields.sequence = {string: "Sequence", type: "integer"};
|
||||
this.data.partner.fields.sequence = {string: "Sequence", type: "integer"};
|
||||
this.data.partner.records.push({id: 3, foo: 'aaa', product_id: 37});
|
||||
|
||||
var nbReseq = 0;
|
||||
var model = await createModel({
|
||||
Model: KanbanModel,
|
||||
data: this.data,
|
||||
mockRPC: function (route, args) {
|
||||
if (route === '/web/dataset/resequence') {
|
||||
nbReseq++;
|
||||
if (nbReseq === 1) { // resequencing columns
|
||||
assert.deepEqual(args.ids, [41, 37],
|
||||
"ids should be correct");
|
||||
assert.strictEqual(args.model, 'product',
|
||||
"model should be correct");
|
||||
} else if (nbReseq === 2) { // resequencing records
|
||||
assert.deepEqual(args.ids, [3, 1],
|
||||
"ids should be correct");
|
||||
assert.strictEqual(args.model, 'partner',
|
||||
"model should be correct");
|
||||
}
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
var params = _.extend(this.params, {
|
||||
groupedBy: ['product_id'],
|
||||
fieldNames: ['foo'],
|
||||
});
|
||||
|
||||
model.load(params)
|
||||
.then(function (stateID) {
|
||||
var state = model.get(stateID);
|
||||
assert.strictEqual(state.data[0].res_id, 37,
|
||||
"first group should be res_id 37");
|
||||
|
||||
// resequence columns
|
||||
return model.resequence('product', [41, 37], stateID);
|
||||
})
|
||||
.then(function (stateID) {
|
||||
var state = model.get(stateID);
|
||||
assert.strictEqual(state.data[0].res_id, 41,
|
||||
"first group should be res_id 41 after resequencing");
|
||||
assert.strictEqual(state.data[1].data[0].res_id, 1,
|
||||
"first record should be res_id 1");
|
||||
|
||||
// resequence records
|
||||
return model.resequence('partner', [3, 1], state.data[1].id);
|
||||
})
|
||||
.then(function (groupID) {
|
||||
var group = model.get(groupID);
|
||||
assert.strictEqual(group.data[0].res_id, 3,
|
||||
"first record should be res_id 3 after resequencing");
|
||||
|
||||
model.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('add record to group', async function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(8);
|
||||
|
||||
var self = this;
|
||||
var model = await createModel({
|
||||
Model: KanbanModel,
|
||||
data: this.data,
|
||||
});
|
||||
var params = _.extend(this.params, {
|
||||
groupedBy: ['product_id'],
|
||||
fieldNames: ['foo'],
|
||||
});
|
||||
|
||||
model.load(params).then(function (stateID) {
|
||||
self.data.partner.records.push({id: 3, foo: 'new record', product_id: 37});
|
||||
|
||||
var state = model.get(stateID);
|
||||
assert.deepEqual(state.res_ids, [1, 2],
|
||||
"state should have the correct res_ids");
|
||||
assert.strictEqual(state.count, 2,
|
||||
"state should have the correct count");
|
||||
assert.strictEqual(state.data[0].count, 1,
|
||||
"first group should contain one record");
|
||||
|
||||
return model.addRecordToGroup(state.data[0].id, 3).then(function () {
|
||||
var state = model.get(stateID);
|
||||
assert.deepEqual(state.res_ids, [3, 1, 2],
|
||||
"state should have the correct res_ids");
|
||||
assert.strictEqual(state.count, 3,
|
||||
"state should have the correct count");
|
||||
assert.deepEqual(state.data[0].res_ids, [3, 1],
|
||||
"new record's id should have been added to the res_ids");
|
||||
assert.strictEqual(state.data[0].count, 2,
|
||||
"first group should now contain two records");
|
||||
assert.strictEqual(state.data[0].data[0].data.foo, 'new record',
|
||||
"new record should have been fetched");
|
||||
});
|
||||
}).then(function() {
|
||||
model.destroy();
|
||||
done();
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
QUnit.test('call get (raw: true) before loading x2many data', async function (assert) {
|
||||
// Sometimes, get can be called on a datapoint that is currently being
|
||||
// reloaded, and thus in a partially updated state (e.g. in a kanban
|
||||
// view, the user interacts with the searchview, and before the view is
|
||||
// fully reloaded, it clicks on CREATE). Ideally, this shouldn't happen,
|
||||
// but with the sync API of get, we can't change that easily. So at most,
|
||||
// we can ensure that it doesn't crash. Moreover, sensitive functions
|
||||
// requesting the state for more precise information that, e.g., the
|
||||
// count, can do that in the mutex to ensure that the state isn't
|
||||
// currently being reloaded.
|
||||
// In this test, we have a grouped kanban view with a one2many, whose
|
||||
// relational data is loaded in batch, once for all groups. We call get
|
||||
// when the search_read for the first group has returned, but not the
|
||||
// second (and thus, the read of the one2many hasn't started yet).
|
||||
// Note: this test can be removed as soon as search_reads are performed
|
||||
// alongside read_group.
|
||||
var done = assert.async();
|
||||
assert.expect(2);
|
||||
|
||||
this.data.partner.records[1].product_ids = [37, 41];
|
||||
this.params.fieldsInfo = {
|
||||
kanban: {
|
||||
product_ids: {
|
||||
fieldsInfo: {
|
||||
default: { display_name: {}, color: {} },
|
||||
},
|
||||
relatedFields: this.data.product.fields,
|
||||
viewType: 'default',
|
||||
},
|
||||
},
|
||||
};
|
||||
this.params.viewType = 'kanban';
|
||||
this.params.groupedBy = ['foo'];
|
||||
|
||||
var block;
|
||||
var def = testUtils.makeTestPromise();
|
||||
var model = await createModel({
|
||||
Model: KanbanModel,
|
||||
data: this.data,
|
||||
mockRPC: function (route) {
|
||||
var result = this._super.apply(this, arguments);
|
||||
if (route === '/web/dataset/search_read' && block) {
|
||||
block = false;
|
||||
return Promise.all([def]).then(_.constant(result));
|
||||
}
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
model.load(this.params).then(function (handle) {
|
||||
block = true;
|
||||
model.reload(handle, {});
|
||||
|
||||
var state = model.get(handle, {raw: true});
|
||||
assert.strictEqual(state.count, 2);
|
||||
|
||||
def.resolve();
|
||||
|
||||
state = model.get(handle, {raw: true});
|
||||
assert.strictEqual(state.count, 2);
|
||||
}).then(function() {
|
||||
model.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,114 @@
|
|||
/* global Benchmark */
|
||||
odoo.define('web.list_benchmarks', function (require) {
|
||||
"use strict";
|
||||
|
||||
const ListView = require('web.ListView');
|
||||
const { createView } = require('web.test_utils');
|
||||
|
||||
QUnit.module('List View', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
foo: {
|
||||
fields: {
|
||||
foo: {string: "Foo", type: "char"},
|
||||
bar: {string: "Bar", type: "boolean"},
|
||||
int_field: {string: "int_field", type: "integer", sortable: true},
|
||||
qux: {string: "my float", type: "float"},
|
||||
},
|
||||
records: [
|
||||
{id: 1, bar: true, foo: "yop", int_field: 10, qux: 0.4},
|
||||
{id: 2, bar: true, foo: "blip", int_field: 9, qux: 13},
|
||||
]
|
||||
},
|
||||
};
|
||||
this.arch = null;
|
||||
this.run = function (assert, cb) {
|
||||
const data = this.data;
|
||||
const arch = this.arch;
|
||||
return new Promise(resolve => {
|
||||
new Benchmark.Suite({})
|
||||
.add('list', {
|
||||
defer: true,
|
||||
fn: async (deferred) => {
|
||||
const list = await createView({
|
||||
View: ListView,
|
||||
model: 'foo',
|
||||
data,
|
||||
arch,
|
||||
});
|
||||
if (cb) {
|
||||
cb(list);
|
||||
}
|
||||
list.destroy();
|
||||
deferred.resolve();
|
||||
},
|
||||
})
|
||||
.on('cycle', event => {
|
||||
assert.ok(true, String(event.target));
|
||||
})
|
||||
.on('complete', resolve)
|
||||
.run({ async: true });
|
||||
});
|
||||
};
|
||||
}
|
||||
}, function () {
|
||||
QUnit.test('simple readonly list with 2 rows and 2 fields', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.arch = '<tree><field name="foo"/><field name="int_field"/></tree>';
|
||||
return this.run(assert);
|
||||
});
|
||||
|
||||
QUnit.test('simple readonly list with 200 rows and 2 fields', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
for (let i = 2; i < 200; i++) {
|
||||
this.data.foo.records.push({
|
||||
id: i,
|
||||
foo: "automated data",
|
||||
int_field: 10 * i,
|
||||
});
|
||||
}
|
||||
this.arch = '<tree><field name="foo"/><field name="int_field"/></tree>';
|
||||
return this.run(assert);
|
||||
});
|
||||
|
||||
QUnit.test('simple readonly list with 200 rows and 2 fields (with widgets)', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
for (let i = 2; i < 200; i++) {
|
||||
this.data.foo.records.push({
|
||||
id: i,
|
||||
foo: "automated data",
|
||||
int_field: 10 * i,
|
||||
});
|
||||
}
|
||||
this.arch = '<tree><field name="foo" widget="char"/><field name="int_field" widget="integer"/></tree>';
|
||||
return this.run(assert);
|
||||
});
|
||||
|
||||
QUnit.test('editable list with 200 rows 4 fields', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
for (let i = 2; i < 200; i++) {
|
||||
this.data.foo.records.push({
|
||||
id: i,
|
||||
foo: "automated data",
|
||||
int_field: 10 * i,
|
||||
bar: i % 2 === 0,
|
||||
});
|
||||
}
|
||||
this.arch = `
|
||||
<tree editable="bottom">
|
||||
<field name="foo" attrs="{'readonly': [['bar', '=', True]]}"/>
|
||||
<field name="int_field"/>
|
||||
<field name="bar"/>
|
||||
<field name="qux"/>
|
||||
</tree>`;
|
||||
return this.run(assert, list => {
|
||||
list.$('.o_list_button_add').click();
|
||||
list.$('.o_list_button_discard').click();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
12837
odoo-bringout-oca-ocb-web/web/static/tests/legacy/views/list_tests.js
Normal file
12837
odoo-bringout-oca-ocb-web/web/static/tests/legacy/views/list_tests.js
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,75 @@
|
|||
odoo.define('web.qweb_view_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
const utils = require('web.test_utils');
|
||||
const { createWebClient, doAction } = require('@web/../tests/webclient/helpers');
|
||||
const { getFixture } = require("@web/../tests/helpers/utils");
|
||||
|
||||
QUnit.module("Views", {
|
||||
|
||||
}, function () {
|
||||
QUnit.module("QWeb");
|
||||
QUnit.test("basic", async function (assert) {
|
||||
assert.expect(14);
|
||||
|
||||
const serverData = {
|
||||
models: {
|
||||
test: {
|
||||
fields: {},
|
||||
records: [],
|
||||
}
|
||||
},
|
||||
views: {
|
||||
'test,5,qweb': '<div id="xxx"><t t-esc="ok"/></div>',
|
||||
'test,false,search': '<search/>'
|
||||
},
|
||||
};
|
||||
|
||||
const mockRPC = (route, args) => {
|
||||
if (/^\/web\/dataset\/call_kw/.test(route)) {
|
||||
switch (_.str.sprintf('%(model)s.%(method)s', args)) {
|
||||
case 'test.qweb_render_view':
|
||||
assert.step('fetch');
|
||||
assert.equal(args.kwargs.view_id, 5);
|
||||
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>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
case 'test.wheee':
|
||||
assert.step('unfold');
|
||||
assert.deepEqual(args.args, [42]);
|
||||
assert.deepEqual(args.kwargs, { other: 5, context: {} });
|
||||
return Promise.resolve('<div id="sub">ok</div>');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const target = getFixture();
|
||||
const webClient = await createWebClient({ serverData, mockRPC});
|
||||
|
||||
let resolved = false;
|
||||
const doActionProm = doAction(webClient, {
|
||||
type: 'ir.actions.act_window',
|
||||
views: [[false, 'qweb']],
|
||||
res_model: 'test',
|
||||
}).then(function () { resolved = true; });
|
||||
assert.ok(!resolved, "Action cannot be resolved synchronously");
|
||||
|
||||
await doActionProm;
|
||||
assert.ok(resolved, "Action is resolved asynchronously");
|
||||
|
||||
const content = target.querySelector('.o_content');
|
||||
assert.ok(/^\s*foo/.test(content.textContent));
|
||||
await utils.dom.click(content.querySelector('[type=toggle]'));
|
||||
assert.equal(content.querySelector('div#sub').textContent, 'ok', 'should have unfolded the sub-item');
|
||||
await utils.dom.click(content.querySelector('[type=toggle]'));
|
||||
assert.containsNone(content, "div#sub");
|
||||
await utils.dom.click(content.querySelector('[type=toggle]'));
|
||||
|
||||
assert.verifySteps(['fetch', 'unfold', 'unfold']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,486 @@
|
|||
odoo.define('web.sample_server_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
const SampleServer = require('web.SampleServer');
|
||||
const session = require('web.session');
|
||||
const { mock } = require('web.test_utils');
|
||||
|
||||
const {
|
||||
MAIN_RECORDSET_SIZE, SEARCH_READ_LIMIT, // Limits
|
||||
SAMPLE_COUNTRIES, SAMPLE_PEOPLE, SAMPLE_TEXTS, // Text values
|
||||
MAX_COLOR_INT, MAX_FLOAT, MAX_INTEGER, MAX_MONETARY, // Number values
|
||||
SUB_RECORDSET_SIZE, // Records sise
|
||||
} = SampleServer;
|
||||
|
||||
/**
|
||||
* Transforms random results into deterministic ones.
|
||||
*/
|
||||
class DeterministicSampleServer extends SampleServer {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.arrayElCpt = 0;
|
||||
this.boolCpt = 0;
|
||||
this.subRecordIdCpt = 0;
|
||||
}
|
||||
_getRandomArrayEl(array) {
|
||||
return array[this.arrayElCpt++ % array.length];
|
||||
}
|
||||
_getRandomBool() {
|
||||
return Boolean(this.boolCpt++ % 2);
|
||||
}
|
||||
_getRandomSubRecordId() {
|
||||
return (this.subRecordIdCpt++ % SUB_RECORDSET_SIZE) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
QUnit.module("Sample Server (legacy)", {
|
||||
beforeEach() {
|
||||
this.fields = {
|
||||
'res.users': {
|
||||
display_name: { string: "Name", type: 'char' },
|
||||
name: { string: "Reference", type: 'char' },
|
||||
email: { string: "Email", type: 'char' },
|
||||
phone_number: { string: "Phone number", type: 'char' },
|
||||
brol_machin_url_truc: { string: "URL", type: 'char' },
|
||||
urlemailphone: { string: "Whatever", type: 'char' },
|
||||
active: { string: "Active", type: 'boolean' },
|
||||
is_alive: { string: "Is alive", type: 'boolean' },
|
||||
description: { string: "Description", type: 'text' },
|
||||
birthday: { string: "Birthday", type: 'date' },
|
||||
arrival_date: { string: "Date of arrival", type: 'datetime' },
|
||||
height: { string: "Height", type: 'float' },
|
||||
color: { string: "Color", type: 'integer' },
|
||||
age: { string: "Age", type: 'integer' },
|
||||
salary: { string: "Salary", type: 'monetary' },
|
||||
currency: { string: "Currency", type: 'many2one', relation: 'res.currency' },
|
||||
manager_id: { string: "Manager", type: 'many2one', relation: 'res.users' },
|
||||
cover_image_id: { string: "Cover Image", type: 'many2one', relation: 'ir.attachment' },
|
||||
managed_ids: { string: "Managing", type: 'one2many', relation: 'res.users' },
|
||||
tag_ids: { string: "Tags", type: 'many2many', relation: 'tag' },
|
||||
type: { string: "Type", type: 'selection', selection: [
|
||||
['client', "Client"], ['partner', "Partner"], ['employee', "Employee"]
|
||||
] },
|
||||
},
|
||||
'res.country': {
|
||||
display_name: { string: "Name", type: 'char' },
|
||||
},
|
||||
'hobbit': {
|
||||
display_name: { string: "Name", type: 'char' },
|
||||
profession: { string: "Profession", type: 'selection', selection: [
|
||||
['gardener', "Gardener"], ['brewer', "Brewer"], ['adventurer', "Adventurer"]
|
||||
] },
|
||||
age: { string: "Age", type: 'integer' },
|
||||
},
|
||||
'ir.attachment': {
|
||||
display_name: { string: "Name", type: 'char' },
|
||||
}
|
||||
};
|
||||
},
|
||||
}, function () {
|
||||
|
||||
QUnit.module("Basic behaviour");
|
||||
|
||||
QUnit.test("Sample data: people type + all field names", async function (assert) {
|
||||
assert.expect(26);
|
||||
|
||||
mock.patch(session, {
|
||||
company_currency_id: 4,
|
||||
});
|
||||
|
||||
const allFieldNames = Object.keys(this.fields['res.users']);
|
||||
const server = new DeterministicSampleServer('res.users', this.fields['res.users']);
|
||||
const { records } = await server.mockRpc({
|
||||
method: '/web/dataset/search_read',
|
||||
model: 'res.users',
|
||||
fields: allFieldNames,
|
||||
});
|
||||
const rec = records[0];
|
||||
|
||||
function assertFormat(fieldName, regex) {
|
||||
if (regex instanceof RegExp) {
|
||||
assert.ok(
|
||||
regex.test(rec[fieldName].toString()),
|
||||
`Field "${fieldName}" has the correct format`
|
||||
);
|
||||
} else {
|
||||
assert.strictEqual(
|
||||
typeof rec[fieldName], regex,
|
||||
`Field "${fieldName}" is of type ${regex}`
|
||||
);
|
||||
}
|
||||
}
|
||||
function assertBetween(fieldName, min, max) {
|
||||
const val = rec[fieldName];
|
||||
assert.ok(
|
||||
min <= val && val < max && parseInt(val, 10) === val,
|
||||
`Field "${fieldName}" should be an integer between ${min} and ${max}: ${val}`
|
||||
);
|
||||
}
|
||||
|
||||
// Basic fields
|
||||
assert.ok(SAMPLE_PEOPLE.includes(rec.display_name));
|
||||
assert.ok(SAMPLE_PEOPLE.includes(rec.name));
|
||||
assert.strictEqual(rec.email,
|
||||
`${rec.display_name.replace(/ /, ".").toLowerCase()}@sample.demo`
|
||||
);
|
||||
assertFormat('phone_number', /\+1 555 754 000\d/);
|
||||
assertFormat('brol_machin_url_truc', /http:\/\/sample\d\.com/);
|
||||
assert.strictEqual(rec.urlemailphone, false);
|
||||
assert.strictEqual(rec.active, true);
|
||||
assertFormat('is_alive', 'boolean');
|
||||
assert.ok(SAMPLE_TEXTS.includes(rec.description));
|
||||
assertFormat('birthday', /\d{4}-\d{2}-\d{2}/);
|
||||
assertFormat('arrival_date', /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/);
|
||||
assert.ok(rec.height >= 0 && rec.height <= MAX_FLOAT, "Field height should be between 0 and 100");
|
||||
assertBetween('color', 0, MAX_COLOR_INT);
|
||||
assertBetween('age', 0, MAX_INTEGER);
|
||||
assertBetween('salary', 0, MAX_MONETARY);
|
||||
|
||||
// check float field have 2 decimal rounding
|
||||
assert.strictEqual(rec.height, parseFloat(parseFloat(rec.height).toFixed(2)));
|
||||
|
||||
const selectionValues = this.fields['res.users'].type.selection.map(
|
||||
(sel) => sel[0]
|
||||
);
|
||||
assert.ok(selectionValues.includes(rec.type));
|
||||
|
||||
// Relational fields
|
||||
assert.strictEqual(rec.currency[0], 4);
|
||||
// Currently we expect the currency name to be a latin string, which
|
||||
// is not important; in most case we only need the ID. The following
|
||||
// assertion can be removed if needed.
|
||||
assert.ok(SAMPLE_TEXTS.includes(rec.currency[1]));
|
||||
|
||||
assert.strictEqual(typeof rec.manager_id[0], 'number');
|
||||
assert.ok(SAMPLE_PEOPLE.includes(rec.manager_id[1]));
|
||||
|
||||
assert.strictEqual(rec.cover_image_id, false);
|
||||
|
||||
assert.strictEqual(rec.managed_ids.length, 2);
|
||||
assert.ok(rec.managed_ids.every(
|
||||
(id) => typeof id === 'number')
|
||||
);
|
||||
|
||||
assert.strictEqual(rec.tag_ids.length, 2);
|
||||
assert.ok(rec.tag_ids.every(
|
||||
(id) => typeof id === 'number')
|
||||
);
|
||||
|
||||
mock.unpatch(session);
|
||||
});
|
||||
|
||||
QUnit.test("Sample data: country type", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const server = new DeterministicSampleServer('res.country', this.fields['res.country']);
|
||||
const { records } = await server.mockRpc({
|
||||
method: '/web/dataset/search_read',
|
||||
model: 'res.country',
|
||||
fields: ['display_name'],
|
||||
});
|
||||
|
||||
assert.ok(SAMPLE_COUNTRIES.includes(records[0].display_name));
|
||||
});
|
||||
|
||||
QUnit.test("Sample data: any type", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const server = new DeterministicSampleServer('hobbit', this.fields.hobbit);
|
||||
|
||||
const { records } = await server.mockRpc({
|
||||
method: '/web/dataset/search_read',
|
||||
model: 'hobbit',
|
||||
fields: ['display_name'],
|
||||
});
|
||||
|
||||
assert.ok(SAMPLE_TEXTS.includes(records[0].display_name));
|
||||
});
|
||||
|
||||
QUnit.module("RPC calls");
|
||||
|
||||
QUnit.test("Send 'search_read' RPC: valid field names", async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const server = new DeterministicSampleServer('hobbit', this.fields.hobbit);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: '/web/dataset/search_read',
|
||||
model: 'hobbit',
|
||||
fields: ['display_name'],
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
Object.keys(result.records[0]),
|
||||
['id', 'display_name']
|
||||
);
|
||||
assert.strictEqual(result.length, SEARCH_READ_LIMIT);
|
||||
assert.ok(/\w+/.test(result.records[0].display_name),
|
||||
"Display name has been mocked"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("Send 'search_read' RPC: invalid field names", async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const server = new DeterministicSampleServer('hobbit', this.fields.hobbit);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: '/web/dataset/search_read',
|
||||
model: 'hobbit',
|
||||
fields: ['name'],
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
Object.keys(result.records[0]),
|
||||
['id', 'name']
|
||||
);
|
||||
assert.strictEqual(result.length, SEARCH_READ_LIMIT);
|
||||
assert.strictEqual(result.records[0].name, false,
|
||||
`Field "name" doesn't exist => returns false`
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("Send 'web_read_group' RPC: no group", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const server = new DeterministicSampleServer('hobbit', this.fields.hobbit);
|
||||
server.setExistingGroups([]);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: 'web_read_group',
|
||||
model: 'hobbit',
|
||||
groupBy: ['profession'],
|
||||
});
|
||||
|
||||
assert.deepEqual(result, { groups: [], length: 0 });
|
||||
});
|
||||
|
||||
QUnit.test("Send 'web_read_group' RPC: 2 groups", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
const server = new DeterministicSampleServer('hobbit', this.fields.hobbit);
|
||||
const existingGroups = [
|
||||
{ profession: 'gardener', profession_count: 0 },
|
||||
{ profession: 'adventurer', profession_count: 0 },
|
||||
];
|
||||
server.setExistingGroups(existingGroups);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: 'web_read_group',
|
||||
model: 'hobbit',
|
||||
groupBy: ['profession'],
|
||||
fields: [],
|
||||
});
|
||||
|
||||
assert.strictEqual(result.length, 2);
|
||||
assert.strictEqual(result.groups.length, 2);
|
||||
|
||||
assert.deepEqual(
|
||||
result.groups.map((g) => g.profession),
|
||||
["gardener", "adventurer"]
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
result.groups.reduce((acc, g) => acc + g.profession_count, 0),
|
||||
MAIN_RECORDSET_SIZE
|
||||
);
|
||||
assert.ok(
|
||||
result.groups.every((g) => g.profession_count === g.__data.length)
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("Send 'web_read_group' RPC: all groups", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
const server = new DeterministicSampleServer('hobbit', this.fields.hobbit);
|
||||
const existingGroups = [
|
||||
{ profession: 'gardener', profession_count: 0 },
|
||||
{ profession: 'brewer', profession_count: 0 },
|
||||
{ profession: 'adventurer', profession_count: 0 },
|
||||
];
|
||||
server.setExistingGroups(existingGroups);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: 'web_read_group',
|
||||
model: 'hobbit',
|
||||
groupBy: ['profession'],
|
||||
fields: [],
|
||||
});
|
||||
|
||||
assert.strictEqual(result.length, 3);
|
||||
assert.strictEqual(result.groups.length, 3);
|
||||
|
||||
assert.deepEqual(
|
||||
result.groups.map((g) => g.profession),
|
||||
["gardener", "brewer", "adventurer"]
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
result.groups.reduce((acc, g) => acc + g.profession_count, 0),
|
||||
MAIN_RECORDSET_SIZE
|
||||
);
|
||||
assert.ok(
|
||||
result.groups.every((g) => g.profession_count === g.__data.length)
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("Send 'read_group' RPC: no group", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const server = new DeterministicSampleServer('hobbit', this.fields.hobbit);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: 'read_group',
|
||||
model: 'hobbit',
|
||||
fields: [],
|
||||
groupBy: [],
|
||||
});
|
||||
|
||||
assert.deepEqual(result, [{
|
||||
__count: MAIN_RECORDSET_SIZE,
|
||||
__domain: [],
|
||||
}]);
|
||||
});
|
||||
|
||||
QUnit.test("Send 'read_group' RPC: groupBy", async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const server = new DeterministicSampleServer('hobbit', this.fields.hobbit);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: 'read_group',
|
||||
model: 'hobbit',
|
||||
fields: [],
|
||||
groupBy: ['profession'],
|
||||
});
|
||||
|
||||
assert.strictEqual(result.length, 3);
|
||||
assert.deepEqual(
|
||||
result.map((g) => g.profession),
|
||||
["adventurer", "brewer", "gardener"]
|
||||
);
|
||||
assert.strictEqual(
|
||||
result.reduce((acc, g) => acc + g.profession_count, 0),
|
||||
MAIN_RECORDSET_SIZE,
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("Send 'read_group' RPC: groupBy and field", async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const server = new DeterministicSampleServer('hobbit', this.fields.hobbit);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: 'read_group',
|
||||
model: 'hobbit',
|
||||
fields: ['age'],
|
||||
groupBy: ['profession'],
|
||||
});
|
||||
|
||||
assert.strictEqual(result.length, 3);
|
||||
assert.deepEqual(
|
||||
result.map((g) => g.profession),
|
||||
["adventurer", "brewer", "gardener"]
|
||||
);
|
||||
assert.strictEqual(
|
||||
result.reduce((acc, g) => acc + g.profession_count, 0),
|
||||
MAIN_RECORDSET_SIZE,
|
||||
);
|
||||
assert.strictEqual(
|
||||
result.reduce((acc, g) => acc + g.age, 0),
|
||||
server.data.hobbit.records.reduce((acc, g) => acc + g.age, 0)
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("Send 'read_group' RPC: multiple groupBys and lazy", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const server = new DeterministicSampleServer('hobbit', this.fields.hobbit);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: 'read_group',
|
||||
model: 'hobbit',
|
||||
fields: [],
|
||||
groupBy: ['profession', 'age'],
|
||||
});
|
||||
|
||||
assert.ok('profession' in result[0]);
|
||||
assert.notOk('age' in result[0]);
|
||||
});
|
||||
|
||||
QUnit.test("Send 'read_group' RPC: multiple groupBys and not lazy", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const server = new DeterministicSampleServer('hobbit', this.fields.hobbit);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: 'read_group',
|
||||
model: 'hobbit',
|
||||
fields: [],
|
||||
groupBy: ['profession', 'age'],
|
||||
lazy: false,
|
||||
});
|
||||
|
||||
assert.ok('profession' in result[0]);
|
||||
assert.ok('age' in result[0]);
|
||||
});
|
||||
|
||||
QUnit.test("Send 'read' RPC: no id", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const server = new DeterministicSampleServer('hobbit', this.fields.hobbit);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: 'read',
|
||||
model: 'hobbit',
|
||||
args: [
|
||||
[], ['display_name']
|
||||
],
|
||||
});
|
||||
|
||||
assert.deepEqual(result, []);
|
||||
});
|
||||
|
||||
QUnit.test("Send 'read' RPC: one id", async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const server = new DeterministicSampleServer('hobbit', this.fields.hobbit);
|
||||
|
||||
const result = await server.mockRpc({
|
||||
method: 'read',
|
||||
model: 'hobbit',
|
||||
args: [
|
||||
[1], ['display_name']
|
||||
],
|
||||
});
|
||||
|
||||
assert.strictEqual(result.length, 1);
|
||||
assert.ok(
|
||||
/\w+/.test(result[0].display_name),
|
||||
"Display name has been mocked"
|
||||
);
|
||||
assert.strictEqual(result[0].id, 1);
|
||||
});
|
||||
|
||||
QUnit.test("Send 'read' RPC: more than all available ids", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const server = new DeterministicSampleServer('hobbit', this.fields.hobbit);
|
||||
|
||||
const amount = MAIN_RECORDSET_SIZE + 3;
|
||||
const ids = new Array(amount).fill().map((_, i) => i + 1);
|
||||
const result = await server.mockRpc({
|
||||
method: 'read',
|
||||
model: 'hobbit',
|
||||
args: [
|
||||
ids, ['display_name']
|
||||
],
|
||||
});
|
||||
|
||||
assert.strictEqual(result.length, MAIN_RECORDSET_SIZE);
|
||||
});
|
||||
|
||||
// To be implemented if needed
|
||||
// QUnit.test("Send 'read_progress_bar' RPC", async function (assert) { ... });
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,404 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { getFixture, legacyExtraNextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import {
|
||||
getFacetTexts,
|
||||
removeFacet,
|
||||
saveFavorite,
|
||||
setupControlPanelFavoriteMenuRegistry,
|
||||
setupControlPanelServiceRegistry,
|
||||
switchView,
|
||||
toggleFavoriteMenu,
|
||||
toggleMenu,
|
||||
toggleMenuItem,
|
||||
toggleSaveFavorite,
|
||||
} from "@web/../tests/search/helpers";
|
||||
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { ControlPanel } from "@web/search/control_panel/control_panel";
|
||||
import { SearchModel } from "@web/search/search_model";
|
||||
import AbstractView from "web.AbstractView";
|
||||
import ActionModel from "web.ActionModel";
|
||||
import { mock } from "web.test_utils";
|
||||
import legacyViewRegistry from "web.view_registry";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
|
||||
import { xml } from "@odoo/owl";
|
||||
const viewRegistry = registry.category("views");
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
QUnit.module("LegacyViews", (hooks) => {
|
||||
hooks.beforeEach(async () => {
|
||||
target = getFixture();
|
||||
|
||||
serverData = {
|
||||
models: {
|
||||
foo: {
|
||||
fields: {
|
||||
display_name: { string: "Displayed name", type: "char" },
|
||||
foo: {
|
||||
string: "Foo",
|
||||
type: "char",
|
||||
default: "My little Foo Value",
|
||||
store: true,
|
||||
sortable: true,
|
||||
},
|
||||
date_field: { string: "Date", type: "date", store: true, sortable: true },
|
||||
float_field: { string: "Float", type: "float" },
|
||||
bar: {
|
||||
string: "Bar",
|
||||
type: "many2one",
|
||||
relation: "partner",
|
||||
store: true,
|
||||
sortable: true,
|
||||
},
|
||||
},
|
||||
records: [],
|
||||
},
|
||||
},
|
||||
views: {
|
||||
"foo,false,legacy_toy": `<legacy_toy/>`,
|
||||
"foo,false,toy": `<toy/>`,
|
||||
"foo,false,search": `
|
||||
<search>
|
||||
<field name="foo" operator="="/>
|
||||
<filter name="true_domain" string="True Domain" domain="[(1, '=', 1)]"/>
|
||||
<filter name="date_domain" string="Date Filter" date="date_field" domain="[]"/>
|
||||
<filter name="group_by_bar" string="Bar" context="{ 'group_by': 'bar' }"/>
|
||||
<filter name="group_by_date_field" string="Date GroupBy" context="{ 'group_by': 'date_field' }"/>
|
||||
</search>
|
||||
`,
|
||||
},
|
||||
};
|
||||
setupControlPanelFavoriteMenuRegistry();
|
||||
setupControlPanelServiceRegistry();
|
||||
|
||||
class ToyController extends LegacyComponent {}
|
||||
ToyController.template = xml`<div class="o_toy_view"><ControlPanel /></div>`;
|
||||
ToyController.components = { ControlPanel};
|
||||
|
||||
viewRegistry.add("toy", {
|
||||
type: "toy",
|
||||
display_name: _lt("Toy view"),
|
||||
multiRecord: true,
|
||||
searchMenuTypes: ["filter", "groupBy", "comparison", "favorite"],
|
||||
Controller: ToyController,
|
||||
});
|
||||
|
||||
const LegacyToyView = AbstractView.extend({
|
||||
display_name: _lt("Legacy toy view"),
|
||||
icon: "fa fa-bars",
|
||||
multiRecord: true,
|
||||
viewType: "legacy_toy",
|
||||
searchMenuTypes: ["filter", "groupBy", "comparison", "favorite"],
|
||||
});
|
||||
|
||||
legacyViewRegistry.add("legacy_toy", LegacyToyView);
|
||||
});
|
||||
|
||||
QUnit.module("State Mappings");
|
||||
|
||||
QUnit.test(
|
||||
"legacy and new views can share search model state (no favorite)",
|
||||
async function (assert) {
|
||||
assert.expect(10);
|
||||
|
||||
const unpatchDate = mock.patchDate(2021, 6, 1, 10, 0, 0);
|
||||
|
||||
const webClient = await createWebClient({ serverData });
|
||||
|
||||
await doAction(webClient, {
|
||||
name: "Action name",
|
||||
res_model: "foo",
|
||||
type: "ir.actions.act_window",
|
||||
views: [
|
||||
[false, "toy"],
|
||||
[false, "legacy_toy"],
|
||||
],
|
||||
context: {
|
||||
search_default_foo: "ABC",
|
||||
search_default_true_domain: 1,
|
||||
search_default_date_domain: 1,
|
||||
search_default_group_by_bar: 50,
|
||||
search_default_group_by_date_field: 1,
|
||||
},
|
||||
});
|
||||
|
||||
assert.containsOnce(target, ".o_switch_view.o_toy.active");
|
||||
assert.deepEqual(
|
||||
getFacetTexts(target).map((s) => s.replace(/\s/, "")),
|
||||
[
|
||||
"FooABC",
|
||||
"TrueDomainorDate Filter: July 2021",
|
||||
"DateGroupBy: Month>Bar",
|
||||
]
|
||||
);
|
||||
|
||||
await toggleMenu(target, "Comparison");
|
||||
await toggleMenuItem(target, "Date Filter: Previous Period");
|
||||
|
||||
assert.deepEqual(
|
||||
getFacetTexts(target).map((s) => s.replace(/\s/, "")),
|
||||
[
|
||||
"FooABC",
|
||||
"TrueDomainorDate Filter: July 2021",
|
||||
"DateGroupBy: Month>Bar",
|
||||
"DateFilter: Previous Period",
|
||||
]
|
||||
);
|
||||
|
||||
await switchView(target, "legacy_toy");
|
||||
await legacyExtraNextTick();
|
||||
|
||||
assert.containsOnce(target, ".o_switch_view.o_legacy_toy.active");
|
||||
assert.deepEqual(
|
||||
getFacetTexts(target).map((s) => s.replace(/\s/, "")),
|
||||
[
|
||||
"FooABC",
|
||||
"TrueDomainorDate Filter: July 2021",
|
||||
"DateGroupBy: Month>Bar",
|
||||
"DateFilter: Previous Period",
|
||||
]
|
||||
);
|
||||
|
||||
await removeFacet(target, 1);
|
||||
|
||||
assert.deepEqual(
|
||||
getFacetTexts(target).map((s) => s.replace(/\s/, "")),
|
||||
[
|
||||
"FooABC",
|
||||
"DateGroupBy: Month>Bar",
|
||||
]
|
||||
);
|
||||
|
||||
await switchView(target, "toy");
|
||||
|
||||
assert.containsOnce(target, ".o_switch_view.o_toy.active");
|
||||
assert.deepEqual(
|
||||
getFacetTexts(target).map((s) => s.replace(/\s/, "")),
|
||||
[
|
||||
"FooABC",
|
||||
"DateGroupBy: Month>Bar",
|
||||
]
|
||||
);
|
||||
|
||||
// Check if update works
|
||||
await removeFacet(target, 1);
|
||||
|
||||
assert.deepEqual(
|
||||
getFacetTexts(target).map((s) => s.replace(/\s/, "")),
|
||||
[
|
||||
"FooABC",
|
||||
]
|
||||
);
|
||||
|
||||
await switchView(target, "legacy_toy");
|
||||
|
||||
assert.deepEqual(
|
||||
getFacetTexts(target).map((s) => s.replace(/\s/, "")),
|
||||
[
|
||||
"FooABC",
|
||||
]
|
||||
);
|
||||
|
||||
unpatchDate();
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"legacy and new views can share search model state (favorite)",
|
||||
async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
serverData.models.foo.filters = [
|
||||
{
|
||||
context: "{}",
|
||||
domain: "[['foo', '=', 'qsdf']]",
|
||||
id: 7,
|
||||
is_default: true,
|
||||
name: "My favorite",
|
||||
sort: "[]",
|
||||
user_id: [2, "Mitchell Admin"],
|
||||
},
|
||||
];
|
||||
|
||||
const webClient = await createWebClient({ serverData });
|
||||
|
||||
await doAction(webClient, {
|
||||
name: "Action name",
|
||||
res_model: "foo",
|
||||
type: "ir.actions.act_window",
|
||||
views: [
|
||||
[false, "toy"],
|
||||
[false, "legacy_toy"],
|
||||
],
|
||||
});
|
||||
|
||||
assert.containsOnce(target, ".o_switch_view.o_toy.active");
|
||||
assert.deepEqual(getFacetTexts(target), ["My favorite"]);
|
||||
|
||||
await switchView(target, "legacy_toy");
|
||||
await legacyExtraNextTick();
|
||||
|
||||
assert.containsOnce(target, ".o_switch_view.o_legacy_toy.active");
|
||||
assert.deepEqual(getFacetTexts(target), ["My favorite"]);
|
||||
|
||||
await switchView(target, "toy");
|
||||
|
||||
assert.containsOnce(target, ".o_switch_view.o_toy.active");
|
||||
assert.deepEqual(getFacetTexts(target), ["My favorite"]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"newly created favorite in a new view can be used in a legacy view",
|
||||
async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
patchWithCleanup(browser, { setTimeout: (fn) => fn() });
|
||||
const webClient = await createWebClient({
|
||||
serverData,
|
||||
mockRPC(_, args) {
|
||||
if (args.method === "create_or_replace") {
|
||||
assert.ok(typeof args.args[0].domain === "string");
|
||||
return 7; // fake serverId to simulate the creation of
|
||||
// the favorite in db.
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await doAction(webClient, {
|
||||
name: "Action name",
|
||||
res_model: "foo",
|
||||
type: "ir.actions.act_window",
|
||||
views: [
|
||||
[false, "toy"],
|
||||
[false, "legacy_toy"],
|
||||
],
|
||||
});
|
||||
|
||||
assert.containsOnce(target, ".o_switch_view.o_toy.active");
|
||||
|
||||
await toggleFavoriteMenu(target);
|
||||
await toggleSaveFavorite(target);
|
||||
await saveFavorite(target);
|
||||
|
||||
assert.deepEqual(getFacetTexts(target), ["Action name"]);
|
||||
|
||||
await switchView(target, "legacy_toy");
|
||||
await legacyExtraNextTick();
|
||||
|
||||
assert.containsOnce(target, ".o_switch_view.o_legacy_toy.active");
|
||||
assert.deepEqual(getFacetTexts(target), ["Action name"]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"legacy and new views with search model extensions can share search model state",
|
||||
async function (assert) {
|
||||
assert.expect(16);
|
||||
|
||||
serverData.views[
|
||||
"foo,1,legacy_toy"
|
||||
] = `<legacy_toy js_class="legacy_toy_with_extension"/>`;
|
||||
|
||||
class SearchModelExtension extends SearchModel {
|
||||
setup() {
|
||||
super.setup(...arguments);
|
||||
this.toyExtension = { locationId: "Grand-Rosière" };
|
||||
}
|
||||
|
||||
exportState() {
|
||||
const exportedState = super.exportState(...arguments);
|
||||
exportedState.toyExtension = this.toyExtension;
|
||||
return exportedState;
|
||||
}
|
||||
|
||||
_importState(state) {
|
||||
super._importState(state);
|
||||
this.toyExtension = state.toyExtension;
|
||||
assert.step(JSON.stringify(state.toyExtension));
|
||||
}
|
||||
}
|
||||
|
||||
const ToyView = viewRegistry.get("toy");
|
||||
ToyView.SearchModel = SearchModelExtension;
|
||||
|
||||
class ToyExtension extends ActionModel.Extension {
|
||||
importState(state) {
|
||||
super.importState(state); // done even if state is undefined in legacy code
|
||||
assert.step(JSON.stringify(state) || "no state");
|
||||
}
|
||||
prepareState() {
|
||||
Object.assign(this.state, {
|
||||
locationId: "The place to be",
|
||||
});
|
||||
}
|
||||
}
|
||||
ActionModel.registry.add("toyExtension", ToyExtension);
|
||||
|
||||
const LegacyToyView = legacyViewRegistry.get("legacy_toy");
|
||||
|
||||
const LegacyToyViewWithExtension = LegacyToyView.extend({
|
||||
_createSearchModel(params, extraExtensions = {}) {
|
||||
Object.assign(extraExtensions, { toyExtension: {} });
|
||||
return this._super(params, extraExtensions);
|
||||
},
|
||||
});
|
||||
legacyViewRegistry.add("legacy_toy_with_extension", LegacyToyViewWithExtension);
|
||||
|
||||
const webClient = await createWebClient({ serverData });
|
||||
|
||||
await doAction(webClient, {
|
||||
name: "Action name",
|
||||
res_model: "foo",
|
||||
type: "ir.actions.act_window",
|
||||
views: [
|
||||
[false, "toy"],
|
||||
[1, "legacy_toy"],
|
||||
],
|
||||
});
|
||||
|
||||
assert.containsOnce(target, ".o_switch_view.o_toy.active");
|
||||
|
||||
await switchView(target, "legacy_toy");
|
||||
await legacyExtraNextTick();
|
||||
|
||||
assert.containsOnce(target, ".o_switch_view.o_legacy_toy.active");
|
||||
assert.verifySteps([`{"locationId":"Grand-Rosière"}`]);
|
||||
|
||||
await switchView(target, "toy");
|
||||
|
||||
assert.containsOnce(target, ".o_switch_view.o_toy.active");
|
||||
assert.verifySteps([`{"locationId":"Grand-Rosière"}`]);
|
||||
|
||||
await doAction(webClient, {
|
||||
name: "Action name",
|
||||
res_model: "foo",
|
||||
type: "ir.actions.act_window",
|
||||
views: [
|
||||
[1, "legacy_toy"],
|
||||
[false, "toy"],
|
||||
],
|
||||
});
|
||||
await legacyExtraNextTick();
|
||||
|
||||
assert.containsOnce(target, ".o_switch_view.o_legacy_toy.active");
|
||||
assert.verifySteps([`no state`]);
|
||||
|
||||
await switchView(target, "toy");
|
||||
|
||||
assert.containsOnce(target, ".o_switch_view.o_toy.active");
|
||||
assert.verifySteps([`{"locationId":"The place to be"}`]);
|
||||
|
||||
await switchView(target, "legacy_toy");
|
||||
await legacyExtraNextTick();
|
||||
|
||||
assert.containsOnce(target, ".o_switch_view.o_legacy_toy.active");
|
||||
assert.verifySteps([`{"locationId":"The place to be"}`]);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
@ -0,0 +1,772 @@
|
|||
odoo.define('web.view_dialogs_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var dialogs = require('web.view_dialogs');
|
||||
var ListController = require('web.ListController');
|
||||
var testUtils = require('web.test_utils');
|
||||
var Widget = require('web.Widget');
|
||||
var FormView = require('web.FormView');
|
||||
|
||||
const { browser } = require('@web/core/browser/browser');
|
||||
const { patchWithCleanup } = require('@web/../tests/helpers/utils');
|
||||
const cpHelpers = require('@web/../tests/search/helpers');
|
||||
var createView = testUtils.createView;
|
||||
|
||||
async function createParent(params) {
|
||||
var widget = new Widget();
|
||||
params.server = await testUtils.mock.addMockEnvironment(widget, params);
|
||||
return widget;
|
||||
}
|
||||
|
||||
QUnit.module('LegacyViews', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
partner: {
|
||||
fields: {
|
||||
display_name: { string: "Displayed name", type: "char" },
|
||||
foo: {string: "Foo", type: 'char'},
|
||||
bar: {string: "Bar", type: "boolean"},
|
||||
instrument: {string: 'Instruments', type: 'many2one', relation: 'instrument'},
|
||||
},
|
||||
records: [
|
||||
{id: 1, foo: 'blip', display_name: 'blipblip', bar: true},
|
||||
{id: 2, foo: 'ta tata ta ta', display_name: 'macgyver', bar: false},
|
||||
{id: 3, foo: 'piou piou', display_name: "Jack O'Neill", bar: true},
|
||||
],
|
||||
},
|
||||
instrument: {
|
||||
fields: {
|
||||
name: {string: "name", type: "char"},
|
||||
badassery: {string: 'level', type: 'many2many', relation: 'badassery', domain: [['level', '=', 'Awsome']]},
|
||||
},
|
||||
},
|
||||
|
||||
badassery: {
|
||||
fields: {
|
||||
level: {string: 'level', type: "char"},
|
||||
},
|
||||
records: [
|
||||
{id: 1, level: 'Awsome'},
|
||||
],
|
||||
},
|
||||
|
||||
product: {
|
||||
fields : {
|
||||
name: {string: "name", type: "char" },
|
||||
partner : {string: 'Doors', type: 'one2many', relation: 'partner'},
|
||||
},
|
||||
records: [
|
||||
{id: 1, name: 'The end'},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
}, function () {
|
||||
|
||||
QUnit.module('ViewDialog (legacy)');
|
||||
|
||||
QUnit.test('formviewdialog buttons in footer are positioned properly', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var parent = await createParent({
|
||||
data: this.data,
|
||||
archs: {
|
||||
'partner,false,form':
|
||||
'<form string="Partner">' +
|
||||
'<sheet>' +
|
||||
'<group><field name="foo"/></group>' +
|
||||
'<footer><button string="Custom Button" type="object" class="btn-primary"/></footer>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
},
|
||||
});
|
||||
|
||||
new dialogs.FormViewDialog(parent, {
|
||||
res_model: 'partner',
|
||||
res_id: 1,
|
||||
}).open();
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.notOk($('.modal-body button').length,
|
||||
"should not have any button in body");
|
||||
assert.strictEqual($('.modal-footer button').length, 1,
|
||||
"should have only one button in footer");
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
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 = [];
|
||||
|
||||
var parent = await createParent({
|
||||
data: this.data,
|
||||
archs: {
|
||||
'partner,false,form':
|
||||
'<form string="Partner">' +
|
||||
'<field name="poney_ids"><tree editable="top"><field name="display_name"/></tree></field>' +
|
||||
'<footer><button string="Custom Button" type="object" class="btn-primary"/></footer>' +
|
||||
'</form>',
|
||||
},
|
||||
});
|
||||
|
||||
new dialogs.FormViewDialog(parent, {
|
||||
res_model: 'partner',
|
||||
res_id: 1,
|
||||
}).open();
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.strictEqual($('.modal button.btn-primary').length, 1,
|
||||
"should have 1 buttons in modal");
|
||||
|
||||
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', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
var search = 0;
|
||||
var parent = await createParent({
|
||||
data: this.data,
|
||||
archs: {
|
||||
'partner,false,list':
|
||||
'<tree string="Partner">' +
|
||||
'<field name="display_name"/>' +
|
||||
'<field name="foo"/>' +
|
||||
'</tree>',
|
||||
'partner,false,search':
|
||||
'<search>' +
|
||||
'<field name="foo" filter_domain="[(\'display_name\',\'ilike\',self), (\'foo\',\'ilike\',self)]"/>' +
|
||||
'<group expand="0" string="Group By">' +
|
||||
'<filter name="groupby_bar" context="{\'group_by\' : \'bar\'}"/>' +
|
||||
'</group>' +
|
||||
'</search>',
|
||||
},
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'web_read_group') {
|
||||
assert.deepEqual(args.kwargs, {
|
||||
context: {
|
||||
search_default_foo: "piou",
|
||||
search_default_groupby_bar: true,
|
||||
},
|
||||
domain: ["&", ["display_name", "like", "a"], "&", ["display_name", "ilike", "piou"], ["foo", "ilike", "piou"]],
|
||||
fields: ["display_name", "foo", "bar"],
|
||||
groupby: ["bar"],
|
||||
orderby: '',
|
||||
lazy: true,
|
||||
limit: 80,
|
||||
}, "should search with the complete domain (domain + search), and group by 'bar'");
|
||||
}
|
||||
if (search === 0 && route === '/web/dataset/search_read') {
|
||||
search++;
|
||||
assert.deepEqual(args, {
|
||||
context: {
|
||||
search_default_foo: "piou",
|
||||
search_default_groupby_bar: true,
|
||||
bin_size: true
|
||||
}, // not part of the test, may change
|
||||
domain: ["&", ["display_name", "like", "a"], "&", ["display_name", "ilike", "piou"], ["foo", "ilike", "piou"]],
|
||||
fields: ["display_name", "foo"],
|
||||
model: "partner",
|
||||
limit: 80,
|
||||
sort: ""
|
||||
}, "should search with the complete domain (domain + search)");
|
||||
} else if (search === 1 && route === '/web/dataset/search_read') {
|
||||
assert.deepEqual(args, {
|
||||
context: {
|
||||
search_default_foo: "piou",
|
||||
search_default_groupby_bar: true,
|
||||
bin_size: true
|
||||
}, // not part of the test, may change
|
||||
domain: [["display_name", "like", "a"]],
|
||||
fields: ["display_name", "foo"],
|
||||
model: "partner",
|
||||
limit: 80,
|
||||
sort: ""
|
||||
}, "should search with the domain");
|
||||
}
|
||||
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
new dialogs.SelectCreateDialog(parent, {
|
||||
no_create: true,
|
||||
readonly: true,
|
||||
res_model: 'partner',
|
||||
domain: [['display_name', 'like', 'a']],
|
||||
context: {
|
||||
search_default_groupby_bar: true,
|
||||
search_default_foo: 'piou',
|
||||
},
|
||||
}).open();
|
||||
await testUtils.nextTick();
|
||||
const modal = document.body.querySelector(".modal");
|
||||
await cpHelpers.removeFacet(modal, "Bar");
|
||||
await cpHelpers.removeFacet(modal);
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('SelectCreateDialog correctly evaluates domains', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var parent = await createParent({
|
||||
data: this.data,
|
||||
archs: {
|
||||
'partner,false,list':
|
||||
'<tree string="Partner">' +
|
||||
'<field name="display_name"/>' +
|
||||
'<field name="foo"/>' +
|
||||
'</tree>',
|
||||
'partner,false,search':
|
||||
'<search>' +
|
||||
'<field name="foo"/>' +
|
||||
'</search>',
|
||||
},
|
||||
mockRPC: function (route, args) {
|
||||
if (route === '/web/dataset/search_read') {
|
||||
assert.deepEqual(args.domain, [['id', '=', 2]],
|
||||
"should have correctly evaluated the domain");
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
session: {
|
||||
user_context: {uid: 2},
|
||||
},
|
||||
});
|
||||
|
||||
new dialogs.SelectCreateDialog(parent, {
|
||||
no_create: true,
|
||||
readonly: true,
|
||||
res_model: 'partner',
|
||||
domain: "[['id', '=', uid]]",
|
||||
}).open();
|
||||
await testUtils.nextTick();
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('SelectCreateDialog list view in readonly', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var parent = await createParent({
|
||||
data: this.data,
|
||||
archs: {
|
||||
'partner,false,list':
|
||||
'<tree string="Partner" editable="bottom">' +
|
||||
'<field name="display_name"/>' +
|
||||
'<field name="foo"/>' +
|
||||
'</tree>',
|
||||
'partner,false,search':
|
||||
'<search/>'
|
||||
},
|
||||
});
|
||||
|
||||
var dialog;
|
||||
new dialogs.SelectCreateDialog(parent, {
|
||||
res_model: 'partner',
|
||||
}).open().then(function (result) {
|
||||
dialog = result;
|
||||
});
|
||||
await testUtils.nextTick();
|
||||
|
||||
// click on the first row to see if the list is editable
|
||||
await testUtils.dom.click(dialog.$('.o_legacy_list_view tbody tr:first td:not(.o_list_record_selector):first'));
|
||||
|
||||
assert.equal(dialog.$('.o_legacy_list_view tbody tr:first td:not(.o_list_record_selector):first input').length, 0,
|
||||
"list view should not be editable in a SelectCreateDialog");
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('SelectCreateDialog cascade x2many in create mode', async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
var form = await createView({
|
||||
View: FormView,
|
||||
model: 'product',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<field name="name"/>' +
|
||||
'<field name="partner" widget="one2many" >' +
|
||||
'<tree editable="top">' +
|
||||
'<field name="display_name"/>' +
|
||||
'<field name="instrument"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
archs: {
|
||||
'partner,false,form': '<form>' +
|
||||
'<field name="name"/>' +
|
||||
'<field name="instrument" widget="one2many" mode="tree"/>' +
|
||||
'</form>',
|
||||
|
||||
'instrument,false,form': '<form>'+
|
||||
'<field name="name"/>'+
|
||||
'<field name="badassery">' +
|
||||
'<tree>'+
|
||||
'<field name="level"/>'+
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</form>',
|
||||
|
||||
'badassery,false,list': '<tree>'+
|
||||
'<field name="level"/>'+
|
||||
'</tree>',
|
||||
|
||||
'badassery,false,search': '<search>'+
|
||||
'<field name="level"/>'+
|
||||
'</search>',
|
||||
},
|
||||
|
||||
mockRPC: function(route, args) {
|
||||
if (route === '/web/dataset/call_kw/partner/get_formview_id') {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
if (route === '/web/dataset/call_kw/instrument/get_formview_id') {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
if (route === '/web/dataset/call_kw/instrument/create') {
|
||||
assert.deepEqual(args.args, [{badassery: [[6, false, [1]]], name: "ABC"}],
|
||||
'The method create should have been called with the right arguments');
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
});
|
||||
|
||||
await testUtils.form.clickEdit(form);
|
||||
await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a'));
|
||||
await testUtils.fields.many2one.createAndEdit("instrument");
|
||||
|
||||
var $modal = $('.modal-lg');
|
||||
|
||||
assert.equal($modal.length, 1,
|
||||
'There should be one modal');
|
||||
|
||||
await testUtils.dom.click($modal.find('.o_field_x2many_list_row_add a'));
|
||||
|
||||
var $modals = $('.modal-lg');
|
||||
|
||||
assert.equal($modals.length, 2,
|
||||
'There should be two modals');
|
||||
|
||||
var $second_modal = $modals.not($modal);
|
||||
await testUtils.dom.click($second_modal.find('.o_list_table.table.table-sm.table-striped.o_list_table_ungrouped .o_data_row input[type=checkbox]'));
|
||||
|
||||
await testUtils.dom.click($second_modal.find('.o_select_button'));
|
||||
|
||||
$modal = $('.modal-lg');
|
||||
|
||||
assert.equal($modal.length, 1,
|
||||
'There should be one modal');
|
||||
|
||||
assert.equal($modal.find('.o_data_cell').text(), 'Awsome',
|
||||
'There should be one item in the list of the modal');
|
||||
|
||||
await testUtils.dom.click($modal.find('.btn.btn-primary'));
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
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 = await createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<field name="name"/>' +
|
||||
'<field name="instrument" context="{\'tree_view_ref\': \'some_tree_view\'}"/>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
archs: {
|
||||
'instrument,false,form': '<form>'+
|
||||
'<field name="name"/>'+
|
||||
'<field name="badassery" context="{\'tree_view_ref\': \'some_other_tree_view\'}"/>' +
|
||||
'</form>',
|
||||
|
||||
'badassery,false,list': '<tree>'+
|
||||
'<field name="level"/>'+
|
||||
'</tree>',
|
||||
},
|
||||
viewOptions: {
|
||||
mode: 'edit',
|
||||
},
|
||||
|
||||
mockRPC: function(route, args) {
|
||||
if (args.method === 'get_formview_id') {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
|
||||
interceptsPropagate: {
|
||||
load_views: function (ev) {
|
||||
var evaluatedContext = ev.data.context;
|
||||
if (ev.data.modelName === 'instrument') {
|
||||
assert.deepEqual(evaluatedContext, {tree_view_ref: 'some_tree_view'},
|
||||
'The correct _view_ref should have been sent to the server, first time');
|
||||
}
|
||||
if (ev.data.modelName === 'badassery') {
|
||||
assert.deepEqual(evaluatedContext, {
|
||||
base_model_name: 'instrument',
|
||||
tree_view_ref: 'some_other_tree_view',
|
||||
}, 'The correct _view_ref should have been sent to the server for the subview');
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await testUtils.dom.click(form.$('.o_field_widget[name="instrument"] button.o_external_button'));
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test("Form dialog replaces the context with _createContext method when specified", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
const parent = await createParent({
|
||||
data: this.data,
|
||||
archs: {
|
||||
"partner,false,form":
|
||||
`<form string="Partner">
|
||||
<sheet>
|
||||
<group><field name="foo"/></group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
},
|
||||
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === "create") {
|
||||
assert.step(JSON.stringify(args.kwargs.context));
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
});
|
||||
|
||||
new dialogs.FormViewDialog(parent, {
|
||||
res_model: "partner",
|
||||
context: { answer: 42 },
|
||||
_createContext: () => ({ dolphin: 64 }),
|
||||
}).open();
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.notOk($(".modal-body button").length,
|
||||
"should not have any button in body");
|
||||
assert.strictEqual($(".modal-footer button").length, 3,
|
||||
"should have 3 buttons in footer");
|
||||
|
||||
await testUtils.dom.click($(".modal-footer button:contains(Save & New)"));
|
||||
await testUtils.dom.click($(".modal-footer button:contains(Save & New)"));
|
||||
assert.verifySteps(['{"answer":42}', '{"dolphin":64}']);
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test("Form dialog keeps full context when no _createContext is specified", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
const parent = await createParent({
|
||||
data: this.data,
|
||||
archs: {
|
||||
"partner,false,form":
|
||||
`<form string="Partner">
|
||||
<sheet>
|
||||
<group><field name="foo"/></group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
},
|
||||
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === "create") {
|
||||
assert.step(JSON.stringify(args.kwargs.context));
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
});
|
||||
|
||||
new dialogs.FormViewDialog(parent, {
|
||||
res_model: "partner",
|
||||
context: { answer: 42 }
|
||||
}).open();
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.notOk($(".modal-body button").length,
|
||||
"should not have any button in body");
|
||||
assert.strictEqual($(".modal-footer button").length, 3,
|
||||
"should have 3 buttons in footer");
|
||||
|
||||
await testUtils.dom.click($(".modal-footer button:contains(Save & New)"));
|
||||
await testUtils.dom.click($(".modal-footer button:contains(Save & New)"));
|
||||
assert.verifySteps(['{"answer":42}', '{"answer":42}']);
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('SelectCreateDialog: save current search', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
testUtils.mock.patch(ListController, {
|
||||
getOwnedQueryParams: function () {
|
||||
return {
|
||||
context: {
|
||||
shouldBeInFilterContext: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// save favorite needs this
|
||||
patchWithCleanup(browser, {
|
||||
setTimeout: (fn) => fn(),
|
||||
});
|
||||
|
||||
var parent = await createParent({
|
||||
data: this.data,
|
||||
archs: {
|
||||
'partner,false,list':
|
||||
'<tree>' +
|
||||
'<field name="display_name"/>' +
|
||||
'</tree>',
|
||||
'partner,false,search':
|
||||
'<search>' +
|
||||
'<filter name="bar" help="Bar" domain="[(\'bar\', \'=\', True)]"/>' +
|
||||
'</search>',
|
||||
|
||||
},
|
||||
env: {
|
||||
dataManager: {
|
||||
create_filter: function (filter) {
|
||||
assert.strictEqual(filter.domain, `[("bar", "=", True)]`,
|
||||
"should save the correct domain");
|
||||
const expectedContext = {
|
||||
group_by: [], // default groupby is an empty list
|
||||
shouldBeInFilterContext: true,
|
||||
};
|
||||
assert.deepEqual(filter.context, expectedContext,
|
||||
"should save the correct context");
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
var dialog;
|
||||
new dialogs.SelectCreateDialog(parent, {
|
||||
context: {shouldNotBeInFilterContext: false},
|
||||
res_model: 'partner',
|
||||
}).open().then(function (result) {
|
||||
dialog = result;
|
||||
});
|
||||
await testUtils.nextTick();
|
||||
|
||||
|
||||
assert.containsN(dialog, '.o_data_row', 3, "should contain 3 records");
|
||||
|
||||
// filter on bar
|
||||
const modal = document.body.querySelector(".modal");
|
||||
await cpHelpers.toggleFilterMenu(modal);
|
||||
await cpHelpers.toggleMenuItem(modal, "Bar");
|
||||
|
||||
assert.containsN(dialog, '.o_data_row', 2, "should contain 2 records");
|
||||
|
||||
// save filter
|
||||
await cpHelpers.toggleFavoriteMenu(modal);
|
||||
await cpHelpers.toggleSaveFavorite(modal);
|
||||
await cpHelpers.editFavoriteName(modal, "some name");
|
||||
await cpHelpers.saveFavorite(modal);
|
||||
|
||||
testUtils.mock.unpatch(ListController);
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('SelectCreateDialog calls on_selected with every record matching the domain', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const parent = await createParent({
|
||||
data: this.data,
|
||||
archs: {
|
||||
'partner,false,list':
|
||||
'<tree limit="2" string="Partner">' +
|
||||
'<field name="display_name"/>' +
|
||||
'<field name="foo"/>' +
|
||||
'</tree>',
|
||||
'partner,false,search':
|
||||
'<search>' +
|
||||
'<field name="foo"/>' +
|
||||
'</search>',
|
||||
},
|
||||
session: {},
|
||||
});
|
||||
|
||||
new dialogs.SelectCreateDialog(parent, {
|
||||
res_model: 'partner',
|
||||
on_selected: function(records) {
|
||||
assert.equal(records.length, 3);
|
||||
assert.strictEqual(records.map((r) => r.display_name).toString(), "blipblip,macgyver,Jack O'Neill");
|
||||
assert.strictEqual(records.map((r) => r.id).toString(), "1,2,3");
|
||||
}
|
||||
}).open();
|
||||
await testUtils.nextTick();
|
||||
|
||||
await testUtils.dom.click($('thead .o_list_record_selector input'));
|
||||
await testUtils.dom.click($('.o_list_selection_box .o_list_select_domain'));
|
||||
await testUtils.dom.click($('.modal .o_select_button'));
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('SelectCreateDialog calls on_selected with every record matching without selecting a domain', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const parent = await createParent({
|
||||
data: this.data,
|
||||
archs: {
|
||||
'partner,false,list':
|
||||
'<tree limit="2" string="Partner">' +
|
||||
'<field name="display_name"/>' +
|
||||
'<field name="foo"/>' +
|
||||
'</tree>',
|
||||
'partner,false,search':
|
||||
'<search>' +
|
||||
'<field name="foo"/>' +
|
||||
'</search>',
|
||||
},
|
||||
session: {},
|
||||
});
|
||||
|
||||
new dialogs.SelectCreateDialog(parent, {
|
||||
res_model: 'partner',
|
||||
on_selected: function(records) {
|
||||
assert.equal(records.length, 2);
|
||||
assert.strictEqual(records.map((r) => r.display_name).toString(), "blipblip,macgyver");
|
||||
assert.strictEqual(records.map((r) => r.id).toString(), "1,2");
|
||||
}
|
||||
}).open();
|
||||
await testUtils.nextTick();
|
||||
|
||||
await testUtils.dom.click($('thead .o_list_record_selector input'));
|
||||
await testUtils.dom.click($('.o_list_selection_box '));
|
||||
await testUtils.dom.click($('.modal .o_select_button'));
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('propagate can_create onto the search popup o2m', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
this.data.instrument.records = [
|
||||
{id: 1, name: 'Tromblon1'},
|
||||
{id: 2, name: 'Tromblon2'},
|
||||
{id: 3, name: 'Tromblon3'},
|
||||
{id: 4, name: 'Tromblon4'},
|
||||
{id: 5, name: 'Tromblon5'},
|
||||
{id: 6, name: 'Tromblon6'},
|
||||
{id: 7, name: 'Tromblon7'},
|
||||
{id: 8, name: 'Tromblon8'},
|
||||
];
|
||||
|
||||
var form = await createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form>' +
|
||||
'<field name="name"/>' +
|
||||
'<field name="instrument" can_create="false"/>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
archs: {
|
||||
'instrument,false,list': '<tree>'+
|
||||
'<field name="name"/>'+
|
||||
'</tree>',
|
||||
'instrument,false,search': '<search>'+
|
||||
'<field name="name"/>'+
|
||||
'</search>',
|
||||
},
|
||||
viewOptions: {
|
||||
mode: 'edit',
|
||||
},
|
||||
|
||||
mockRPC: function(route, args) {
|
||||
if (args.method === 'get_formview_id') {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
});
|
||||
|
||||
await testUtils.fields.many2one.clickOpenDropdown('instrument');
|
||||
|
||||
assert.containsNone(form, '.ui-autocomplete a:contains(Start typing...)');
|
||||
|
||||
await testUtils.fields.editInput(form.el.querySelector(".o_field_many2one[name=instrument] input"), "a");
|
||||
|
||||
assert.containsNone(form, '.ui-autocomplete a:contains(Create and Edit)');
|
||||
|
||||
await testUtils.fields.editInput(form.el.querySelector(".o_field_many2one[name=instrument] input"), "");
|
||||
await testUtils.fields.many2one.clickItem('instrument', 'Search More...');
|
||||
|
||||
var $modal = $('.modal-dialog.modal-lg');
|
||||
|
||||
assert.strictEqual($modal.length, 1, 'Modal present');
|
||||
|
||||
assert.strictEqual($modal.find('.modal-footer button').text(), "Cancel",
|
||||
'Only the cancel button is present in modal');
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('formviewdialog is not closed when button handlers return a rejected promise', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
this.data.partner.fields.poney_ids = { string: "Poneys", type: "one2many", relation: 'partner' };
|
||||
this.data.partner.records[0].poney_ids = [];
|
||||
var reject = true;
|
||||
|
||||
var parent = await createParent({
|
||||
data: this.data,
|
||||
archs: {
|
||||
'partner,false,form':
|
||||
'<form string="Partner">' +
|
||||
'<field name="poney_ids"><tree><field name="display_name"/></tree></field>' +
|
||||
'</form>',
|
||||
},
|
||||
});
|
||||
|
||||
new dialogs.FormViewDialog(parent, {
|
||||
res_model: 'partner',
|
||||
res_id: 1,
|
||||
buttons: [{
|
||||
text: 'Click me !',
|
||||
classes: "btn-secondary o_form_button_magic",
|
||||
close: true,
|
||||
click: function () {
|
||||
return reject ? Promise.reject() : Promise.resolve();
|
||||
},
|
||||
}],
|
||||
}).open();
|
||||
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual($('.modal').length, 1, "should have a modal displayed");
|
||||
|
||||
await testUtils.dom.click($('.modal .o_form_button_magic'));
|
||||
assert.strictEqual($('.modal').length, 1, "modal should still be opened");
|
||||
|
||||
reject = false;
|
||||
await testUtils.dom.click($('.modal .o_form_button_magic'));
|
||||
assert.strictEqual($('.modal').length, 0, "modal should be closed");
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue