').addClass('i_am_a_timesheet_toggle_widget');
+ this.$el.append($widgetIdentification);
+ },
+ });
+ });
+ hooks.afterEach(async function (hooks) {
+ // Restores the widgets and trigger reload in FieldRegistry.
+ TimesheetUOM.FieldTimesheetTime = FieldTimesheetTimeBackup;
+ TimesheetUOM.FieldTimesheetToggle = FieldTimesheetToggleBackup;
+ await env.patchSessionAndStartServices({ }, true);
+ });
+ QUnit.test('the timesheet_uom widget added to the fieldRegistry is company related', async function (assert) {
+ assert.expect(2);
+
+ let view = await env.createView();
+ assert.ok(view.$('.i_am_a_timesheet_time_widget').length, 'FieldTimesheetTime is rendered when company uom is hour');
+ view.destroy();
+
+ let option = {
+ session: {
+ user_context: env.singleCompanyDayUOMUser,
+ },
+ };
+ view = await env.createView(option);
+ assert.ok(view.$('.i_am_a_timesheet_toggle_widget').length, 'FieldTimesheetToggle is rendered when company uom is day');
+ view.destroy();
+ });
+ QUnit.test('the timesheet_uom widget added to the fieldRegistry in a multi company environment is the current company', async function (assert) {
+ assert.expect(2);
+
+ let option = {
+ session: {
+ user_context: env.multiCompanyHourUOMUser,
+ },
+ };
+ let view = await env.createView(option);
+ assert.ok(view.$('.i_am_a_timesheet_time_widget').length, 'FieldTimesheetTime is rendered when current company uom is hour');
+ view.destroy();
+
+ option = {
+ session: {
+ user_context: env.multiCompanyDayUOMUser,
+ },
+ };
+ view = await env.createView(option);
+ assert.ok(view.$('.i_am_a_timesheet_toggle_widget').length, 'FieldTimesheetToggle is rendered when current company uom is day');
+ view.destroy();
+ });
+ });
+ QUnit.module('timesheet_uom_factor', function (hooks) {
+ QUnit.test('the timesheet_uom_factor usage in formatters and parsers is company related', async function (assert) {
+ assert.expect(4);
+
+ await env.patchSessionAndStartServices();
+ assert.strictEqual(fieldUtils.format.timesheet_uom(1), '01:00', 'The format is taking the timesheet_uom_factor into account');
+ assert.strictEqual(fieldUtils.parse.timesheet_uom('01:00'), 1, 'The parsing is taking the timesheet_uom_factor into account');
+
+ const sessionToApply = {
+ user_context: env.singleCompanyDayUOMUser,
+ };
+ await env.patchSessionAndStartServices(sessionToApply);
+ assert.strictEqual(fieldUtils.format.timesheet_uom(8), '1.00', 'The format is taking the timesheet_uom_factor into account');
+ assert.strictEqual(fieldUtils.parse.timesheet_uom('1.00'), 8, 'The parsing is taking the timesheet_uom_factor into account');
+ });
+ QUnit.test('the timesheet_uom_factor taken into account in a multi company environment is the current company', async function (assert) {
+ assert.expect(4);
+
+ let sessionToApply = {
+ user_context: env.multiCompanyHourUOMUser,
+ };
+ await env.patchSessionAndStartServices(sessionToApply);
+ assert.strictEqual(fieldUtils.format.timesheet_uom(1), '01:00', 'The format is taking the timesheet_uom_factor into account');
+ assert.strictEqual(fieldUtils.parse.timesheet_uom('01:00'), 1, 'The parsing is taking the timesheet_uom_factor into account');
+
+ sessionToApply.user_context = env.singleCompanyDayUOMUser;
+ await env.patchSessionAndStartServices(sessionToApply);
+ assert.strictEqual(fieldUtils.format.timesheet_uom(8), '1.00', 'The format is taking the timesheet_uom_factor into account');
+ assert.strictEqual(fieldUtils.parse.timesheet_uom('1.00'), 8, 'The parsing is taking the timesheet_uom_factor into account');
+ });
+ });
+ });
+});
+});
diff --git a/addons/hr_timesheet/static/tests/timesheet_uom_widgets_tests.js b/addons/hr_timesheet/static/tests/legacy/timesheet_uom_widgets_tests.js
similarity index 100%
rename from addons/hr_timesheet/static/tests/timesheet_uom_widgets_tests.js
rename to addons/hr_timesheet/static/tests/legacy/timesheet_uom_widgets_tests.js
diff --git a/addons/hr_timesheet/static/tests/task_with_hours_tests.js b/addons/hr_timesheet/static/tests/task_with_hours_tests.js
new file mode 100644
index 00000000000..629ba20f766
--- /dev/null
+++ b/addons/hr_timesheet/static/tests/task_with_hours_tests.js
@@ -0,0 +1,67 @@
+/** @odoo-module */
+
+import { makeView } from "@web/../tests/views/helpers";
+import { click, clickDropdown, editInput, getFixture } from "@web/../tests/helpers/utils";
+
+import { getServerData, updateArch, setupTestEnv } from "./hr_timesheet_common_tests";
+
+
+QUnit.module("hr_timesheet", (hooks) => {
+ let target;
+ let serverData;
+ hooks.beforeEach(async function (assert) {
+ setupTestEnv();
+ serverData = getServerData();
+ updateArch(
+ serverData,
+ { task_id: "task_with_hours" },
+ { task_id: "{ 'default_project_id': project_id }" });
+ target = getFixture();
+ });
+
+ QUnit.module("task_with_hours");
+
+ async function _testCreateAndEdit(target, visible, assert) {
+ await click(target, ".o_list_many2one[name=task_id]");
+ await click(target, ".o_list_many2one[name=task_id] input");
+ await editInput(target, ".o_list_many2one[name=task_id] input", "NonExistingTask");
+ await click(target, ".o_list_many2one[name=task_id] input");
+ await clickDropdown(target, "task_id");
+ const testFunction = visible ? assert.containsOnce : assert.containsNone;
+ testFunction(target, '.o_list_many2one[name=task_id] .dropdown ul li:contains("Create and edit...")');
+ }
+
+ QUnit.test("quick create is enabled when project_id is set", async function (assert) {
+ await makeView({
+ serverData,
+ type: "list",
+ resModel: "account.analytic.line",
+ });
+ const secondRow = target.querySelector(".o_list_table .o_data_row:nth-of-type(2)");
+ await _testCreateAndEdit(secondRow, true, assert);
+ });
+
+ QUnit.test("quick create is no enabled when project_id is not set", async function (assert) {
+ await makeView({
+ serverData,
+ type: "list",
+ resModel: "account.analytic.line",
+ });
+ const thirdRow = target.querySelector(".o_list_table .o_data_row:nth-of-type(3)");
+ await _testCreateAndEdit(thirdRow, false, assert);
+ });
+
+ QUnit.test("the text of the task includes hours in the drop down but not in the line", async function (assert) {
+ await makeView({
+ serverData,
+ type: "list",
+ resModel: "account.analytic.line",
+ });
+ const firstRow = target.querySelector(".o_list_table .o_data_row:first-of-type");
+ assert.containsNone(firstRow, '.o_list_many2one[name=task_id]:contains("AdditionalInfo")');
+ await click(firstRow, ".o_list_many2one[name=task_id]");
+ await clickDropdown(firstRow, "task_id");
+ assert.containsN(firstRow, '.o_list_many2one[name=task_id] .dropdown ul li:contains("AdditionalInfo")', 3);
+ });
+
+});
diff --git a/addons/hr_timesheet/static/tests/timesheet_graph_tests.js b/addons/hr_timesheet/static/tests/timesheet_graph_tests.js
index 972294c7715..91092343897 100644
--- a/addons/hr_timesheet/static/tests/timesheet_graph_tests.js
+++ b/addons/hr_timesheet/static/tests/timesheet_graph_tests.js
@@ -11,7 +11,7 @@ import { setupControlPanelServiceRegistry } from "@web/../tests/search/helpers";
const serviceRegistry = registry.category("services");
-QUnit.module('Views', function (hooks) {
+QUnit.module('hr_timesheet', function (hooks) {
let serverData;
hooks.beforeEach(() => {
serverData = {
@@ -40,7 +40,7 @@ QUnit.module('Views', function (hooks) {
serviceRegistry.add("dialog", dialogService);
});
- QUnit.module("hrTimesheetGraphView");
+ QUnit.module("hr_timesheet_graphview");
QUnit.test('the timesheet graph view data are not multiplied by a factor that is company related (factor = 1)', async function (assert) {
assert.expect(1);
diff --git a/addons/hr_timesheet/static/tests/timesheet_uom_no_toggle_tests.js b/addons/hr_timesheet/static/tests/timesheet_uom_no_toggle_tests.js
new file mode 100644
index 00000000000..98d0b20d8d6
--- /dev/null
+++ b/addons/hr_timesheet/static/tests/timesheet_uom_no_toggle_tests.js
@@ -0,0 +1,86 @@
+/** @odoo-module */
+
+import { session } from "@web/session";
+
+import { makeView } from "@web/../tests/views/helpers";
+import { getFixture, patchWithCleanup } from "@web/../tests/helpers/utils";
+
+import { getServerData, updateArch, setupTestEnv } from "./hr_timesheet_common_tests";
+
+
+QUnit.module("hr_timesheet", (hooks) => {
+ let target;
+ let serverData;
+ hooks.beforeEach(async function (assert) {
+ setupTestEnv();
+ serverData = getServerData();
+ updateArch(serverData, { unit_amount: "timesheet_uom_no_toggle" });
+ target = getFixture();
+ });
+
+ QUnit.module("timesheet_uom_no_toggle");
+
+ QUnit.test("FloatTimeField is used when current company uom uses float_time widget", async function (assert) {
+ await makeView({
+ serverData,
+ type: "form",
+ resModel: "account.analytic.line",
+ resId: 1,
+ });
+ const unitAmountInput = target.querySelector('div[name="unit_amount"] input');
+ assert.equal(unitAmountInput.value, "01:00", "unit_amount is displayed as time");
+ });
+
+ QUnit.test("FloatTimeField is not dependent of timesheet_uom_factor of the current company when current company uom uses float_time widget", async function (assert) {
+ patchWithCleanup(session.user_companies.allowed_companies[1], { timesheet_uom_factor: 2 });
+ await makeView({
+ serverData,
+ type: "form",
+ resModel: "account.analytic.line",
+ resId: 1,
+ });
+ const unitAmountInput = target.querySelector('div[name="unit_amount"] input');
+ assert.equal(unitAmountInput.value, "01:00", "timesheet_uom_factor is not taken into account");
+ });
+
+ QUnit.test("FloatToggleField is not used when current company uom uses float_toggle widget, FloatFactorField is used instead", async function (assert) {
+ patchWithCleanup(session.user_companies.allowed_companies[1], { timesheet_uom_id: 2 });
+ await makeView({
+ serverData,
+ type: "form",
+ resModel: "account.analytic.line",
+ resId: 1,
+ });
+ assert.containsNone(target, 'div[name="unit_amount"] .o_field_float_toggle', "unit_amount is not displayed as float toggle");
+ const unitAmountInput = target.querySelector('div[name="unit_amount"] input');
+ assert.containsOnce(target, 'div[name="unit_amount"] input[inputmode="numeric"]', "unit_amount is displayed as float");
+ assert.equal(unitAmountInput.value, "1.00", "unit_amount is not displayed as float and not as time");
+ });
+
+ QUnit.test("FloatFactorField is used when the current_company uom is not part of the session uom", async function (assert) {
+ patchWithCleanup(session.user_companies.allowed_companies[1], { timesheet_uom_id: 'dummy' });
+ await makeView({
+ serverData,
+ type: "form",
+ resModel: "account.analytic.line",
+ resId: 1,
+ });
+ const unitAmountInput = target.querySelector('div[name="unit_amount"] input');
+ assert.containsOnce(target, 'div[name="unit_amount"] input[inputmode="numeric"]', "unit_amount is displayed as float");
+ assert.equal(unitAmountInput.value, "1.00", "unit_amount is not displayed as float and not as time");
+ assert.containsNone(target, 'div[name="unit_amount"].o_field_float_toggle', "unit_amount is not displayed as float toggle");
+ });
+
+ QUnit.test("FloatFactorField is dependent of timesheet_uom_factor of the current company when current company uom uses float_toggle widget", async function (assert) {
+ patchWithCleanup(session.user_companies.allowed_companies[1], { timesheet_uom_id: 'dummy', timesheet_uom_factor: 2 });
+ await makeView({
+ serverData,
+ type: "form",
+ resModel: "account.analytic.line",
+ resId: 1,
+ });
+ const unitAmountInput = target.querySelector('div[name="unit_amount"] input');
+ assert.equal(unitAmountInput.value, "2.00", "timesheet_uom_factor is taken into account");
+ });
+
+});
diff --git a/addons/hr_timesheet/static/tests/timesheet_uom_tests.js b/addons/hr_timesheet/static/tests/timesheet_uom_tests.js
index 2482cdad4d0..0e3f37fad7d 100644
--- a/addons/hr_timesheet/static/tests/timesheet_uom_tests.js
+++ b/addons/hr_timesheet/static/tests/timesheet_uom_tests.js
@@ -1,140 +1,94 @@
-odoo.define("hr_timesheet.timesheet_uom_tests", function (require) {
-"use strict";
+/** @odoo-module */
-const session = require('web.session');
-const SetupTimesheetUOMWidgetsTestEnvironment = require('hr_timesheet.timesheet_uom_tests_env');
-const fieldUtils = require('web.field_utils');
-const TimesheetUOM = require('hr_timesheet.timesheet_uom');
+import { session } from "@web/session";
-QUnit.module('Timesheet UOM Widgets', function (hooks) {
- let env;
- let sessionUserCompaniesBackup;
- let sessionUserContextBackup;
- let sessionUOMIdsBackup;
- let sessionUIDBackup;
+import { makeView } from "@web/../tests/views/helpers";
+import { getFixture, patchWithCleanup } from "@web/../tests/helpers/utils";
+
+import { getServerData, updateArch, setupTestEnv } from "./hr_timesheet_common_tests";
+
+
+QUnit.module("hr_timesheet", (hooks) => {
+ let target;
+ let serverData;
hooks.beforeEach(async function (assert) {
- env = new SetupTimesheetUOMWidgetsTestEnvironment();
- // Backups session parts that this testing module will alter in order to restore it at the end.
- sessionUserCompaniesBackup = session.user_companies || false;
- sessionUserContextBackup = session.user_context || false;
- sessionUOMIdsBackup = session.uom_ids || false;
- sessionUIDBackup = session.uid || false;
+ setupTestEnv();
+ serverData = getServerData();
+ updateArch(serverData, { unit_amount: "timesheet_uom" });
+ target = getFixture();
});
- hooks.afterEach(async function (assert) {
- // Restores the session
- const sessionToApply = Object.assign(
- { },
- sessionUserCompaniesBackup && {
- user_companies: sessionUserCompaniesBackup,
- } || { },
- sessionUserContextBackup && {
- user_context: sessionUserContextBackup,
- } || { },
- sessionUOMIdsBackup && {
- uom_ids: sessionUOMIdsBackup,
- } || { },
- sessionUIDBackup && {
- uid: sessionUIDBackup,
- } || { });
- await env.patchSessionAndStartServices(sessionToApply, true);
- });
- QUnit.module('timesheet_uom', function (hooks) {
- QUnit.module('fieldRegistry', function (hooks) {
- let FieldTimesheetTimeBackup;
- let FieldTimesheetToggleBackup;
- hooks.beforeEach(function (assert) {
- // Backups the FieldTimesheetTime widget as it will be altered in this testing module
- // in order to to ease testing.
- FieldTimesheetTimeBackup = TimesheetUOM.FieldTimesheetTime;
- TimesheetUOM.FieldTimesheetTime.include({
- _render: function () {
- const $widgetIdentification = $('
').addClass('i_am_a_timesheet_time_widget');
- this.$el.append($widgetIdentification);
- },
- });
- FieldTimesheetToggleBackup = TimesheetUOM.FieldTimesheetToggle;
- TimesheetUOM.FieldTimesheetToggle.include({
- _render: function () {
- const $widgetIdentification = $('
').addClass('i_am_a_timesheet_toggle_widget');
- this.$el.append($widgetIdentification);
- },
- });
- });
- hooks.afterEach(async function (hooks) {
- // Restores the widgets and trigger reload in FieldRegistry.
- TimesheetUOM.FieldTimesheetTime = FieldTimesheetTimeBackup;
- TimesheetUOM.FieldTimesheetToggle = FieldTimesheetToggleBackup;
- await env.patchSessionAndStartServices({ }, true);
- });
- QUnit.test('the timesheet_uom widget added to the fieldRegistry is company related', async function (assert) {
- assert.expect(2);
- let view = await env.createView();
- assert.ok(view.$('.i_am_a_timesheet_time_widget').length, 'FieldTimesheetTime is rendered when company uom is hour');
- view.destroy();
+ QUnit.module("timesheet_uom");
- let option = {
- session: {
- user_context: env.singleCompanyDayUOMUser,
- },
- };
- view = await env.createView(option);
- assert.ok(view.$('.i_am_a_timesheet_toggle_widget').length, 'FieldTimesheetToggle is rendered when company uom is day');
- view.destroy();
- });
- QUnit.test('the timesheet_uom widget added to the fieldRegistry in a multi company environment is the current company', async function (assert) {
- assert.expect(2);
-
- let option = {
- session: {
- user_context: env.multiCompanyHourUOMUser,
- },
- };
- let view = await env.createView(option);
- assert.ok(view.$('.i_am_a_timesheet_time_widget').length, 'FieldTimesheetTime is rendered when current company uom is hour');
- view.destroy();
-
- option = {
- session: {
- user_context: env.multiCompanyDayUOMUser,
- },
- };
- view = await env.createView(option);
- assert.ok(view.$('.i_am_a_timesheet_toggle_widget').length, 'FieldTimesheetToggle is rendered when current company uom is day');
- view.destroy();
- });
- });
- QUnit.module('timesheet_uom_factor', function (hooks) {
- QUnit.test('the timesheet_uom_factor usage in formatters and parsers is company related', async function (assert) {
- assert.expect(4);
-
- await env.patchSessionAndStartServices();
- assert.strictEqual(fieldUtils.format.timesheet_uom(1), '01:00', 'The format is taking the timesheet_uom_factor into account');
- assert.strictEqual(fieldUtils.parse.timesheet_uom('01:00'), 1, 'The parsing is taking the timesheet_uom_factor into account');
-
- const sessionToApply = {
- user_context: env.singleCompanyDayUOMUser,
- };
- await env.patchSessionAndStartServices(sessionToApply);
- assert.strictEqual(fieldUtils.format.timesheet_uom(8), '1.00', 'The format is taking the timesheet_uom_factor into account');
- assert.strictEqual(fieldUtils.parse.timesheet_uom('1.00'), 8, 'The parsing is taking the timesheet_uom_factor into account');
- });
- QUnit.test('the timesheet_uom_factor taken into account in a multi company environment is the current company', async function (assert) {
- assert.expect(4);
-
- let sessionToApply = {
- user_context: env.multiCompanyHourUOMUser,
- };
- await env.patchSessionAndStartServices(sessionToApply);
- assert.strictEqual(fieldUtils.format.timesheet_uom(1), '01:00', 'The format is taking the timesheet_uom_factor into account');
- assert.strictEqual(fieldUtils.parse.timesheet_uom('01:00'), 1, 'The parsing is taking the timesheet_uom_factor into account');
-
- sessionToApply.user_context = env.singleCompanyDayUOMUser;
- await env.patchSessionAndStartServices(sessionToApply);
- assert.strictEqual(fieldUtils.format.timesheet_uom(8), '1.00', 'The format is taking the timesheet_uom_factor into account');
- assert.strictEqual(fieldUtils.parse.timesheet_uom('1.00'), 8, 'The parsing is taking the timesheet_uom_factor into account');
- });
+ QUnit.test("FloatTimeField is used when current company uom uses float_time widget", async function (assert) {
+ await makeView({
+ serverData,
+ type: "form",
+ resModel: "account.analytic.line",
+ resId: 1,
});
+ const unitAmountInput = target.querySelector('div[name="unit_amount"] input');
+ assert.equal(unitAmountInput.value, "01:00", "unit_amount is displayed as time");
});
-});
+
+ QUnit.test("FloatTimeField is not dependent of timesheet_uom_factor of the current company when current company uom uses float_time widget", async function (assert) {
+ patchWithCleanup(session.user_companies.allowed_companies[1], { timesheet_uom_factor: 2 });
+ await makeView({
+ serverData,
+ type: "form",
+ resModel: "account.analytic.line",
+ resId: 1,
+ });
+ const unitAmountInput = target.querySelector('div[name="unit_amount"] input');
+ assert.equal(unitAmountInput.value, "01:00", "timesheet_uom_factor is not taken into account");
+ });
+
+ QUnit.test("FloatToggleField is used when current company uom uses float_toggle widget", async function (assert) {
+ patchWithCleanup(session.user_companies.allowed_companies[1], { timesheet_uom_id: 2 });
+ await makeView({
+ serverData,
+ type: "form",
+ resModel: "account.analytic.line",
+ resId: 1,
+ });
+ assert.containsOnce(target, 'div[name="unit_amount"] .o_field_float_toggle', "unit_amount is displayed as float toggle");
+ });
+
+ QUnit.test("FloatToggleField is dependent of timesheet_uom_factor of the current company when current company uom uses float_toggle widget", async function (assert) {
+ patchWithCleanup(session.user_companies.allowed_companies[1], { timesheet_uom_id: 2, timesheet_uom_factor: 2 });
+ await makeView({
+ serverData,
+ type: "form",
+ resModel: "account.analytic.line",
+ resId: 1,
+ });
+ assert.containsOnce(target, 'div[name="unit_amount"] .o_field_float_toggle:contains("2.00")', "timesheet_uom_factor is taken into account");
+ });
+
+ QUnit.test("FloatFactorField is used when the current_company uom is not part of the session uom", async function (assert) {
+ patchWithCleanup(session.user_companies.allowed_companies[1], { timesheet_uom_id: 'dummy' });
+ await makeView({
+ serverData,
+ type: "form",
+ resModel: "account.analytic.line",
+ resId: 1,
+ });
+ const unitAmountInput = target.querySelector('div[name="unit_amount"] input');
+ assert.containsOnce(target, 'div[name="unit_amount"] input[inputmode="numeric"]', "unit_amount is displayed as float");
+ assert.equal(unitAmountInput.value, "1.00", "unit_amount is not displayed as float and not as time");
+ assert.containsNone(target, 'div[name="unit_amount"].o_field_float_toggle', "unit_amount is not displayed as float toggle");
+ });
+
+ QUnit.test("FloatFactorField is dependent of timesheet_uom_factor of the current company when current company uom uses float_toggle widget", async function (assert) {
+ patchWithCleanup(session.user_companies.allowed_companies[1], { timesheet_uom_id: 'dummy', timesheet_uom_factor: 2 });
+ await makeView({
+ serverData,
+ type: "form",
+ resModel: "account.analytic.line",
+ resId: 1,
+ });
+ const unitAmountInput = target.querySelector('div[name="unit_amount"] input');
+ assert.equal(unitAmountInput.value, "2.00", "timesheet_uom_factor is taken into account");
+ });
+
});
diff --git a/addons/web/static/src/views/fields/many2one/many2one_field.js b/addons/web/static/src/views/fields/many2one/many2one_field.js
index 3cb54051d29..57406b8a44d 100644
--- a/addons/web/static/src/views/fields/many2one/many2one_field.js
+++ b/addons/web/static/src/views/fields/many2one/many2one_field.js
@@ -51,18 +51,10 @@ export class Many2OneField extends Component {
this.autocompleteContainerRef.el.querySelector("input").focus();
};
- const computeActiveActions = (props) => {
- this.state.activeActions = {
- create: props.canCreate,
- createEdit: props.canCreateEdit,
- write: props.canWrite,
- };
- };
-
this.state = useState({
isFloating: !this.props.value,
});
- computeActiveActions(this.props);
+ this.computeActiveActions(this.props);
this.openMany2X = useOpenMany2XRecord({
resModel: this.relation,
@@ -102,7 +94,7 @@ export class Many2OneField extends Component {
onWillUpdateProps(async (nextProps) => {
this.state.isFloating = !nextProps.value;
- computeActiveActions(nextProps);
+ this.computeActiveActions(nextProps);
});
}
@@ -151,6 +143,13 @@ export class Many2OneField extends Component {
autocomplete_container: this.autocompleteContainerRef,
};
}
+ computeActiveActions(props) {
+ this.state.activeActions = {
+ create: props.canCreate,
+ createEdit: props.canCreateEdit,
+ write: props.canWrite,
+ };
+ }
getDomain() {
return this.domain.toList(this.context);
}