summaryrefslogtreecommitdiff
path: root/addons/web/static/tests/views
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/tests/views')
-rw-r--r--addons/web/static/tests/views/abstract_model_tests.js130
-rw-r--r--addons/web/static/tests/views/basic_model_tests.js14
-rw-r--r--addons/web/static/tests/views/kanban_tests.js525
-rw-r--r--addons/web/static/tests/views/list_tests.js279
-rw-r--r--addons/web/static/tests/views/sample_server_tests.js461
5 files changed, 1401 insertions, 8 deletions
diff --git a/addons/web/static/tests/views/abstract_model_tests.js b/addons/web/static/tests/views/abstract_model_tests.js
new file mode 100644
index 00000000000..544a908b063
--- /dev/null
+++ b/addons/web/static/tests/views/abstract_model_tests.js
@@ -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('Views', {}, 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"
+ );
+ });
+ });
+});
diff --git a/addons/web/static/tests/views/basic_model_tests.js b/addons/web/static/tests/views/basic_model_tests.js
index 3d28a32cf2e..f57478e1252 100644
--- a/addons/web/static/tests/views/basic_model_tests.js
+++ b/addons/web/static/tests/views/basic_model_tests.js
@@ -1309,7 +1309,7 @@ odoo.define('web.basic_model_tests', function (require) {
res_id: 1,
};
- model.load(params).then(function (resultID) {
+ await model.load(params).then(function (resultID) {
var record = model.get(resultID);
assert.ok(record.data.date instanceof moment,
"fetched date field should have been formatted");
@@ -1317,7 +1317,7 @@ odoo.define('web.basic_model_tests', function (require) {
params.res_id = 2;
- model.load(params).then(function (resultID) {
+ await model.load(params).then(function (resultID) {
var record = model.get(resultID);
assert.strictEqual(record.data.date, false,
"unset date field should be false");
@@ -1340,7 +1340,7 @@ odoo.define('web.basic_model_tests', function (require) {
type: 'list',
};
- model.load(params).then(function (resultID) {
+ await model.load(params).then(function (resultID) {
var record = model.get(resultID);
var firstRecord = record.data[0];
var secondRecord = record.data[1];
@@ -1367,7 +1367,7 @@ odoo.define('web.basic_model_tests', function (require) {
type: 'record',
};
- model.load(params).then(function (resultID) {
+ await model.load(params).then(function (resultID) {
var record = model.get(resultID);
assert.strictEqual(record.data.date, false, "date default value should be false");
});
@@ -1391,7 +1391,7 @@ odoo.define('web.basic_model_tests', function (require) {
type: 'record',
};
- model.load(params).then(function (resultID) {
+ await model.load(params).then(function (resultID) {
var record = model.get(resultID);
assert.ok(_.isEqual(record.data.category.res_ids, [12, 14]),
"category field should have correct default value");
@@ -1958,7 +1958,7 @@ odoo.define('web.basic_model_tests', function (require) {
},
});
- model.load(this.params);
+ await model.load(this.params);
assert.verifySteps(['read'],
"there should be only one read");
model.destroy();
@@ -1996,7 +1996,7 @@ odoo.define('web.basic_model_tests', function (require) {
data: this.data,
});
- model.load(params).then(function (resultID) {
+ await model.load(params).then(function (resultID) {
var record = model.get(resultID);
assert.strictEqual(record.data.product_ids.data[0].data.date, false,
"date value should be in data, and should be false");
diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js
index 795d3b54324..ec9bc62d6fc 100644
--- a/addons/web/static/tests/views/kanban_tests.js
+++ b/addons/web/static/tests/views/kanban_tests.js
@@ -2,6 +2,7 @@ odoo.define('web.kanban_tests', function (require) {
"use strict";
var AbstractField = require('web.AbstractField');
+const Domain = require('web.Domain');
var fieldRegistry = require('web.field_registry');
var KanbanColumnProgressBar = require('web.KanbanColumnProgressBar');
var kanbanExamplesRegistry = require('web.kanban_examples_registry');
@@ -4045,6 +4046,529 @@ QUnit.module('Views', {
kanban.destroy();
});
+ QUnit.test('empty grouped kanban with sample data and no columns', async function (assert) {
+ assert.expect(3);
+
+ this.data.partner.records = [];
+
+ const kanban = await createView({
+ arch: `
+ <kanban sample="1">
+ <field name="product_id"/>
+ <templates>
+ <div t-name="kanban-box">
+ <field name="foo"/>
+ </div>
+ </templates>
+ </kanban>`,
+ data: this.data,
+ groupBy: ['product_id'],
+ model: 'partner',
+ View: KanbanView,
+ viewOptions: {
+ action: {
+ help: "No content helper",
+ },
+ },
+ });
+
+ assert.containsNone(kanban, '.o_view_nocontent');
+ assert.containsOnce(kanban, '.o_quick_create_unfolded');
+ assert.containsOnce(kanban, '.o_kanban_example_background_container');
+
+ kanban.destroy();
+ });
+
+ QUnit.test('empty grouped kanban with sample data and click quick create', async function (assert) {
+ assert.expect(11);
+
+ const kanban = await createView({
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch: `
+ <kanban sample="1">
+ <field name="product_id"/>
+ <templates>
+ <t t-name="kanban-box">
+ <div><field name="foo"/></div>
+ </t>
+ </templates>
+ </kanban>`,
+ groupBy: ['product_id'],
+ async mockRPC(route, { kwargs, method }) {
+ const result = await this._super(...arguments);
+ if (method === 'web_read_group') {
+ // override read_group to return empty groups, as this is
+ // the case for several models (e.g. project.task grouped
+ // by stage_id)
+ result.groups.forEach(group => {
+ group[`${kwargs.groupby[0]}_count`] = 0;
+ });
+ }
+ return result;
+ },
+ viewOptions: {
+ action: {
+ help: "No content helper",
+ },
+ },
+ });
+
+ assert.containsN(kanban, '.o_kanban_group', 2,
+ "there should be two columns");
+ assert.hasClass(kanban.$el, 'o_view_sample_data');
+ assert.containsOnce(kanban, '.o_view_nocontent');
+ assert.containsN(kanban, '.o_kanban_record', 16,
+ "there should be 8 sample records by column");
+
+ await testUtils.dom.click(kanban.$('.o_kanban_quick_add:first'));
+ assert.doesNotHaveClass(kanban.$el, 'o_view_sample_data');
+ assert.containsNone(kanban, '.o_kanban_record');
+ assert.containsNone(kanban, '.o_view_nocontent');
+ assert.containsOnce(kanban.$('.o_kanban_group:first'), '.o_kanban_quick_create');
+
+ await testUtils.fields.editInput(kanban.$('.o_kanban_quick_create .o_input'), 'twilight sparkle');
+ await testUtils.dom.click(kanban.$('.o_kanban_quick_create button.o_kanban_add'));
+
+ assert.doesNotHaveClass(kanban.$el, 'o_view_sample_data');
+ assert.containsOnce(kanban.$('.o_kanban_group:first'), '.o_kanban_record');
+ assert.containsNone(kanban, '.o_view_nocontent');
+
+ kanban.destroy();
+ });
+
+ QUnit.test('empty grouped kanban with sample data and cancel quick create', async function (assert) {
+ assert.expect(12);
+
+ const kanban = await createView({
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch: `
+ <kanban sample="1">
+ <field name="product_id"/>
+ <templates>
+ <t t-name="kanban-box">
+ <div><field name="foo"/></div>
+ </t>
+ </templates>
+ </kanban>`,
+ groupBy: ['product_id'],
+ async mockRPC(route, { kwargs, method }) {
+ const result = await this._super(...arguments);
+ if (method === 'web_read_group') {
+ // override read_group to return empty groups, as this is
+ // the case for several models (e.g. project.task grouped
+ // by stage_id)
+ result.groups.forEach(group => {
+ group[`${kwargs.groupby[0]}_count`] = 0;
+ });
+ }
+ return result;
+ },
+ viewOptions: {
+ action: {
+ help: "No content helper",
+ },
+ },
+ });
+
+ assert.containsN(kanban, '.o_kanban_group', 2,
+ "there should be two columns");
+ assert.hasClass(kanban.$el, 'o_view_sample_data');
+ assert.containsOnce(kanban, '.o_view_nocontent');
+ assert.containsN(kanban, '.o_kanban_record', 16,
+ "there should be 8 sample records by column");
+
+ await testUtils.dom.click(kanban.$('.o_kanban_quick_add:first'));
+ assert.doesNotHaveClass(kanban.$el, 'o_view_sample_data');
+ assert.containsNone(kanban, '.o_kanban_record');
+ assert.containsNone(kanban, '.o_view_nocontent');
+ assert.containsOnce(kanban.$('.o_kanban_group:first'), '.o_kanban_quick_create');
+
+ await testUtils.dom.click(kanban.$('.o_kanban_view'));
+ assert.doesNotHaveClass(kanban.$el, 'o_view_sample_data');
+ assert.containsNone(kanban, '.o_kanban_quick_create');
+ assert.containsNone(kanban, '.o_kanban_record');
+ assert.containsOnce(kanban, '.o_view_nocontent');
+
+ kanban.destroy();
+ });
+
+ QUnit.test('empty kanban with sample data', async function (assert) {
+ assert.expect(6);
+
+ this.data.partner.records = [];
+
+ const kanban = await createView({
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch: `
+ <kanban sample="1">
+ <field name="product_id"/>
+ <templates>
+ <t t-name="kanban-box">
+ <div><field name="foo"/></div>
+ </t>
+ </templates>
+ </kanban>`,
+ viewOptions: {
+ action: {
+ help: "No content helper",
+ },
+ },
+ });
+
+ assert.hasClass(kanban.$el, 'o_view_sample_data');
+ assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 10,
+ "there should be 10 sample records");
+ assert.containsOnce(kanban, '.o_view_nocontent');
+
+ await kanban.reload({ domain: [['id', '<', 0]]});
+ assert.doesNotHaveClass(kanban.$el, 'o_view_sample_data');
+ assert.containsNone(kanban, '.o_kanban_record:not(.o_kanban_ghost)');
+ assert.containsOnce(kanban, '.o_view_nocontent');
+
+ kanban.destroy();
+ });
+
+ QUnit.test('empty grouped kanban with sample data and many2many_tags', async function (assert) {
+ assert.expect(6);
+
+ const kanban = await createView({
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch: `
+ <kanban sample="1">
+ <field name="product_id"/>
+ <templates>
+ <t t-name="kanban-box">
+ <div>
+ <field name="int_field"/>
+ <field name="category_ids" widget="many2many_tags"/>
+ </div>
+ </t>
+ </templates>
+ </kanban>`,
+ groupBy: ['product_id'],
+ async mockRPC(route, { kwargs, method }) {
+ assert.step(method || route);
+ const result = await this._super(...arguments);
+ if (method === 'web_read_group') {
+ // override read_group to return empty groups, as this is
+ // the case for several models (e.g. project.task grouped
+ // by stage_id)
+ result.groups.forEach(group => {
+ group[`${kwargs.groupby[0]}_count`] = 0;
+ });
+ }
+ return result;
+ },
+ });
+
+ assert.containsN(kanban, '.o_kanban_group', 2, "there should be 2 'real' columns");
+ assert.hasClass(kanban.$el, 'o_view_sample_data');
+ assert.ok(kanban.$('.o_kanban_record').length >= 1, "there should be sample records");
+ assert.ok(kanban.$('.o_field_many2manytags .o_tag').length >= 1, "there should be tags");
+
+ assert.verifySteps(["web_read_group"], "should not read the tags");
+ kanban.destroy();
+ });
+
+ QUnit.test('sample data does not change after reload with sample data', async function (assert) {
+ assert.expect(4);
+
+ const kanban = await createView({
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch: `
+ <kanban sample="1">
+ <field name="product_id"/>
+ <templates>
+ <t t-name="kanban-box">
+ <div><field name="int_field"/></div>
+ </t>
+ </templates>
+ </kanban>`,
+ groupBy: ['product_id'],
+ async mockRPC(route, { kwargs, method }) {
+ const result = await this._super(...arguments);
+ if (method === 'web_read_group') {
+ // override read_group to return empty groups, as this is
+ // the case for several models (e.g. project.task grouped
+ // by stage_id)
+ result.groups.forEach(group => {
+ group[`${kwargs.groupby[0]}_count`] = 0;
+ });
+ }
+ return result;
+ },
+ });
+
+ const columns = kanban.el.querySelectorAll('.o_kanban_group');
+
+ assert.ok(columns.length >= 1, "there should be at least 1 sample column");
+ assert.hasClass(kanban.$el, 'o_view_sample_data');
+ assert.containsN(kanban, '.o_kanban_record', 16);
+
+ const kanbanText = kanban.el.innerText;
+ await kanban.reload();
+
+ assert.strictEqual(kanbanText, kanban.el.innerText,
+ "the content should be the same after reloading the view");
+
+ kanban.destroy();
+ });
+
+ QUnit.test('non empty kanban with sample data', async function (assert) {
+ assert.expect(5);
+
+ const kanban = await createView({
+ View: KanbanView,
+ model: 'partner',
+ data: this.data,
+ arch: `
+ <kanban sample="1">
+ <field name="product_id"/>
+ <templates>
+ <t t-name="kanban-box">
+ <div><field name="foo"/></div>
+ </t>
+ </templates>
+ </kanban>`,
+ viewOptions: {
+ action: {
+ help: "No content helper",
+ },
+ },
+ });
+
+ assert.doesNotHaveClass(kanban.$el, 'o_view_sample_data');
+ assert.containsN(kanban, '.o_kanban_record:not(.o_kanban_ghost)', 4);
+ assert.containsNone(kanban, '.o_view_nocontent');
+
+ await kanban.reload({ domain: [['id', '<', 0]]});
+ assert.doesNotHaveClass(kanban.$el, 'o_view_sample_data');
+ assert.containsNone(kanban, '.o_kanban_record:not(.o_kanban_ghost)');
+
+ kanban.destroy();
+ });
+
+ QUnit.test('empty grouped kanban with sample data: add a column', async function (assert) {
+ assert.expect(6);
+
+ const kanban = await createView({
+ arch: `
+ <kanban sample="1">
+ <field name="product_id"/>
+ <templates>
+ <div t-name="kanban-box">
+ <field name="foo"/>
+ </div>
+ </templates>
+ </kanban>`,
+ data: this.data,
+ groupBy: ['product_id'],
+ model: 'partner',
+ View: KanbanView,
+ async mockRPC(route, { method }) {
+ const result = await this._super(...arguments);
+ if (method === 'web_read_group') {
+ result.groups = this.data.product.records.map(r => {
+ return {
+ product_id: [r.id, r.display_name],
+ product_id_count: 0,
+ __domain: ['product_id', '=', r.id],
+ };
+ });
+ result.length = result.groups.length;
+ }
+ return result;
+ },
+ });
+
+ assert.hasClass(kanban, 'o_view_sample_data');
+ assert.containsN(kanban, '.o_kanban_group', 2);
+ assert.ok(kanban.$('.o_kanban_record').length > 0, 'should contain sample records');
+
+ await testUtils.dom.click(kanban.el.querySelector('.o_kanban_add_column'));
+ await testUtils.fields.editInput(kanban.el.querySelector('.o_kanban_header input'), "Yoohoo");
+ await testUtils.dom.click(kanban.el.querySelector('.btn.o_kanban_add'));
+
+ assert.hasClass(kanban, 'o_view_sample_data');
+ assert.containsN(kanban, '.o_kanban_group', 3);
+ assert.ok(kanban.$('.o_kanban_record').length > 0, 'should contain sample records');
+
+ kanban.destroy();
+ });
+
+ QUnit.skip('empty grouped kanban with sample data: fold/unfold a column', async function (assert) {
+ // folding/unfolding of grouped kanban with sample data is currently disabled
+ assert.expect(8);
+
+ const kanban = await createView({
+ arch: `
+ <kanban sample="1">
+ <field name="product_id"/>
+ <templates>
+ <div t-name="kanban-box">
+ <field name="foo"/>
+ </div>
+ </templates>
+ </kanban>`,
+ data: this.data,
+ groupBy: ['product_id'],
+ model: 'partner',
+ View: KanbanView,
+ async mockRPC(route, { kwargs, method }) {
+ const result = await this._super(...arguments);
+ if (method === 'web_read_group') {
+ // override read_group to return a single, empty group
+ result.groups = result.groups.slice(0, 1);
+ result.groups[0][`${kwargs.groupby[0]}_count`] = 0;
+ result.length = 1;
+ }
+ return result;
+ },
+ });
+
+ assert.hasClass(kanban, 'o_view_sample_data');
+ assert.containsOnce(kanban, '.o_kanban_group');
+ assert.ok(kanban.$('.o_kanban_record').length > 0, 'should contain sample records');
+
+ // Fold the column
+ await testUtils.dom.click(kanban.el.querySelector('.o_kanban_config > a'));
+ await testUtils.dom.click(kanban.el.querySelector('.dropdown-item.o_kanban_toggle_fold'));
+
+ assert.containsOnce(kanban, '.o_kanban_group');
+ assert.hasClass(kanban.$('.o_kanban_group'), 'o_column_folded');
+
+ // Unfold the column
+ await testUtils.dom.click(kanban.el.querySelector('.o_kanban_group.o_column_folded'));
+
+ assert.containsOnce(kanban, '.o_kanban_group');
+ assert.doesNotHaveClass(kanban.$('.o_kanban_group'), 'o_column_folded');
+ assert.ok(kanban.$('.o_kanban_record').length > 0, 'should contain sample records');
+
+ kanban.destroy();
+ });
+
+ QUnit.test('empty grouped kanban with sample data: delete a column', async function (assert) {
+ assert.expect(5);
+
+ this.data.partner.records = [];
+
+ let groups = [{
+ product_id: [1, 'New'],
+ product_id_count: 0,
+ __domain: [],
+ }];
+ const kanban = await createView({
+ arch: `
+ <kanban sample="1">
+ <field name="product_id"/>
+ <templates>
+ <div t-name="kanban-box">
+ <field name="foo"/>
+ </div>
+ </templates>
+ </kanban>`,
+ data: this.data,
+ groupBy: ['product_id'],
+ model: 'partner',
+ View: KanbanView,
+ async mockRPC(route, { method }) {
+ let result = await this._super(...arguments);
+ if (method === 'web_read_group') {
+ // override read_group to return a single, empty group
+ return {
+ groups,
+ length: groups.length,
+ };
+ }
+ return result;
+ },
+ });
+
+ assert.hasClass(kanban, 'o_view_sample_data');
+ assert.containsOnce(kanban, '.o_kanban_group');
+ assert.ok(kanban.$('.o_kanban_record').length > 0, 'should contain sample records');
+
+ // Delete the first column
+ groups = [];
+ await testUtils.dom.click(kanban.el.querySelector('.o_kanban_config > a'));
+ await testUtils.dom.click(kanban.el.querySelector('.dropdown-item.o_column_delete'));
+ await testUtils.dom.click(document.querySelector('.modal .btn-primary'));
+
+ assert.containsNone(kanban, '.o_kanban_group');
+ assert.containsOnce(kanban, '.o_column_quick_create .o_quick_create_unfolded');
+
+ kanban.destroy();
+ });
+
+ QUnit.test('empty grouped kanban with sample data: add a column and delete it right away', async function (assert) {
+ assert.expect(9);
+
+ const kanban = await createView({
+ arch: `
+ <kanban sample="1">
+ <field name="product_id"/>
+ <templates>
+ <div t-name="kanban-box">
+ <field name="foo"/>
+ </div>
+ </templates>
+ </kanban>`,
+ data: this.data,
+ groupBy: ['product_id'],
+ model: 'partner',
+ View: KanbanView,
+ async mockRPC(route, { method }) {
+ const result = await this._super(...arguments);
+ if (method === 'web_read_group') {
+ result.groups = this.data.product.records.map(r => {
+ return {
+ product_id: [r.id, r.display_name],
+ product_id_count: 0,
+ __domain: ['product_id', '=', r.id],
+ };
+ });
+ result.length = result.groups.length;
+ }
+ return result;
+ },
+ });
+
+ assert.hasClass(kanban, 'o_view_sample_data');
+ assert.containsN(kanban, '.o_kanban_group', 2);
+ assert.ok(kanban.$('.o_kanban_record').length > 0, 'should contain sample records');
+
+ // add a new column
+ await testUtils.dom.click(kanban.el.querySelector('.o_kanban_add_column'));
+ await testUtils.fields.editInput(kanban.el.querySelector('.o_kanban_header input'), "Yoohoo");
+ await testUtils.dom.click(kanban.el.querySelector('.btn.o_kanban_add'));
+
+ assert.hasClass(kanban, 'o_view_sample_data');
+ assert.containsN(kanban, '.o_kanban_group', 3);
+ assert.ok(kanban.$('.o_kanban_record').length > 0, 'should contain sample records');
+
+ // delete the column we just created
+ const newColumn = kanban.el.querySelectorAll('.o_kanban_group')[2];
+ await testUtils.dom.click(newColumn.querySelector('.o_kanban_config > a'));
+ await testUtils.dom.click(newColumn.querySelector('.dropdown-item.o_column_delete'));
+ await testUtils.dom.click(document.querySelector('.modal .btn-primary'));
+
+ assert.hasClass(kanban, 'o_view_sample_data');
+ assert.containsN(kanban, '.o_kanban_group', 2);
+ assert.ok(kanban.$('.o_kanban_record').length > 0, 'should contain sample records');
+
+ kanban.destroy();
+ });
+
QUnit.test('bounce create button when no data and click on empty area', async function (assert) {
assert.expect(2);
@@ -4535,7 +5059,6 @@ QUnit.module('Views', {
await testUtils.dom.click(kanban.$('.o_column_quick_create'));
kanban.$('.o_column_quick_create input').val('new column');
await testUtils.dom.click(kanban.$('.o_column_quick_create button.o_kanban_add'));
-
assert.isVisible(kanban.$buttons.find('.o-kanban-button-new'),
"Create button should now be visible");
kanban.destroy();
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index 67c1a7962a0..3d49b2079cc 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -5,6 +5,7 @@ var AbstractFieldOwl = require('web.AbstractFieldOwl');
var AbstractStorageService = require('web.AbstractStorageService');
var BasicModel = require('web.BasicModel');
var core = require('web.core');
+const Domain = require('web.Domain')
var basicFields = require('web.basic_fields');
var fieldRegistry = require('web.field_registry');
var fieldRegistryOwl = require('web.field_registry_owl');
@@ -3764,6 +3765,284 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test("empty list with sample data", async function (assert) {
+ assert.expect(19);
+
+ const list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: `
+ <tree sample="1">
+ <field name="foo"/>
+ <field name="bar"/>
+ <field name="int_field"/>
+ <field name="m2o"/>
+ <field name="m2m" widget="many2many_tags"/>
+ <field name="date"/>
+ <field name="datetime"/>
+ </tree>`,
+ domain: [['id', '<', 0]], // such that no record matches the domain
+ viewOptions: {
+ action: {
+ help: '<p class="hello">click to add a partner</p>'
+ }
+ },
+ });
+
+ assert.hasClass(list.$el, 'o_view_sample_data');
+ assert.containsOnce(list, '.o_list_table');
+ assert.containsN(list, '.o_data_row', 10);
+ assert.containsOnce(list, '.o_nocontent_help .hello');
+
+ // Check list sample data
+ const firstRow = list.el.querySelector('.o_data_row');
+ const cells = firstRow.querySelectorAll(':scope > .o_data_cell');
+ assert.strictEqual(cells[0].innerText.trim(), "",
+ "Char field should yield an empty element"
+ );
+ assert.containsOnce(cells[1], '.custom-checkbox',
+ "Boolean field has been instantiated"
+ );
+ assert.notOk(isNaN(cells[2].innerText.trim()), "Intger value is a number");
+ assert.ok(cells[3].innerText.trim(), "Many2one field is a string");
+
+ const firstM2MTag = cells[4].querySelector(
+ ':scope span.o_badge_text'
+ ).innerText.trim();
+ assert.ok(firstM2MTag.length > 0, "Many2many contains at least one string tag");
+
+ assert.ok(/\d{2}\/\d{2}\/\d{4}/.test(cells[5].innerText.trim()),
+ "Date field should have the right format"
+ );
+ assert.ok(/\d{2}\/\d{2}\/\d{4} \d{2}:\d{2}:\d{2}/.test(cells[6].innerText.trim()),
+ "Datetime field should have the right format"
+ );
+
+ const textContent = list.$el.text();
+ await list.reload();
+ assert.strictEqual(textContent, list.$el.text(),
+ 'The content should be the same after reloading the view without change'
+ );
+
+ // reload with another domain -> should no longer display the sample records
+ await list.reload({ domain: Domain.FALSE_DOMAIN });
+
+ assert.doesNotHaveClass(list.$el, 'o_view_sample_data');
+ assert.containsNone(list, '.o_list_table');
+ assert.containsOnce(list, '.o_nocontent_help .hello');
+
+ // reload with another domain matching records
+ await list.reload({ domain: Domain.TRUE_DOMAIN });
+
+ assert.doesNotHaveClass(list.$el, 'o_view_sample_data');
+ assert.containsOnce(list, '.o_list_table');
+ assert.containsN(list, '.o_data_row', 4);
+ assert.containsNone(list, '.o_nocontent_help .hello');
+
+ list.destroy();
+ });
+
+ QUnit.test("non empty list with sample data", async function (assert) {
+ assert.expect(6);
+
+ const list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: `
+ <tree sample="1">
+ <field name="foo"/>
+ <field name="bar"/>
+ <field name="int_field"/>
+ </tree>`,
+ domain: Domain.TRUE_DOMAIN,
+ });
+
+ assert.containsOnce(list, '.o_list_table');
+ assert.containsN(list, '.o_data_row', 4);
+ assert.doesNotHaveClass(list.$el, 'o_view_sample_data');
+
+ // reload with another domain matching no record (should not display the sample records)
+ await list.reload({ domain: Domain.FALSE_DOMAIN });
+
+ assert.containsOnce(list, '.o_list_table');
+ assert.containsNone(list, '.o_data_row');
+ assert.doesNotHaveClass(list.$el, 'o_view_sample_data');
+
+ list.destroy();
+ });
+
+ QUnit.test('click on header in empty list with sample data', async function (assert) {
+ assert.expect(4);
+
+ const list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: `
+ <tree sample="1">
+ <field name="foo"/>
+ <field name="bar"/>
+ <field name="int_field"/>
+ </tree>`,
+ domain: Domain.FALSE_DOMAIN,
+ });
+
+ assert.hasClass(list, 'o_view_sample_data');
+ assert.containsOnce(list, '.o_list_table');
+ assert.containsN(list, '.o_data_row', 10);
+
+ const content = list.$el.text();
+ await testUtils.dom.click(list.$('tr:first .o_column_sortable:first'));
+ assert.strictEqual(list.$el.text(), content, "the content should still be the same");
+
+ list.destroy();
+ });
+
+ QUnit.test("non empty editable list with sample data: delete all records", async function (assert) {
+ assert.expect(7);
+
+ const list = await createView({
+ arch: `
+ <tree editable="top" sample="1">
+ <field name="foo"/>
+ <field name="bar"/>
+ <field name="int_field"/>
+ </tree>`,
+ data: this.data,
+ domain: Domain.TRUE_DOMAIN,
+ model: 'foo',
+ View: ListView,
+ viewOptions: {
+ action: {
+ help: '<p class="hello">click to add a partner</p>'
+ },
+ hasActionMenus: true,
+ },
+ });
+
+ // Initial state: all records displayed
+ assert.doesNotHaveClass(list, 'o_view_sample_data');
+ assert.containsOnce(list, '.o_list_table');
+ assert.containsN(list, '.o_data_row', 4);
+ assert.containsNone(list, '.o_nocontent_help');
+
+ // Delete all records
+ await testUtils.dom.click(list.el.querySelector('thead .o_list_record_selector input'));
+ await cpHelpers.toggleActionMenu(list);
+ await cpHelpers.toggleMenuItem(list, "Delete");
+ await testUtils.dom.click($('.modal-footer .btn-primary'));
+
+ // Final state: no more sample data, but nocontent helper displayed
+ assert.doesNotHaveClass(list, 'o_view_sample_data');
+ assert.containsNone(list, '.o_list_table');
+ assert.containsOnce(list, '.o_nocontent_help');
+
+ list.destroy();
+ });
+
+ QUnit.test("empty editable list with sample data: start create record and cancel", async function (assert) {
+ assert.expect(10);
+
+ const list = await createView({
+ arch: `
+ <tree editable="top" sample="1">
+ <field name="foo"/>
+ <field name="bar"/>
+ <field name="int_field"/>
+ </tree>`,
+ data: this.data,
+ domain: Domain.FALSE_DOMAIN,
+ model: 'foo',
+ View: ListView,
+ viewOptions: {
+ action: {
+ help: '<p class="hello">click to add a partner</p>'
+ },
+ },
+ });
+
+ // Initial state: sample data and nocontent helper displayed
+ assert.hasClass(list, 'o_view_sample_data');
+ assert.containsOnce(list, '.o_list_table');
+ assert.containsN(list, '.o_data_row', 10);
+ assert.containsOnce(list, '.o_nocontent_help');
+
+ // Start creating a record
+ await testUtils.dom.click(list.el.querySelector('.btn.o_list_button_add'));
+
+ assert.doesNotHaveClass(list, 'o_view_sample_data');
+ assert.containsOnce(list, '.o_data_row');
+
+ // Discard temporary record
+ await testUtils.dom.click(list.el.querySelector('.btn.o_list_button_discard'));
+
+ // Final state: table should be displayed with no data at all
+ assert.doesNotHaveClass(list, 'o_view_sample_data');
+ assert.containsOnce(list, '.o_list_table');
+ assert.containsNone(list, '.o_data_row');
+ assert.containsNone(list, '.o_nocontent_help');
+
+ list.destroy();
+ });
+
+ QUnit.test("empty editable list with sample data: create and delete record", async function (assert) {
+ assert.expect(13);
+
+ const list = await createView({
+ arch: `
+ <tree editable="top" sample="1">
+ <field name="foo"/>
+ <field name="bar"/>
+ <field name="int_field"/>
+ </tree>`,
+ data: this.data,
+ domain: Domain.FALSE_DOMAIN,
+ model: 'foo',
+ View: ListView,
+ viewOptions: {
+ action: {
+ help: '<p class="hello">click to add a partner</p>'
+ },
+ hasActionMenus: true,
+ },
+ });
+
+ // Initial state: sample data and nocontent helper displayed
+ assert.hasClass(list, 'o_view_sample_data');
+ assert.containsOnce(list, '.o_list_table');
+ assert.containsN(list, '.o_data_row', 10);
+ assert.containsOnce(list, '.o_nocontent_help');
+
+ // Start creating a record
+ await testUtils.dom.click(list.el.querySelector('.btn.o_list_button_add'));
+
+ assert.doesNotHaveClass(list, 'o_view_sample_data');
+ assert.containsOnce(list, '.o_data_row');
+
+ // Save temporary record
+ await testUtils.dom.click(list.el.querySelector('.btn.o_list_button_save'));
+
+ assert.doesNotHaveClass(list, 'o_view_sample_data');
+ assert.containsOnce(list, '.o_list_table');
+ assert.containsOnce(list, '.o_data_row');
+ assert.containsNone(list, '.o_nocontent_help');
+
+ // Delete newly created record
+ await testUtils.dom.click(list.el.querySelector('.o_data_row input'));
+ await cpHelpers.toggleActionMenu(list);
+ await cpHelpers.toggleMenuItem(list, "Delete");
+ await testUtils.dom.click($('.modal-footer .btn-primary'));
+
+ // Final state: there should be no table, but the no content helper
+ assert.doesNotHaveClass(list, 'o_view_sample_data');
+ assert.containsNone(list, '.o_list_table');
+ assert.containsOnce(list, '.o_nocontent_help');
+
+ list.destroy();
+ });
+
QUnit.test('groupby node with a button', async function (assert) {
assert.expect(14);
diff --git a/addons/web/static/tests/views/sample_server_tests.js b/addons/web/static/tests/views/sample_server_tests.js
new file mode 100644
index 00000000000..5582585a0a1
--- /dev/null
+++ b/addons/web/static/tests/views/sample_server_tests.js
@@ -0,0 +1,461 @@
+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
+ } = SampleServer;
+
+ QUnit.module("Sample Server", {
+ 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' },
+ 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' },
+ },
+ };
+ },
+ }, function () {
+
+ QUnit.module("Basic behaviour");
+
+ QUnit.test("Sample data: people type + all field names", async function (assert) {
+ assert.expect(24);
+
+ mock.patch(session, {
+ company_currency_id: 4,
+ });
+
+ const allFieldNames = Object.keys(this.fields['res.users']);
+ const server = new SampleServer('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, decimal = 1) {
+ assert.ok(
+ min <= rec[fieldName] && rec[fieldName] < max &&
+ rec[fieldName].toString().split(".").length === decimal,
+ `Field "${fieldName}" is between ${min} and ${max} and is ${
+ decimal === 1 ? "an integer" : "a float number"
+ }`
+ );
+ }
+
+ // 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}/);
+ assertBetween('height', 0, MAX_FLOAT, 2);
+ assertBetween('color', 0, MAX_COLOR_INT);
+ assertBetween('age', 0, MAX_INTEGER);
+ assertBetween('salary', 0, MAX_MONETARY);
+
+ 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.ok([1, 2].includes(rec.managed_ids.length));
+ assert.ok(rec.managed_ids.every(
+ (id) => typeof id === 'number')
+ );
+
+ assert.ok([1, 2].includes(rec.tag_ids.length));
+ 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 SampleServer('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 SampleServer('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 SampleServer('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 SampleServer('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 SampleServer('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(6);
+
+ const server = new SampleServer('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);
+
+ const professions = result.groups.map((g) => g.profession);
+ assert.ok(professions.includes("gardener"));
+ assert.ok(professions.includes("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(7);
+
+ const server = new SampleServer('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);
+
+ const professions = result.groups.map((g) => g.profession);
+ assert.ok(professions.includes("gardener"));
+ assert.ok(professions.includes("brewer"));
+ assert.ok(professions.includes("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 SampleServer('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(5);
+
+ const server = new SampleServer('hobbit', this.fields.hobbit);
+
+ const result = await server.mockRpc({
+ method: 'read_group',
+ model: 'hobbit',
+ fields: [],
+ groupBy: ['profession'],
+ });
+
+ assert.strictEqual(result.length, 3);
+
+ const professions = result.map((g) => g.profession);
+ assert.ok(professions.includes("gardener"));
+ assert.ok(professions.includes("brewer"));
+ assert.ok(professions.includes("adventurer"));
+
+ 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(6);
+
+ const server = new SampleServer('hobbit', this.fields.hobbit);
+
+ const result = await server.mockRpc({
+ method: 'read_group',
+ model: 'hobbit',
+ fields: ['age'],
+ groupBy: ['profession'],
+ });
+
+ assert.strictEqual(result.length, 3);
+
+ const professions = result.map((g) => g.profession);
+ assert.ok(professions.includes("gardener"));
+ assert.ok(professions.includes("brewer"));
+ assert.ok(professions.includes("adventurer"));
+
+ 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 SampleServer('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 SampleServer('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 SampleServer('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 SampleServer('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 SampleServer('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) { ... });
+ });
+});