summaryrefslogtreecommitdiff
path: root/addons/web/static/tests
diff options
context:
space:
mode:
authorJérémy Hennecart <jeh@odoo.com>2020-06-19 07:06:31 +0000
committerAaron Bohy <aab@odoo.com>2020-06-22 13:04:01 +0000
commitc7964657ecbbad278cec73cf231d7eef55b44347 (patch)
treebe77532a80cbed1283c2062cb2dc1159aa0fbdd4 /addons/web/static/tests
parent96fcefb6f1fe3fb5a5f197e2635e6d71d9a1d9dd (diff)
[IMP] web: add support of "sample" attribute in views
With this commit, it is now possible to display sample (fake) data in empty views, in the hope of easing user onboarding. This can be enabled (in list and kanban views) with attribute sample="1" on the arch root element. In this case, if there is no data to display, sample data will be generated based on heuristics (depending on field types and names). This can be used in addition to the no content helper. Task 2232801 X-original-commit: 7c8e627ff5029cb539d702705e5ce53d658c76ed Co-authored-by: Aaron Bohy <aab@odoo.com> Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com> Co-authored-by: Jérémy Hennecart <jeh@odoo.com> Co-authored-by: Julien Mougenot <jum@odoo.com>
Diffstat (limited to 'addons/web/static/tests')
-rw-r--r--addons/web/static/tests/helpers/test_utils_create.js2
-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
6 files changed, 1402 insertions, 9 deletions
diff --git a/addons/web/static/tests/helpers/test_utils_create.js b/addons/web/static/tests/helpers/test_utils_create.js
index 801120bf64b..341ace4fba8 100644
--- a/addons/web/static/tests/helpers/test_utils_create.js
+++ b/addons/web/static/tests/helpers/test_utils_create.js
@@ -282,7 +282,7 @@ odoo.define('web.test_utils_create', function (require) {
async function createModel(params) {
const widget = new Widget();
- const model = new params.Model(widget);
+ const model = new params.Model(widget, params);
await testUtilsMock.addMockEnvironment(widget, params);
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) { ... });
+ });
+});