/** @odoo-module **/ import { SampleServer } from "@web/model/sample_server"; const { MAIN_RECORDSET_SIZE, SEARCH_READ_LIMIT, // Limits SAMPLE_COUNTRIES, SAMPLE_PEOPLE, SAMPLE_TEXTS, // Text values MAX_COLOR_INT, MAX_FLOAT, MAX_INTEGER, MAX_MONETARY, // Number values SUB_RECORDSET_SIZE, // Records sise } = SampleServer; /** * Transforms random results into deterministic ones. */ class DeterministicSampleServer extends SampleServer { constructor() { super(...arguments); this.arrayElCpt = 0; this.boolCpt = 0; this.subRecordIdCpt = 0; } _getRandomArrayEl(array) { return array[this.arrayElCpt++ % array.length]; } _getRandomBool() { return Boolean(this.boolCpt++ % 2); } _getRandomSubRecordId() { return (this.subRecordIdCpt++ % SUB_RECORDSET_SIZE) + 1; } } let fields; QUnit.module( "Sample Server", { beforeEach() { fields = { "res.users": { display_name: { string: "Name", type: "char" }, name: { string: "Reference", type: "char" }, email: { string: "Email", type: "char" }, phone_number: { string: "Phone number", type: "char" }, brol_machin_url_truc: { string: "URL", type: "char" }, urlemailphone: { string: "Whatever", type: "char" }, active: { string: "Active", type: "boolean" }, is_alive: { string: "Is alive", type: "boolean" }, description: { string: "Description", type: "text" }, birthday: { string: "Birthday", type: "date" }, arrival_date: { string: "Date of arrival", type: "datetime" }, height: { string: "Height", type: "float" }, color: { string: "Color", type: "integer" }, age: { string: "Age", type: "integer" }, salary: { string: "Salary", type: "monetary" }, currency: { string: "Currency", type: "many2one", relation: "res.currency", }, manager_id: { string: "Manager", type: "many2one", relation: "res.users", }, cover_image_id: { string: "Cover Image", type: "many2one", relation: "ir.attachment", }, managed_ids: { string: "Managing", type: "one2many", relation: "res.users", }, tag_ids: { string: "Tags", type: "many2many", relation: "tag" }, type: { string: "Type", type: "selection", selection: [ ["client", "Client"], ["partner", "Partner"], ["employee", "Employee"], ], }, }, "res.country": { display_name: { string: "Name", type: "char" }, }, hobbit: { display_name: { string: "Name", type: "char" }, profession: { string: "Profession", type: "selection", selection: [ ["gardener", "Gardener"], ["brewer", "Brewer"], ["adventurer", "Adventurer"], ], }, age: { string: "Age", type: "integer" }, }, "ir.attachment": { display_name: { string: "Name", type: "char" }, }, }; }, }, function () { QUnit.module("Basic behaviour"); QUnit.test("Sample data: people type + all field names", async function (assert) { assert.expect(26); const specification = {}; for (const fieldName in fields["res.users"]) { specification[fieldName] = {}; if (fields["res.users"][fieldName].type === "many2one") { specification[fieldName] = { fields: { display_name: {} }, }; } } const server = new DeterministicSampleServer("res.users", fields["res.users"]); const { records } = await server.mockRpc({ method: "web_search_read", model: "res.users", specification, }); const rec = records[0]; function assertFormat(fieldName, regex) { if (regex instanceof RegExp) { assert.ok( regex.test(rec[fieldName].toString()), `Field "${fieldName}" has the correct format` ); } else { assert.strictEqual( typeof rec[fieldName], regex, `Field "${fieldName}" is of type ${regex}` ); } } function assertBetween(fieldName, min, max) { const val = rec[fieldName]; assert.ok( min <= val && val < max && parseInt(val, 10) === val, `Field "${fieldName}" should be an integer between ${min} and ${max}: ${val}` ); } // Basic fields assert.ok(SAMPLE_PEOPLE.includes(rec.display_name)); assert.ok(SAMPLE_PEOPLE.includes(rec.name)); assert.strictEqual( rec.email, `${rec.display_name.replace(/ /, ".").toLowerCase()}@sample.demo` ); assertFormat("phone_number", /\+1 555 754 000\d/); assertFormat("brol_machin_url_truc", /http:\/\/sample\d\.com/); assert.strictEqual(rec.urlemailphone, false); assert.strictEqual(rec.active, true); assertFormat("is_alive", "boolean"); assert.ok(SAMPLE_TEXTS.includes(rec.description)); assertFormat("birthday", /\d{4}-\d{2}-\d{2}/); assertFormat("arrival_date", /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/); assert.ok( rec.height >= 0 && rec.height <= MAX_FLOAT, "Field height should be between 0 and 100" ); assertBetween("color", 0, MAX_COLOR_INT); assertBetween("age", 0, MAX_INTEGER); assertBetween("salary", 0, MAX_MONETARY); // check float field have 2 decimal rounding assert.strictEqual(rec.height, parseFloat(parseFloat(rec.height).toFixed(2))); const selectionValues = fields["res.users"].type.selection.map((sel) => sel[0]); assert.ok(selectionValues.includes(rec.type)); // Relational fields assert.strictEqual(rec.currency.id, 1); // 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.display_name)); assert.strictEqual(typeof rec.manager_id.id, "number"); assert.ok(SAMPLE_PEOPLE.includes(rec.manager_id.display_name)); assert.strictEqual(rec.cover_image_id, false); assert.strictEqual(rec.managed_ids.length, 2); assert.ok(rec.managed_ids.every((id) => typeof id === "number")); assert.strictEqual(rec.tag_ids.length, 2); assert.ok(rec.tag_ids.every((id) => typeof id === "number")); }); QUnit.test("Sample data: country type", async function (assert) { assert.expect(1); const server = new DeterministicSampleServer("res.country", fields["res.country"]); const { records } = await server.mockRpc({ method: "web_search_read", model: "res.country", specification: { display_name: {} }, }); assert.ok(SAMPLE_COUNTRIES.includes(records[0].display_name)); }); QUnit.test("Sample data: any type", async function (assert) { assert.expect(1); const server = new DeterministicSampleServer("hobbit", fields.hobbit); const { records } = await server.mockRpc({ method: "web_search_read", model: "hobbit", specification: { display_name: {} }, }); assert.ok(SAMPLE_TEXTS.includes(records[0].display_name)); }); QUnit.module("RPC calls"); QUnit.test("Send 'search_read' RPC: valid field names", async function (assert) { assert.expect(3); const server = new DeterministicSampleServer("hobbit", fields.hobbit); const result = await server.mockRpc({ method: "web_search_read", model: "hobbit", specification: { 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: many2one fields", async function (assert) { const server = new DeterministicSampleServer("res.users", fields["res.users"]); const result = await server.mockRpc({ method: "web_search_read", model: "res.users", specification: { manager_id: { fields: { display_name: {} }, }, }, }); assert.deepEqual(Object.keys(result.records[0]), ["id", "manager_id"]); assert.ok(result.records[0].manager_id.id); assert.ok(/\w+/.test(result.records[0].manager_id.display_name)); }); QUnit.test("Send 'web_read_group' RPC: no group", async function (assert) { assert.expect(1); const server = new DeterministicSampleServer("hobbit", fields.hobbit); server.setExistingGroups(null); const result = await server.mockRpc({ method: "web_read_group", model: "hobbit", groupBy: ["profession"], }); assert.deepEqual(result, { groups: [ { __domain: [], profession: "adventurer", profession_count: 5, }, { __domain: [], profession: "brewer", profession_count: 5, }, { __domain: [], profession: "gardener", profession_count: 6, }, ], length: 3, }); }); QUnit.test("Send 'web_read_group' RPC: 2 groups", async function (assert) { assert.expect(5); const server = new DeterministicSampleServer("hobbit", fields.hobbit); const existingGroups = [ { profession: "gardener", count: 0 }, // fake group { profession: "adventurer", count: 0 }, // fake group ]; server.setExistingGroups(existingGroups); const result = await server.mockRpc({ method: "web_read_group", model: "hobbit", groupBy: ["profession"], fields: [], }); assert.strictEqual(result.length, 2); assert.strictEqual(result.groups.length, 2); assert.deepEqual( result.groups.map((g) => g.profession), ["gardener", "adventurer"] ); assert.strictEqual( result.groups.reduce((acc, g) => acc + g.profession_count, 0), MAIN_RECORDSET_SIZE ); assert.ok(result.groups.every((g) => g.profession_count === g.__data.length)); }); QUnit.test("Send 'web_read_group' RPC: all groups", async function (assert) { assert.expect(5); const server = new DeterministicSampleServer("hobbit", fields.hobbit); const existingGroups = [ { profession: "gardener", count: 0 }, // fake group { profession: "brewer", count: 0 }, // fake group { profession: "adventurer", count: 0 }, // fake group ]; server.setExistingGroups(existingGroups); const result = await server.mockRpc({ method: "web_read_group", model: "hobbit", groupBy: ["profession"], fields: [], }); assert.strictEqual(result.length, 3); assert.strictEqual(result.groups.length, 3); assert.deepEqual( result.groups.map((g) => g.profession), ["gardener", "brewer", "adventurer"] ); assert.strictEqual( result.groups.reduce((acc, g) => acc + g.profession_count, 0), MAIN_RECORDSET_SIZE ); assert.ok(result.groups.every((g) => g.profession_count === g.__data.length)); }); QUnit.test("Send 'read_group' RPC: no group", async function (assert) { assert.expect(1); const server = new DeterministicSampleServer("hobbit", fields.hobbit); const result = await server.mockRpc({ method: "read_group", model: "hobbit", fields: [], groupBy: [], }); assert.deepEqual(result, [ { __count: MAIN_RECORDSET_SIZE, __domain: [], }, ]); }); QUnit.test("Send 'read_group' RPC: groupBy", async function (assert) { assert.expect(3); const server = new DeterministicSampleServer("hobbit", fields.hobbit); const result = await server.mockRpc({ method: "read_group", model: "hobbit", fields: [], groupBy: ["profession"], }); assert.strictEqual(result.length, 3); assert.deepEqual( result.map((g) => g.profession), ["adventurer", "brewer", "gardener"] ); assert.strictEqual( result.reduce((acc, g) => acc + g.profession_count, 0), MAIN_RECORDSET_SIZE ); }); QUnit.test("Send 'read_group' RPC: groupBy and field", async function (assert) { assert.expect(4); const server = new DeterministicSampleServer("hobbit", fields.hobbit); const result = await server.mockRpc({ method: "read_group", model: "hobbit", fields: ["age:sum"], groupBy: ["profession"], }); assert.strictEqual(result.length, 3); assert.deepEqual( result.map((g) => g.profession), ["adventurer", "brewer", "gardener"] ); assert.strictEqual( result.reduce((acc, g) => acc + g.profession_count, 0), MAIN_RECORDSET_SIZE ); assert.strictEqual( result.reduce((acc, g) => acc + g.age, 0), server.data.hobbit.records.reduce((acc, g) => acc + g.age, 0) ); }); QUnit.test("Send 'read_group' RPC: multiple groupBys and lazy", async function (assert) { assert.expect(2); const server = new DeterministicSampleServer("hobbit", fields.hobbit); const result = await server.mockRpc({ method: "read_group", model: "hobbit", fields: [], groupBy: ["profession", "age"], }); assert.ok("profession" in result[0]); assert.notOk("age" in result[0]); }); QUnit.test( "Send 'read_group' RPC: multiple groupBys and not lazy", async function (assert) { assert.expect(2); const server = new DeterministicSampleServer("hobbit", 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_group' RPC: multiple groupBys among which a many2many", async function (assert) { const server = new DeterministicSampleServer("res.users", fields["res.users"]); const result = await server.mockRpc({ method: "read_group", model: "res.users", fields: [], groupBy: ["height", "tag_ids"], lazy: false, }); assert.ok(typeof result[0].tag_ids[0] === "number"); assert.ok(typeof result[0].tag_ids[1] === "string"); } ); QUnit.test("Send 'read' RPC: no id", async function (assert) { assert.expect(1); const server = new DeterministicSampleServer("hobbit", fields.hobbit); const result = await server.mockRpc({ method: "read", model: "hobbit", args: [[], ["display_name"]], }); assert.deepEqual(result, []); }); QUnit.test("Send 'read' RPC: one id", async function (assert) { assert.expect(3); const server = new DeterministicSampleServer("hobbit", fields.hobbit); const result = await server.mockRpc({ method: "read", model: "hobbit", args: [[1], ["display_name"]], }); assert.strictEqual(result.length, 1); assert.ok(/\w+/.test(result[0].display_name), "Display name has been mocked"); assert.strictEqual(result[0].id, 1); }); QUnit.test("Send 'read' RPC: more than all available ids", async function (assert) { assert.expect(1); const server = new DeterministicSampleServer("hobbit", 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); }); QUnit.test("partial support of array_agg", async function (assert) { fields["res.users"].id = { type: "integer", name: "ID" }; const server = new DeterministicSampleServer("res.users", fields["res.users"]); const result = await server.mockRpc({ method: "read_group", model: "res.users", fields: ["unused_label:array_agg(id)"], groupBy: [], lazy: false, }); assert.strictEqual(result.length, 1); const ids = new Array(16).fill(0).map((_, index) => index + 1); assert.deepEqual(result[0].id, ids); }); // To be implemented if needed // QUnit.test("Send 'read_progress_bar' RPC", async function (assert) { ... }); } );