[REF] *: adapt code to owl 2

Owl 2 changelog: https://github.com/odoo/owl/blob/a9f29c4caad4f32d06be1ec4780572825781cd9b/CHANGELOG.md

Part-of: odoo/odoo#80156
Co-authored-by: Aaron Bohy <aab@odoo.com>
Co-authored-by: Bruno Boi <boi@odoo.com>
Co-authored-by: Géry Debongnie <ged@odoo.com>
Co-authored-by: Samuel Degueldre <sad@odoo.com>
Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com>
Co-authored-by: Simon Genin (ges) <ges@odoo.com>
Co-authored-by: Francois (fge) <fge@odoo.com>
Co-authored-by: Michael Mattiello (mcm) <mcm@odoo.com>
Co-authored-by: Julien Mougenot <jum@odoo.com>
Co-authored-by: luvi <luvi@odoo.com>
Co-authored-by: Lucas Perais (lpe) <lpe@odoo.com>
Co-authored-by: Jorge Pinna Puissant <jpp@odoo.com>
This commit is contained in:
Aaron Bohy
2022-02-10 07:46:13 +00:00
committed by Samuel Degueldre
co-authored by Bruno Boi Géry Debongnie Samuel Degueldre Mathieu Duckerts-Antoine Simon Genin Francois Michael Mattiello Julien Mougenot luvi Lucas Perais Jorge Pinna Puissant
parent edd79ff560
commit 318cdcc0b8
42 changed files with 351 additions and 378 deletions
@@ -4,7 +4,7 @@ odoo.define('account.ShowResequenceRenderer', function (require) {
const AbstractFieldOwl = require('web.AbstractFieldOwl');
const field_registry = require('web.field_registry_owl');
const { Component } = owl;
const { Component, onWillUpdateProps } = owl;
class ChangeLine extends Component { }
ChangeLine.template = 'account.ResequenceChangeLine';
@@ -12,16 +12,15 @@ ChangeLine.props = ["changeLine", 'ordering'];
class ShowResequenceRenderer extends AbstractFieldOwl {
constructor(...args) {
super(...args);
setup() {
super.setup();
this.data = this.value ? JSON.parse(this.value) : {
changeLines: [],
ordering: 'date',
};
}
async willUpdateProps(nextProps) {
await super.willUpdateProps(nextProps);
Object.assign(this.data, JSON.parse(this.value));
onWillUpdateProps(() => {
Object.assign(this.data, JSON.parse(this.value));
});
}
}
ShowResequenceRenderer.template = 'account.ResequenceRenderer';
@@ -4,7 +4,7 @@ odoo.define('account.ShowGroupedList', function (require) {
const AbstractFieldOwl = require('web.AbstractFieldOwl');
const field_registry = require('web.field_registry_owl');
const { Component } = owl;
const { Component, onWillUpdateProps } = owl;
class ListItem extends Component { }
ListItem.template = 'account.GroupedItemTemplate';
@@ -17,8 +17,8 @@ ListGroup.props = ["group_vals", "options"];
class ShowGroupedList extends AbstractFieldOwl {
constructor(...args) {
super(...args);
setup() {
super.setup();
this.data = this.value ? JSON.parse(this.value) : {
groups_vals: [],
options: {
@@ -26,10 +26,9 @@ class ShowGroupedList extends AbstractFieldOwl {
columns: [],
},
};
}
async willUpdateProps(nextProps) {
await super.willUpdateProps(nextProps);
Object.assign(this.data, JSON.parse(this.value));
onWillUpdateProps(() => {
Object.assign(this.data, JSON.parse(this.value));
});
}
}
ShowGroupedList.template = 'account.GroupedListTemplate';
+13 -12
View File
@@ -6,29 +6,29 @@ import AbstractFieldOwl from 'web.AbstractFieldOwl';
import fieldUtils from 'web.field_utils';
import field_registry from 'web.field_registry_owl';
const { Component, useRef, useState } = owl;
const { Component, onPatched, onWillUpdateProps, useRef, useState } = owl;
/**
A line of some TaxTotalsComponent, giving the values of a tax group.
**/
class TaxGroupComponent extends Component {
constructor(parent, props) {
super(parent, props);
setup() {
this.inputTax = useRef('taxValueInput');
this.state = useState({value: 'readonly'});
this.allowTaxEdition = this.__owl__.parent.mode === 'edit' ? props.allowTaxEdition : false;
this.allowTaxEdition = this.props.mode === 'edit' ? this.props.allowTaxEdition : false;
onPatched(this.onPatched);
onWillUpdateProps(this.onWillUpdateProps);
}
//--------------------------------------------------------------------------
// Life cycle methods
//--------------------------------------------------------------------------
willUpdateProps(nextProps) {
onWillUpdateProps(nextProps) {
this.setState('readonly'); // If props are edited, we set the state to readonly
}
patched() {
onPatched() {
if (this.state.value === 'edit') {
this.inputTax.el.focus(); // Focus the input
this.inputTax.el.value = this.props.taxGroup.tax_group_amount;
@@ -88,14 +88,14 @@ class TaxGroupComponent extends Component {
return;
}
this.props.taxGroup.tax_group_amount = newValue;
this.trigger('change-tax-group', {
this.props.onChangeTaxGroup({
oldValue: this.props.taxGroup.tax_group_amount,
newValue: newValue,
taxGroupId: this.props.taxGroup.tax_group_id
});
}
}
TaxGroupComponent.props = ['taxGroup', 'allowTaxEdition', 'record'];
TaxGroupComponent.props = ['taxGroup', 'allowTaxEdition', 'record', 'onChangeTaxGroup', 'mode'];
TaxGroupComponent.template = 'account.TaxGroupComponent';
/**
@@ -106,13 +106,14 @@ TaxGroupComponent.template = 'account.TaxGroupComponent';
currency_id field.
**/
class TaxTotalsComponent extends AbstractFieldOwl {
constructor(...args) {
super(...args);
setup() {
super.setup();
this.totals = useState({value: this.value ? JSON.parse(this.value) : null});
this.allowTaxEdition = this.nodeOptions['allowTaxEdition'];
onWillUpdateProps(this.onWillUpdateProps);
}
willUpdateProps(nextProps) {
onWillUpdateProps(nextProps) {
// We only reformat tax groups if there are changed
this.totals.value = JSON.parse(nextProps.record.data[this.props.fieldName]);
}
+3 -2
View File
@@ -21,7 +21,7 @@
</span>
</t>
<t t-else="">
<span class="o_tax_group_edit" t-on-click.prevent="setState('edit')">
<span class="o_tax_group_edit" t-on-click.prevent="() => this.setState('edit')">
<i class="fa fa-pencil"/>
<span class="o_tax_group_amount_value">
<t t-esc="props.taxGroup.formatted_tax_group_amount"/>
@@ -56,8 +56,9 @@
<TaxGroupComponent
taxGroup="taxGroup"
record="record"
mode="mode"
allowTaxEdition="totals.value.allow_tax_edition"
t-on-change-tax-group="_onChangeTaxValueByTaxGroup"
onChangeTaxGroup.bind="_onChangeTaxValueByTaxGroup"
/>
</t>
</t>
@@ -15,10 +15,11 @@ import { RPCError } from "@web/core/network/rpc_service";
import { BaseAutomationErrorDialog } from "../src/js/base_automation_error_dialog";
import { patchWithCleanup } from "@web/../tests/helpers/utils";
import { DialogContainer } from "@web/core/dialog/dialog_container";
import { getFixture } from "@web/../tests/helpers/utils";
import { getFixture, mount } from "@web/../tests/helpers/utils";
import { nextTick } from "@web/../tests/helpers/utils";
const { mount } = owl;
const { onMounted } = owl;
const serviceRegistry = registry.category("services");
QUnit.module("base_automation", {}, function () {
@@ -80,7 +81,7 @@ QUnit.module("base_automation", {}, function () {
const env = await makeTestEnv();
const { Component: Container, props } = registry.category("main_components").get("DialogContainer");
const dialogContainer = await mount(Container, { target: getFixture(), env, props });
const dialogContainer = await mount(Container, getFixture(), { env, props });
const errorEvent = new PromiseRejectionEvent("error", { reason: {
message: error,
@@ -107,15 +108,15 @@ QUnit.module("base_automation", {}, function () {
});
patchWithCleanup(DialogContainer.prototype, {
mounted() {
setup() {
this._super();
this.el.classList.add("o_dialog_container");
onMounted(() => this.el.classList.add("o_dialog_container"));
}
});
const env = await makeTestEnv();
const { Component: Container, props } = registry.category("main_components").get("DialogContainer");
const dialogContainer = await mount(Container, { target: getFixture(), env, props });
const dialogContainer = await mount(Container, getFixture(), { env, props });
const errorEvent = new PromiseRejectionEvent("error", { reason: {
message: error,
@@ -1,5 +1,6 @@
/** @odoo-module **/
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
@@ -32,6 +33,7 @@ export class ImportRecords extends Component {
}
ImportRecords.template = "base_import.ImportRecords";
ImportRecords.components = { DropdownItem };
const importRecordsItem = {
Component: ImportRecords,
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="base_import.ImportRecords" owl="1">
<DropdownItem class="o_import_menu dropdown-item" t-on-click="importRecords">
<DropdownItem class="'o_import_menu'" onSelected.bind="importRecords">
Import records
</DropdownItem>
</t>
@@ -1,6 +1,7 @@
odoo.define('base_import.ImportMenu', function (require) {
"use strict";
const { DropdownItem } = require('@web/core/dropdown/dropdown_item');
const FavoriteMenu = require('web.FavoriteMenu');
const { useModel } = require('web.Model');
@@ -12,8 +13,7 @@ odoo.define('base_import.ImportMenu', function (require) {
* This component is used to import the records for particular model.
*/
class ImportMenu extends Component {
constructor() {
super(...arguments);
setup() {
this.model = useModel('searchModel');
}
@@ -56,6 +56,7 @@ odoo.define('base_import.ImportMenu', function (require) {
ImportMenu.props = {};
ImportMenu.template = "base_import.ImportRecords";
ImportMenu.components = { DropdownItem };
FavoriteMenu.registry.add('import-menu', ImportMenu, 1);
@@ -1,5 +1,6 @@
/** @odoo-module **/
import { Dropdown } from "@web/core/dropdown/dropdown";
import { registry } from "@web/core/registry";
import { useAutofocus, useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
@@ -82,6 +83,7 @@ export class AddToBoard extends Component {
}
AddToBoard.template = "board.AddToBoard";
AddToBoard.components = { Dropdown };
const addToBoardItem = {
Component: AddToBoard,
@@ -2,10 +2,10 @@
<templates xml:space="preserve">
<t t-name="board.AddToBoard" owl="1">
<Dropdown class="o_add_to_board">
<Dropdown class="'o_add_to_board'">
<t t-set-slot="toggler">Add to my dashboard</t>
<div class="px-3 py-2">
<input type="text" class="o_input" autofocus="" t-model.trim="state.name" t-on-keydown="onInputKeydown" />
<input type="text" class="o_input" t-ref="autofocus" t-model.trim="state.name" t-on-keydown="onInputKeydown" />
</div>
<div class="px-3 py-2">
<button type="button" class="btn btn-primary" t-on-click="addToBoard">Add</button>
@@ -3,9 +3,10 @@ odoo.define('board.AddToBoardMenu', function (require) {
const Context = require('web.Context');
const Domain = require('web.Domain');
const { Dropdown } = require('@web/core/dropdown/dropdown');
const FavoriteMenu = require('web.FavoriteMenu');
const { sprintf } = require('web.utils');
const { useAutofocus } = require('web.custom_hooks');
const { useAutofocus } = require("@web/core/utils/hooks");
const { Component, useState } = owl;
@@ -22,9 +23,7 @@ odoo.define('board.AddToBoardMenu', function (require) {
* This component is only available in actions of type 'ir.actions.act_window'.
*/
class AddToBoardMenu extends Component {
constructor() {
super(...arguments);
setup() {
this.interactive = true;
this.state = useState({
name: this.env.action.name || "",
@@ -143,6 +142,7 @@ odoo.define('board.AddToBoardMenu', function (require) {
AddToBoardMenu.props = {};
AddToBoardMenu.template = 'board.AddToBoard';
AddToBoardMenu.components = { Dropdown };
FavoriteMenu.registry.add('add-to-board-menu', AddToBoardMenu, 10);
+45 -41
View File
@@ -8,7 +8,7 @@ odoo.define("board.dashboard_tests", function (require) {
var ListRenderer = require("web.ListRenderer");
var pyUtils = require("web.py_utils");
const { browser } = require("@web/core/browser/browser");
const { patchWithCleanup } = require("@web/../tests/helpers/utils");
const { getFixture, patchWithCleanup } = require("@web/../tests/helpers/utils");
const { registry } = require("@web/core/registry");
const { makeFakeUserService } = require("@web/../tests/helpers/mock_services");
const {
@@ -35,7 +35,10 @@ odoo.define("board.dashboard_tests", function (require) {
const patchDate = testUtils.mock.patchDate;
const favoriteMenuRegistry = registry.category("favoriteMenu");
const { markup } = owl;
let serverData;
let target;
QUnit.module("Dashboard", {
beforeEach: function () {
this.data = {
@@ -93,6 +96,7 @@ odoo.define("board.dashboard_tests", function (require) {
{ sequence: 10 }
);
serverData = { models: this.data };
target = getFixture();
},
});
@@ -155,7 +159,7 @@ odoo.define("board.dashboard_tests", function (require) {
"</form>",
viewOptions: {
action: {
help: '<p class="hello">click to add a partner</p>',
help: markup('<p class="hello">click to add a partner</p>'),
},
},
});
@@ -886,18 +890,18 @@ odoo.define("board.dashboard_tests", function (require) {
views: [[false, "list"]],
});
assert.containsOnce(webClient, ".o_list_view", "should display the list view");
assert.containsOnce(target, ".o_list_view", "should display the list view");
// Sort the list
await testUtils.dom.click($(".o_column_sortable"));
// Group It
await toggleGroupByMenu(webClient);
await toggleAddCustomGroup(webClient);
await applyGroup(webClient);
await toggleGroupByMenu(target);
await toggleAddCustomGroup(target);
await applyGroup(target);
// add this action to dashboard
await toggleFavoriteMenu(webClient);
await toggleFavoriteMenu(target);
await testUtils.dom.triggerEvent($(".o_add_to_board button.dropdown-toggle"), "mouseenter");
await testUtils.fields.editInput($(".o_add_to_board input"), "a name");
@@ -949,32 +953,32 @@ odoo.define("board.dashboard_tests", function (require) {
var filter_count = 0;
// Add a first filter
await toggleFilterMenu(webClient);
await toggleAddCustomFilter(webClient);
await editConditionValue(webClient, 0, "a");
await applyFilter(webClient);
await toggleFilterMenu(target);
await toggleAddCustomFilter(target);
await editConditionValue(target, 0, "a");
await applyFilter(target);
// Add it to dashboard
await toggleFavoriteMenu(webClient);
await toggleFavoriteMenu(target);
await testUtils.dom.triggerEvent($(".o_add_to_board button.dropdown-toggle"), "mouseenter");
await testUtils.dom.click($(".o_add_to_board .dropdown-menu button"));
// Remove it
await testUtils.dom.click(webClient.el.querySelector(".o_facet_remove"));
await testUtils.dom.click(target.querySelector(".o_facet_remove"));
// Add the second filter
await toggleFilterMenu(webClient);
await toggleAddCustomFilter(webClient);
await editConditionValue(webClient, 0, "b");
await applyFilter(webClient);
await toggleFilterMenu(target);
await toggleAddCustomFilter(target);
await editConditionValue(target, 0, "b");
await applyFilter(target);
// Add it to dashboard
await toggleFavoriteMenu(webClient);
await toggleFavoriteMenu(target);
await testUtils.dom.triggerEvent(
webClient.el.querySelector(".o_add_to_board button.dropdown-toggle"),
target.querySelector(".o_add_to_board button.dropdown-toggle"),
"mouseenter"
);
await testUtils.dom.click(
webClient.el.querySelector(".o_add_to_board .dropdown-menu button")
target.querySelector(".o_add_to_board .dropdown-menu button")
);
});
@@ -1012,19 +1016,19 @@ odoo.define("board.dashboard_tests", function (require) {
});
// Add a filter
await toggleFilterMenu(webClient);
await toggleAddCustomFilter(webClient);
await editConditionValue(webClient, 0, "b");
await applyFilter(webClient);
await toggleFilterMenu(target);
await toggleAddCustomFilter(target);
await editConditionValue(target, 0, "b");
await applyFilter(target);
// Add it to dashboard
await toggleFavoriteMenu(webClient);
await toggleFavoriteMenu(target);
await testUtils.dom.triggerEvent(
webClient.el.querySelector(".o_add_to_board button.dropdown-toggle"),
target.querySelector(".o_add_to_board button.dropdown-toggle"),
"mouseenter"
);
// add
await testUtils.dom.click(
webClient.el.querySelector(".o_add_to_board .dropdown-menu button")
target.querySelector(".o_add_to_board .dropdown-menu button")
);
});
@@ -1253,19 +1257,19 @@ odoo.define("board.dashboard_tests", function (require) {
});
// filter on July 2020
await toggleFilterMenu(webClient);
await toggleMenuItem(webClient, "Date");
await toggleMenuItemOption(webClient, "Date", "July");
await toggleFilterMenu(target);
await toggleMenuItem(target, "Date");
await toggleMenuItemOption(target, "Date", "July");
// compare July 2020 to June 2020
await toggleComparisonMenu(webClient);
await toggleMenuItem(webClient, 0);
await toggleComparisonMenu(target);
await toggleMenuItem(target, 0);
// add the view to the dashboard
await toggleFavoriteMenu(webClient);
await toggleFavoriteMenu(target);
await mouseEnter(webClient.el.querySelector(".o_add_to_board .dropdown-toggle"));
const input = webClient.el.querySelector(".o_add_to_board .dropdown-menu input");
await mouseEnter(target.querySelector(".o_add_to_board .dropdown-toggle"));
const input = target.querySelector(".o_add_to_board .dropdown-menu input");
await testUtils.fields.editInput(input, "Pipeline");
await testUtils.dom.click($(".o_add_to_board div button"));
@@ -1301,9 +1305,9 @@ odoo.define("board.dashboard_tests", function (require) {
views: [[false, "pivot"]],
});
await toggleFavoriteMenu(webClient.el);
await mouseEnter(webClient.el.querySelector(".o_add_to_board .dropdown-toggle"));
const input = webClient.el.querySelector(".o_add_to_board .dropdown-menu input");
await toggleFavoriteMenu(target);
await mouseEnter(target.querySelector(".o_add_to_board .dropdown-toggle"));
const input = target.querySelector(".o_add_to_board .dropdown-menu input");
await testUtils.fields.editInput(input, "Pipeline");
await triggerEvent(input, null, "keydown", { key: "Enter" });
@@ -1411,9 +1415,9 @@ odoo.define("board.dashboard_tests", function (require) {
context: { search_default_filter: 1 },
});
await toggleFavoriteMenu(webClient.el);
await mouseEnter(webClient.el.querySelector(".o_add_to_board .dropdown-toggle"));
const input = webClient.el.querySelector(".o_add_to_board .dropdown-menu input");
await toggleFavoriteMenu(target);
await mouseEnter(target.querySelector(".o_add_to_board .dropdown-toggle"));
const input = target.querySelector(".o_add_to_board .dropdown-menu input");
await testUtils.fields.editInput(input, "Pipeline");
await triggerEvent(input, null, "keydown", { key: "Enter" });
});
@@ -7,25 +7,24 @@ import * as AbstractStorageService from "web.AbstractStorageService";
import { createWebClient } from "@web/../tests/webclient/helpers";
import { assetsWatchdogService } from "@bus/js/services/assets_watchdog_service";
import { click, nextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
import { click, getFixture, nextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
import { browser } from "@web/core/browser/browser";
import { registry } from "@web/core/registry";
const LocalStorageService = AbstractStorageService.extend({
storage: new RamStorage(),
});
const mainComponentRegistry = registry.category("main_components");
const serviceRegistry = registry.category("services");
QUnit.module("Bus Assets WatchDog", (hooks) => {
let legacyServicesRegistry;
let target;
hooks.beforeEach((assert) => {
legacyServicesRegistry = new legacyRegistry();
legacyServicesRegistry.add("bus_service", BusService);
legacyServicesRegistry.add("local_storage", LocalStorageService);
serviceRegistry.add("assetsWatchdog", assetsWatchdogService);
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
clearTimeout: () => {},
@@ -33,6 +32,8 @@ QUnit.module("Bus Assets WatchDog", (hooks) => {
reload: () => assert.step("reloadPage"),
},
});
target = getFixture();
});
QUnit.test("can listen on bus and displays notifications in DOM", async (assert) => {
@@ -54,21 +55,21 @@ QUnit.module("Bus Assets WatchDog", (hooks) => {
}
};
const webClient = await createWebClient({
await createWebClient({
legacyParams: { serviceRegistry: legacyServicesRegistry },
mockRPC,
});
await nextTick();
assert.containsOnce(webClient.el, ".o_notification_body");
assert.containsOnce(target, ".o_notification_body");
assert.strictEqual(
webClient.el.querySelector(".o_notification_body .o_notification_content").textContent,
target.querySelector(".o_notification_body .o_notification_content").textContent,
"The page appears to be out of date."
);
// reload by clicking on the reload button
await click(webClient.el, ".o_notification_buttons .btn-primary");
await click(target, ".o_notification_buttons .btn-primary");
assert.verifySteps(["reloadPage"]);
});
});
@@ -7,7 +7,7 @@ import * as AbstractStorageService from "web.AbstractStorageService";
import { createWebClient } from "@web/../tests/webclient/helpers";
import { calendarNotificationService } from "@calendar/js/services/calendar_notification_service";
import { click, nextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
import { click, getFixture, nextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
import { browser } from "@web/core/browser/browser";
import { registry } from "@web/core/registry";
@@ -18,7 +18,10 @@ const serviceRegistry = registry.category("services");
QUnit.module("Calendar Notification", (hooks) => {
let legacyServicesRegistry;
let target;
hooks.beforeEach(() => {
target = getFixture();
legacyServicesRegistry = new legacyRegistry();
legacyServicesRegistry.add("bus_service", BusService);
legacyServicesRegistry.add("local_storage", LocalStorageService);
@@ -69,23 +72,23 @@ QUnit.module("Calendar Notification", (hooks) => {
}
};
const webClient = await createWebClient({
await createWebClient({
legacyParams: { serviceRegistry: legacyServicesRegistry },
mockRPC,
});
await nextTick();
assert.containsOnce(webClient.el, ".o_notification_body");
assert.containsOnce(target, ".o_notification_body");
assert.strictEqual(
webClient.el.querySelector(".o_notification_body .o_notification_content")
target.querySelector(".o_notification_body .o_notification_content")
.textContent,
"Very old meeting message"
);
await click(webClient.el.querySelector(".o_notification_buttons .btn"));
await click(target.querySelector(".o_notification_buttons .btn"));
assert.verifySteps(["notifyAck"]);
assert.containsNone(webClient.el, ".o_notification");
assert.containsNone(target, ".o_notification");
}
);
@@ -139,23 +142,23 @@ QUnit.module("Calendar Notification", (hooks) => {
};
serviceRegistry.add("action", fakeActionService, { force: true });
const webClient = await createWebClient({
await createWebClient({
legacyParams: { serviceRegistry: legacyServicesRegistry },
mockRPC,
});
await nextTick();
assert.containsOnce(webClient.el, ".o_notification_body");
assert.containsOnce(target, ".o_notification_body");
assert.strictEqual(
webClient.el.querySelector(".o_notification_body .o_notification_content")
target.querySelector(".o_notification_body .o_notification_content")
.textContent,
"Very old meeting message"
);
await click(webClient.el.querySelectorAll(".o_notification_buttons .btn")[1]);
await click(target.querySelectorAll(".o_notification_buttons .btn")[1]);
assert.verifySteps(["calendar.action_calendar_event_notify"]);
assert.containsNone(webClient.el, ".o_notification");
assert.containsNone(target, ".o_notification");
}
);
@@ -197,23 +200,23 @@ QUnit.module("Calendar Notification", (hooks) => {
}
};
const webClient = await createWebClient({
await createWebClient({
legacyParams: { serviceRegistry: legacyServicesRegistry },
mockRPC,
});
await nextTick();
assert.containsOnce(webClient.el, ".o_notification_body");
assert.containsOnce(target, ".o_notification_body");
assert.strictEqual(
webClient.el.querySelector(".o_notification_body .o_notification_content")
target.querySelector(".o_notification_body .o_notification_content")
.textContent,
"Very old meeting message"
);
await click(webClient.el.querySelectorAll(".o_notification_buttons .btn")[2]);
await click(target.querySelectorAll(".o_notification_buttons .btn")[2]);
assert.verifySteps([], "should only close the notification withtout calling a rpc");
assert.containsNone(webClient.el, ".o_notification");
assert.containsNone(target, ".o_notification");
}
);
});
+26 -23
View File
@@ -1,6 +1,6 @@
/** @odoo-module **/
import { legacyExtraNextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
import { getFixture, legacyExtraNextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
import {
setupControlPanelServiceRegistry,
switchView,
@@ -29,6 +29,7 @@ const forecastDomain = (forecastStart) => {
};
let serverData;
let target;
QUnit.module("Views", (hooks) => {
hooks.beforeEach(async () => {
serverData = {
@@ -66,6 +67,8 @@ QUnit.module("Views", (hooks) => {
};
setupControlPanelServiceRegistry();
serviceRegistry.add("dialog", dialogService);
target = getFixture();
});
QUnit.module("Forecast views");
@@ -241,24 +244,24 @@ QUnit.module("Views", (hooks) => {
},
});
assert.containsOnce(webClient, ".o_switch_view.o_graph.active");
assert.containsOnce(target, ".o_switch_view.o_graph.active");
await toggleGroupByMenu(webClient);
await toggleMenuItem(webClient, "Date Field");
await toggleMenuItemOption(webClient, "Date Field", "Quarter");
await toggleGroupByMenu(target);
await toggleMenuItem(target, "Date Field");
await toggleMenuItemOption(target, "Date Field", "Quarter");
await switchView(webClient, "legacy_toy");
await switchView(target, "legacy_toy");
await legacyExtraNextTick();
assert.containsOnce(webClient, ".o_switch_view.o_legacy_toy.active");
assert.containsOnce(target, ".o_switch_view.o_legacy_toy.active");
await cpHelpers.toggleGroupByMenu(webClient);
await toggleMenuItem(webClient, "Date Field");
await toggleMenuItemOption(webClient, "Date Field", "Year");
await cpHelpers.toggleGroupByMenu(target);
await toggleMenuItem(target, "Date Field");
await toggleMenuItemOption(target, "Date Field", "Year");
await switchView(webClient, "graph");
await switchView(target, "graph");
assert.containsOnce(webClient, ".o_switch_view.o_graph.active");
assert.containsOnce(target, ".o_switch_view.o_graph.active");
// second doAction legacy_toy -> forecast_graph -> legacy_toy
@@ -277,24 +280,24 @@ QUnit.module("Views", (hooks) => {
});
await legacyExtraNextTick();
assert.containsOnce(webClient, ".o_switch_view.o_legacy_toy.active");
assert.containsOnce(target, ".o_switch_view.o_legacy_toy.active");
await cpHelpers.toggleGroupByMenu(webClient);
await toggleMenuItem(webClient, "Date Field");
await toggleMenuItemOption(webClient, "Date Field", "Quarter");
await cpHelpers.toggleGroupByMenu(target);
await toggleMenuItem(target, "Date Field");
await toggleMenuItemOption(target, "Date Field", "Quarter");
await switchView(webClient, "graph");
await switchView(target, "graph");
assert.containsOnce(webClient, ".o_switch_view.o_graph.active");
assert.containsOnce(target, ".o_switch_view.o_graph.active");
await toggleGroupByMenu(webClient);
await toggleMenuItem(webClient, "Date Field");
await toggleMenuItemOption(webClient, "Date Field", "Year");
await toggleGroupByMenu(target);
await toggleMenuItem(target, "Date Field");
await toggleMenuItemOption(target, "Date Field", "Year");
await switchView(webClient, "legacy_toy");
await switchView(target, "legacy_toy");
await legacyExtraNextTick();
assert.containsOnce(webClient, ".o_switch_view.o_legacy_toy.active");
assert.containsOnce(target, ".o_switch_view.o_legacy_toy.active");
unpatchDate();
}
@@ -77,6 +77,9 @@ odoo.define('event.configurator.tests', function (require) {
}
}, function (){
QUnit.test('Select a regular product and verify that the event configurator is not opened', async function (assert) {
// When the module event_booth_sale is installed, it will also call the rpc: read/detailed_type when selecting a product.
// This means that 2 asserts are done when this module is installed.
// Note that: this test will fail if the module event_booth_sale is not installed.
assert.expect(2);
var form = await createView({
@@ -6,7 +6,7 @@
role="menuitem"
class="o_menu_item oe_share_gdoc_item dropdown-item"
>
<a href="#" t-esc="gdriveItem.name" t-on-click.prevent="_onGoogleDocItemClick(gdriveItem.id)"/>
<a href="#" t-esc="gdriveItem.name" t-on-click.prevent="() => this._onGoogleDocItemClick(gdriveItem.id)"/>
</li>
</ul>
</li>
@@ -1,5 +1,6 @@
/** @odoo-module **/
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
@@ -41,6 +42,7 @@ export class AddToGoogleSpreadsheet extends Component {
}
AddToGoogleSpreadsheet.template = "google_spreadsheet.AddToGoogleSpreadsheet";
AddToGoogleSpreadsheet.components = { DropdownItem };
const addToGoogleSpreadsheetItem = {
Component: AddToGoogleSpreadsheet,
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="google_spreadsheet.AddToGoogleSpreadsheet" owl="1">
<DropdownItem class="o_add_to_spreadsheet dropdown-item" t-on-click="addToGoogleSpreadsheet">
<DropdownItem class="'o_add_to_spreadsheet'" onSelected.bind="addToGoogleSpreadsheet">
Add to Google Spreadsheet
</DropdownItem>
</t>
@@ -2,6 +2,7 @@ odoo.define('board.AddToGoogleSpreadsheetMenu', function (require) {
"use strict";
const Domain = require('web.Domain');
const { DropdownItem } = require('@web/core/dropdown/dropdown_item');
const FavoriteMenu = require('web.FavoriteMenu');
const { Component } = owl;
@@ -56,6 +57,7 @@ odoo.define('board.AddToGoogleSpreadsheetMenu', function (require) {
AddToGoogleSpreadsheetMenu.props = {};
AddToGoogleSpreadsheetMenu.template = "google_spreadsheet.AddToGoogleSpreadsheet";
AddToGoogleSpreadsheetMenu.components = { DropdownItem };
FavoriteMenu.registry.add('add-to-google-spreadsheet-menu', AddToGoogleSpreadsheetMenu, 20);
@@ -16,9 +16,9 @@ QUnit.module('thread_view_tests.js', {
this.start = async params => {
const res = await start({ ...params, data: this.data });
const { afterEvent, components, env, widget } = res;
const { afterEvent, apps, env, widget } = res;
this.afterEvent = afterEvent;
this.components = components;
this.apps = apps;
this.env = env;
this.widget = widget;
return res;
@@ -1,8 +1,11 @@
/* @odoo-module */
import Popover from "web.Popover";
const { Component } = owl;
export class TimeOffCardPopover extends Component {}
TimeOffCardPopover.components = { Popover };
TimeOffCardPopover.template = 'hr_holidays.TimeOffCardPopover';
TimeOffCardPopover.props = ['allocated', 'approved', 'planned', 'left'];
@@ -4,37 +4,35 @@ import { useService } from '@web/core/utils/hooks';
import { getLangDateFormat } from 'web.time';
import widgetRegistry from 'web.widgetRegistry';
const { Component, useState } = owl;
const { Component, useState, onWillStart, onWillUpdateProps } = owl;
export class LeaveStatsComponent extends Component {
setup() {
super.setup(...arguments);
this.rpc = useService('rpc');
this.state = useState({
leaves: [],
departmentLeaves: [],
});
}
async willStart() {
await this.loadLeaves(this.date, this.employee);
await this.loadDepartmentLeaves(this.date, this.department, this.employee);
}
onWillStart(async () => {
await this.loadLeaves(this.date, this.employee);
await this.loadDepartmentLeaves(this.date, this.department, this.employee);
});
onWillUpdateProps(async (nextProps) => {
const dateFrom = nextProps.record.data.date_from || moment();
const dateChanged = this.date !== dateFrom;
const employee = nextProps.record.data.employee_id.data;
const department = nextProps.record.data.department_id.data;
async willUpdateProps(nextProps) {
const dateFrom = nextProps.record.data.date_from || moment();
const dateChanged = this.date !== dateFrom;
const employee = nextProps.record.data.employee_id.data;
const department = nextProps.record.data.department_id.data;
if (dateChanged || employee && (this.employee && this.employee.id) !== employee.id) {
await this.loadLeaves(dateFrom, employee);
}
if (dateChanged || employee && (this.employee && this.employee.id) !== employee.id) {
await this.loadLeaves(dateFrom, employee);
}
if (dateChanged || department && (this.department && this.department.id) !== department.id) {
await this.loadDepartmentLeaves(dateFrom, department, employee);
}
if (dateChanged || department && (this.department && this.department.id) !== department.id) {
await this.loadDepartmentLeaves(dateFrom, department, employee);
}
})
}
get date() {
@@ -8,7 +8,7 @@
<div t-if="state.leaves.length === 0">
None
</div>
<div t-foreach="state.leaves" t-as="leave" class="d-flex flex-row justify-content-between">
<div t-foreach="state.leaves" t-as="leave" t-key="leave_index" class="d-flex flex-row justify-content-between">
<span t-esc="leave.holiday_status_id[1]"/>
<span><t t-esc="leave.number_of_days"/> day(s)</span>
</div>
@@ -21,7 +21,7 @@
<div t-if="state.departmentLeaves.length === 0">
None
</div>
<div t-foreach="state.departmentLeaves" t-as="leave" t-attf-class="d-flex flex-row justify-content-between {{leave.sameEmployee ? 'font-weight-bold': ''}}">
<div t-foreach="state.departmentLeaves" t-as="leave" t-key="leave_index" t-attf-class="d-flex flex-row justify-content-between {{leave.sameEmployee ? 'font-weight-bold': ''}}">
<span><t t-esc="leave.employee_id[1]"/>: <t t-esc="leave.number_of_days"/> day(s)</span>
<span><t t-esc="leave.dateFrom"/> - <t t-esc="leave.dateTo"/></span>
</div>
@@ -5,9 +5,11 @@ const {_t} = require('web.core');
const {Markup} = require('web.utils');
var tour = require('web_tour.tour');
const { markup } = owl;
tour.register('hr_recruitment_tour',{
url: "/web",
rainbowManMessage: _t("<div>Great job! You hired a new colleague!</div><div>Try the Website app to publish job offers online.</div>"),
rainbowManMessage: markup(_t("<div>Great job! You hired a new colleague!</div><div>Try the Website app to publish job offers online.</div>")),
fadeout: 'very_slow',
sequence: 230,
}, [tour.stepUtils.showAppsMenuItem(), {
@@ -7,9 +7,10 @@ class InsufficientCreditDialog extends Dialog {
setup() {
super.setup(...arguments);
this.orm = useService("orm");
owl.onWillStart(this.onWillStart);
}
async willStart() {
async onWillStart() {
const { errorData } = this.props;
this.url = await this.orm.call("iap.account", "get_credits_url", [], {
base_url: errorData.base_url,
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<span t-name="LunchWidget">
<t t-foreach="widget.alerts" t-as="alert">
<t t-foreach="widget.alerts" t-as="alert" t-key="alert_index">
<div class="alert alert-warning mb-0" role="alert">
<t t-out="alert.message"/>
</div>
@@ -43,7 +43,7 @@
<span t-if="widget.raw_state != 'new'" t-esc="widget.state" t-attf-class="badge badge-pill {{ state_class }}"/>
</h4>
<ul class="list-unstyled o_lunch_widget_lines">
<li t-foreach="widget.lines" t-as="line">
<li t-foreach="widget.lines" t-as="line" t-key="line_index">
<div class="d-flex align-items-center">
<div class="flex-grow-0 flex-shrink-0 o_lunch_product_quantity">
<button class="btn btn-sm btn-icon btn-link fa fa-minus-circle o_remove_product" t-if="widget.raw_state != 'confirmed'" t-attf-data-id="{{ line.id }}"/>
@@ -60,7 +60,7 @@
</t>
</div>
</div>
<div t-foreach="line.toppings" t-as="topping" class="d-flex flex-row">
<div t-foreach="line.toppings" t-as="topping" t-key="topping_index" class="d-flex flex-row">
<div class="flex-grow-1 pl-5">
<span>+ <t t-esc="topping[0]"/></span>
</div>
@@ -4,6 +4,7 @@ const GeneratePriceList = require('product.generate_pricelist').GeneratePriceLis
const testUtils = require('web.test_utils');
const { createWebClient, doAction } = require('@web/../tests/webclient/helpers');
const { getFixture, patchWithCleanup } = require("@web/../tests/helpers/utils");
let serverData;
@@ -38,9 +39,8 @@ QUnit.module('Product Pricelist', {
QUnit.test('Pricelist Client Action', async function (assert) {
assert.expect(21);
const self = this;
let Qty = [1, 5, 10]; // default quantities
testUtils.mock.patch(GeneratePriceList, {
patchWithCleanup(GeneratePriceList.prototype, {
_onFieldChanged: function (event) {
assert.step('field_changed');
return this._super.apply(this, arguments);
@@ -57,6 +57,7 @@ QUnit.module('Product Pricelist', {
}
};
const target = getFixture();
const webClient = await createWebClient({ serverData, mockRPC });
await doAction(webClient, {
id: 1,
@@ -72,7 +73,7 @@ QUnit.module('Product Pricelist', {
});
// checking default pricelist
assert.strictEqual($(webClient.el).find('.o_field_many2one input').val(), "Public Pricelist",
assert.strictEqual($(target).find('.o_field_many2one input').val(), "Public Pricelist",
"should have default pricelist");
// changing pricelist
@@ -80,28 +81,28 @@ QUnit.module('Product Pricelist', {
await testUtils.fields.many2one.clickItem("pricelist_id", "Test");
// check wherther pricelist value has been updated or not. along with that check default quantities should be there.
assert.strictEqual($(webClient.el).find('.o_field_many2one input').val(), "Test",
assert.strictEqual($(target).find('.o_field_many2one input').val(), "Test",
"After pricelist change, the pricelist_id field should be updated");
assert.strictEqual($(webClient.el).find('.o_badges > .badge').length, 3,
assert.strictEqual($(target).find('.o_badges > .badge').length, 3,
"There should be 3 default Quantities");
// existing quantity can not be added.
await testUtils.dom.click($(webClient.el).find('.o_add_qty'));
await testUtils.dom.click($(target).find('.o_add_qty'));
let notificationElement = document.body.querySelector('.o_notification_manager .o_notification.bg-info');
assert.strictEqual(notificationElement.querySelector('.o_notification_content').textContent,
"Quantity already present (1).", "Existing Quantity can not be added");
// adding few more quantities to check.
$(webClient.el).find('.o_product_qty').val(2);
$(target).find('.o_product_qty').val(2);
Qty.push(2);
await testUtils.dom.click($(webClient.el).find('.o_add_qty'));
$(webClient.el).find('.o_product_qty').val(3);
await testUtils.dom.click($(target).find('.o_add_qty'));
$(target).find('.o_product_qty').val(3);
Qty.push(3);
await testUtils.dom.click($(webClient.el).find('.o_add_qty'));
await testUtils.dom.click($(target).find('.o_add_qty'));
// should not be added more then 5 quantities.
$(webClient.el).find('.o_product_qty').val(4);
await testUtils.dom.click($(webClient.el).find('.o_add_qty'));
$(target).find('.o_product_qty').val(4);
await testUtils.dom.click($(target).find('.o_add_qty'));
notificationElement = document.body.querySelector('.o_notification_manager .o_notification.bg-warning');
assert.strictEqual(notificationElement.querySelector('.o_notification_content').textContent,
@@ -110,15 +111,15 @@ QUnit.module('Product Pricelist', {
// removing all the quantities should work
Qty.pop(10);
await testUtils.dom.click($(webClient.el).find('.o_badges .badge:contains("10") .o_remove_qty'));
await testUtils.dom.click($(target).find('.o_badges .badge:contains("10") .o_remove_qty'));
Qty.pop(5);
await testUtils.dom.click($(webClient.el).find('.o_badges .badge:contains("5") .o_remove_qty'));
await testUtils.dom.click($(target).find('.o_badges .badge:contains("5") .o_remove_qty'));
Qty.pop(3);
await testUtils.dom.click($(webClient.el).find('.o_badges .badge:contains("3") .o_remove_qty'));
await testUtils.dom.click($(target).find('.o_badges .badge:contains("3") .o_remove_qty'));
Qty.pop(2);
await testUtils.dom.click($(webClient.el).find('.o_badges .badge:contains("2") .o_remove_qty'));
await testUtils.dom.click($(target).find('.o_badges .badge:contains("2") .o_remove_qty'));
Qty.pop(1);
await testUtils.dom.click($(webClient.el).find('.o_badges .badge:contains("1") .o_remove_qty'));
await testUtils.dom.click($(target).find('.o_badges .badge:contains("1") .o_remove_qty'));
assert.verifySteps([
'field_changed',
@@ -130,8 +131,6 @@ QUnit.module('Product Pricelist', {
'qty_changed',
'qty_changed'
]);
testUtils.mock.unpatch(GeneratePriceList);
});
}
@@ -3,26 +3,17 @@
import ControlPanel from 'web.ControlPanel';
import session from 'web.session';
const { onWillStart, onWillUpdateProps } = owl;
export class ProjectControlPanel extends ControlPanel {
constructor() {
super(...arguments);
this.show_project_update = this.env.view.type === "form" || this.props.action.context.show_project_update;
setup() {
super.setup();
this.show_project_update = this.props.view.type === "form" || this.props.action.context.show_project_update;
this.project_id = this.show_project_update ? this.props.action.context.active_id : false;
}
async willStart() {
const promises = [];
promises.push(super.willStart(...arguments));
promises.push(this._loadWidgetData());
return Promise.all(promises);
}
async willUpdateProps() {
const promises = [];
promises.push(super.willUpdateProps(...arguments));
promises.push(this._loadWidgetData());
return Promise.all(promises);
onWillStart(() => this._loadWidgetData());
onWillUpdateProps(() => this._loadWidgetData());
}
async _loadWidgetData() {
@@ -3,11 +3,10 @@
import { AddMilestone, OpenMilestone } from '@project/js/right_panel/project_utils';
import { formatFloat } from "@web/fields/formatters";
const { Component, useState } = owl;
const { Component, onWillStart, onWillUpdateProps, useState } = owl;
export default class ProjectRightPanel extends Component {
constructor() {
super(...arguments);
setup() {
this.context = this.props.action.context;
this.domain = this.props.action.domain;
this.project_id = this.context.active_id;
@@ -19,22 +18,15 @@ export default class ProjectRightPanel extends Component {
user: {},
}
});
onWillStart(() => this._loadQwebContext());
onWillUpdateProps(() => this._loadQwebContext());
}
formatFloat(value) {
return formatFloat(value, { digits: [false, 1] });
}
async willStart() {
await super.willStart(...arguments);
await this._loadQwebContext();
}
async willUpdateProps() {
await super.willUpdateProps(...arguments);
await this._loadQwebContext();
}
async _loadQwebContext() {
const data = await this.rpc({
model: 'project.project',
@@ -2,25 +2,17 @@
import { _lt } from 'web.core';
import fieldUtils from 'web.field_utils';
import { ComponentAdapter } from 'web.OwlCompatibility';
import { ComponentAdapter, standaloneAdapter } from 'web.OwlCompatibility';
import { FormViewDialog } from 'web.view_dialogs';
const { Component, useRef, useState } = owl;
const { Component, onWillUpdateProps, useRef, useState } = owl;
class MilestoneComponent extends Component {
constructor() {
super(...arguments);
setup() {
super.setup();
this.contextValue = Object.assign({}, {
'default_project_id': this.props.context.active_id,
}, this.props.context);
this.FormViewDialog = FormViewDialog;
this.state = useState({
openDialog: false
});
this._dialogRef = useRef('dialog');
this._isDialogOpen = false;
this._createContext = this._createContext.bind(this);
this._onDialogSaved = this._onDialogSaved.bind(this);
}
get context() {
@@ -33,65 +25,55 @@ class MilestoneComponent extends Component {
}, value);
}
dialogClosed() {
this._isDialogOpen = false;
this.state.openDialog = false;
}
patched() {
if (this.state.openDialog && !this._isDialogOpen) {
this._isDialogOpen = true;
this._dialogRef.comp.widget.on('closed', this, () => {
this.dialogClosed();
});
this._dialogRef.comp.widget.open();
}
}
_createContext() {
return Object.assign({}, {
'default_project_id': this.contextValue.active_id,
}, this.contextValue);
}
async _onDialogSaved() {
await this.__owl__.parent.willUpdateProps();
async openLegacyFormDialog(params) {
const adapterParent = standaloneAdapter({ Component });
const dialog = new FormViewDialog(adapterParent, {
context: this.context,
res_model: "project.milestone",
res_id: false,
on_saved: this.props.onMilestoneUpdate,
...params,
});
await dialog.open();
return dialog;
}
}
MilestoneComponent.components = { ComponentAdapter };
export class AddMilestone extends MilestoneComponent {
get NEW_PROJECT_MILESTONE() {
return _lt("New Milestone");
}
onAddMilestoneClick(event) {
if (!this._isDialogOpen) {
event.stopPropagation();
this.state.openDialog = true;
}
event.stopPropagation();
this.openLegacyFormDialog({
_createContext: () => {
return {
default_project_id: this.contextValue.active_id,
...this.contextValue
};
},
title: this.env._t("New Milestone"),
multi_select: true
});
}
}
AddMilestone.template = 'project.AddMilestone';
export class OpenMilestone extends MilestoneComponent {
constructor() {
super(...arguments);
setup() {
super.setup();
this.milestone = useState(this.props.milestone);
this.state.colorClass = this.milestone.is_deadline_exceeded ? "o_milestone_danger" : "";
this.state.checkboxIcon = this.milestone.is_reached ? "fa-check-square-o" : "fa-square-o";
}
get OPEN_PROJECT_MILESTONE() {
return _lt("Milestone");
this.state = useState({
colorClass: this.milestone.is_deadline_exceeded ? "o_milestone_danger" : "",
checkboxIcon: this.milestone.is_reached ? "fa-check-square-o" : "fa-square-o",
});
onWillUpdateProps(this.onWillUpdateProps);
}
get deadline() {
return fieldUtils.format.date(moment(this.milestone.deadline));
}
willUpdateProps(nextProps) {
onWillUpdateProps(nextProps) {
if (nextProps.milestone) {
this.milestone = nextProps.milestone;
this.state.colorClass = this.milestone.is_deadline_exceeded ? "o_milestone_danger" : "";
@@ -102,24 +84,27 @@ export class OpenMilestone extends MilestoneComponent {
}
}
dialogClosed() {
super.dialogClosed();
this.write_mutex = false;
}
async onDeleteMilestone() {
await this.rpc({
model: 'project.milestone',
method: 'unlink',
args: [this.milestone.id]
});
await this.__owl__.parent.willUpdateProps();
await this.props.onMilestoneUpdate();
}
onOpenMilestone() {
if (!this._isDialogOpen && !this.write_mutex) {
async onOpenMilestone() {
if (!this.write_mutex) {
this.write_mutex = true;
this.state.openDialog = true;
const dialog = await this.openLegacyFormDialog({
res_id: this.milestone.id,
title: this.env._t("Milestone"),
disable_multiple_selection: true,
deletable: true
});
dialog.on('closed', this, () => {
this.write_mutex = false;
});
}
}
@@ -8,7 +8,7 @@ var tour = require('web_tour.tour');
tour.register('project_tour', {
sequence: 110,
url: "/web",
rainbowManMessage: "Congratulations, you are now a master of project management.",
rainbowManMessage: _t("Congratulations, you are now a master of project management."),
}, [tour.stepUtils.showAppsMenuItem(), {
trigger: '.o_app[data-menu-xmlid="project.menu_main_pm"]',
content: Markup(_t('Want a better way to <b>manage your projects</b>? <i>It starts here.</i>')),
@@ -3,30 +3,28 @@
import { ControlPanel } from "@web/search/control_panel/control_panel";
import { useService } from "@web/core/utils/hooks";
const { onWillStart, onWillUpdateProps } = owl;
export class ProjectControlPanel extends ControlPanel {
constructor() {
super(...arguments);
setup() {
super.setup();
this.orm = useService("orm");
this.user = useService("user");
const { active_id, show_project_update } = this.env.searchModel.globalContext;
this.showProjectUpdate = this.env.config.viewType === "form" || show_project_update;
this.projectId = this.showProjectUpdate ? active_id : false;
}
async willStart() {
const proms = [super.willStart(...arguments)];
if (this.showProjectUpdate) {
proms.push(this.loadData());
}
await Promise.all(proms);
}
onWillStart(async () => {
if (this.showProjectUpdate) {
await this.loadData();
}
});
async willUpdateProps() {
const proms = [super.willUpdateProps(...arguments)];
if (this.showProjectUpdate) {
proms.push(this.loadData());
}
await Promise.all(proms);
onWillUpdateProps(async () => {
if (this.showProjectUpdate) {
await this.loadData();
}
});
}
async loadData() {
@@ -1,13 +1,13 @@
/** @odoo-module **/
import { useBus, useEffect, useService } from '@web/core/utils/hooks';
import { useBus, useService } from '@web/core/utils/hooks';
import { ActionContainer } from '@web/webclient/actions/action_container';
import { MainComponentsContainer } from "@web/core/main_components_container";
import { useOwnDebugContext } from "@web/core/debug/debug_context";
import { ErrorHandler, NotUpdatable } from "@web/core/utils/components";
import { session } from '@web/session';
const { Component } = owl;
const { Component, useEffect } = owl;
export class ProjectSharingWebClient extends Component {
setup() {
@@ -29,8 +29,6 @@ export class ProjectSharingWebClient extends Component {
);
}
mounted() { }
handleComponentError(error, C) {
// remove the faulty component
this.Components.splice(this.Components.indexOf(C), 1);
@@ -4,7 +4,7 @@
<table class="o_dow_widget">
<thead>
<tr>
<th t-foreach="widget.labels" t-as="label">
<th t-foreach="widget.labels" t-as="label" t-key="label_index">
<t t-esc="label"/>
</th>
</tr>
@@ -37,7 +37,7 @@
<div class="o_rightpanel_section">
<div class="o_form_view">
<div class="oe_button_box o_full">
<t t-foreach="state.data.buttons" t-as="button" t-key="button.order">
<t t-foreach="state.data.buttons" t-as="button" t-key="button.action">
<button class="btn oe_stat_button" t-if="button.show"
t-on-click="onProjectActionClick"
t-att-data-action="button.action"
@@ -67,12 +67,12 @@
Milestones
</div>
<span class="o_rightpanel_button">
<AddMilestone context="context"/>
<AddMilestone context="context" onMilestoneUpdate.bind="_loadQwebContext"/>
</span>
</div>
<div class="o_rightpanel_data">
<div t-foreach="milestones.data" t-as="milestone" t-key="milestone.id" class="o_rightpanel_data_row">
<OpenMilestone context="context" milestone="milestone" t-key="milestone.id"/>
<OpenMilestone context="context" milestone="milestone" onMilestoneUpdate.bind="_loadQwebContext"/>
</div>
<span t-if="milestones.data.length === 0" class="text-muted font-italic">
Track major progress points that must be reached to achieve success.
@@ -84,21 +84,6 @@
<t t-name="project.AddMilestone" owl="1">
<div class="o_add_milestone">
<a t-on-click="onAddMilestoneClick">Add Milestone</a>
<span t-if="state.openDialog" class="o_rightpanel_hidden">
<ComponentAdapter
Component="FormViewDialog"
params="{
context: context,
_createContext: _createContext,
on_saved: _onDialogSaved,
res_id: false,
res_model: 'project.milestone',
title: NEW_PROJECT_MILESTONE,
multi_select: true,
}"
t-ref="dialog"
/>
</span>
</div>
</t>
@@ -118,21 +103,6 @@
<span class="o_rightpanel_right_col">
<a class="o_delete_icon" t-on-click="onDeleteMilestone" title="Delete Milestone"><i class="fa fa-trash"></i></a>
</span>
<span t-if="state.openDialog" class="o_rightpanel_hidden">
<ComponentAdapter
Component="FormViewDialog"
params="{
context: context,
on_saved: _onDialogSaved,
res_id: milestone.id,
res_model: 'project.milestone',
title: OPEN_PROJECT_MILESTONE,
disable_multiple_selection: true,
deletable: true
}"
t-ref="dialog"
/>
</span>
</div>
</t>
<t t-name="project.task.PrivateProjectName">
+3 -1
View File
@@ -5,6 +5,8 @@ const {_t} = require('web.core');
const {Markup} = require('web.utils');
var tour = require('web_tour.tour');
const { markup } = owl;
tour.register("sale_tour", {
url: "/web",
rainbowMan: false,
@@ -58,7 +60,7 @@ tour.register("sale_tour", {
tour.register("sale_quote_tour", {
url: "/web#action=sale.action_quotations_with_onboarding&view_type=form",
rainbowMan: true,
rainbowManMessage: "<b>Congratulations</b>, your first quotation is sent!<br>Check your email to validate the quote.",
rainbowManMessage: markup(_t("<b>Congratulations</b>, your first quotation is sent!<br>Check your email to validate the quote.")),
sequence: 30,
}, [{
trigger: ".o_form_editable .o_field_many2one[name='partner_id']",
@@ -5,12 +5,18 @@ odoo.define('stock.stock_traceability_report_backend_tests', function (require)
const dom = require('web.dom');
const StockReportGeneric = require('stock.stock_report_generic');
const testUtils = require('web.test_utils');
const { patch, unpatch } = require('web.utils');
const { dom: domUtils } = testUtils;
const { legacyExtraNextTick } = require("@web/../tests/helpers/utils");
const {
destroy,
getFixture,
legacyExtraNextTick,
patchWithCleanup,
} = require("@web/../tests/helpers/utils");
const { createWebClient, doAction } = require('@web/../tests/webclient/helpers');
const { onMounted, onWillUnmount } = owl;
/**
* Helper function to instantiate a stock report action.
* @param {Object} params
@@ -80,17 +86,17 @@ odoo.define('stock.stock_traceability_report_backend_tests', function (require)
let mountCount = 0;
patch(ControlPanel.prototype, 'test.ControlPanel', {
mounted() {
mountCount = mountCount + 1;
this.__uniqueId = mountCount;
assert.step(`mounted ${this.__uniqueId}`);
this.__superMounted = this._super.bind(this);
this.__superMounted(...arguments);
},
willUnmount() {
assert.step(`willUnmount ${this.__uniqueId}`);
this.__superMounted(...arguments);
patchWithCleanup(ControlPanel.prototype, {
setup() {
this._super();
onMounted(() => {
mountCount = mountCount + 1;
this.__uniqueId = mountCount;
assert.step(`mounted ${this.__uniqueId}`);
});
onWillUnmount(() => {
assert.step(`willUnmount ${this.__uniqueId}`);
});
},
});
const serverData = {
@@ -120,6 +126,7 @@ odoo.define('stock.stock_traceability_report_backend_tests', function (require)
},
};
const target = getFixture();
const webClient = await createWebClient({
serverData,
mockRPC: function (route) {
@@ -132,11 +139,12 @@ odoo.define('stock.stock_traceability_report_backend_tests', function (require)
});
await doAction(webClient, 42);
await domUtils.click($(webClient.el).find('.o_stock_reports_web_action'));
await domUtils.click(target.querySelector('.o_stock_reports_web_action'));
await legacyExtraNextTick();
await domUtils.click($(webClient.el).find('.breadcrumb-item:first'));
await domUtils.click(target.querySelector('.breadcrumb-item'));
await legacyExtraNextTick();
webClient.destroy();
destroy(webClient);
assert.verifySteps([
'mounted 1',
@@ -146,8 +154,6 @@ odoo.define('stock.stock_traceability_report_backend_tests', function (require)
'mounted 3',
'willUnmount 3',
]);
unpatch(ControlPanel.prototype, 'test.ControlPanel');
});
});
});
@@ -30,7 +30,7 @@
</tr>
</thead>
<tbody>
<tr t-foreach="tours" t-as="tour">
<tr t-foreach="tours" t-as="tour" t-key="tour.name">
<td><t t-esc="tour.sequence"/></td>
<td><t t-esc="tour.name"/></td>
<td><t t-esc="tour.url"/></td>
@@ -12,8 +12,10 @@ var ServicesMixin = require('web.ServicesMixin');
var session = require('web.session');
var Tip = require('web_tour.Tip');
const {Markup} = require('web.utils');
const { config: transitionConfig } = require("@web/core/transition");
var _t = core._t;
const { markup } = owl;
var RUNNING_TOUR_TIMEOUT = 10000;
@@ -39,6 +41,12 @@ return core.Class.extend(mixins.EventDispatcherMixin, ServicesMixin, {
});
this.disabled = disabled;
this.running_tour = local_storage.getItem(get_running_key());
if (this.running_tour) {
// Transitions can cause DOM mutations which will cause the tour_service
// MutationObserver to wait longer before proceeding to the next step
// this can slow down tours
transitionConfig.disabled = true;
}
this.running_step_delay = parseInt(local_storage.getItem(get_running_delay_key()), 10) || 0;
this.edition = (_.last(session.server_version_info) === 'e') ? 'enterprise' : 'community';
this._log = [];
@@ -430,14 +438,13 @@ return core.Class.extend(mixins.EventDispatcherMixin, ServicesMixin, {
if (message) {
message = typeof message === 'function' ? message() : message;
} else {
message = _t('<strong><b>Good job!</b> You went through all steps of this tour.</strong>');
message = markup(_t('<strong><b>Good job!</b> You went through all steps of this tour.</strong>'));
}
const fadeout = this.tours[tour_name].fadeout;
core.bus.trigger('show-effect', {
type: "rainbow_man",
message,
fadeout,
messageIsHtml: true,
});
}
this.tours[tour_name].current_step = 0;
@@ -7,15 +7,12 @@ import { ormService } from "@web/core/orm_service";
import { registry } from "@web/core/registry";
import { uiService } from "@web/core/ui/ui_service";
import { click, getFixture } from "@web/../tests/helpers/utils";
import { registerCleanup } from "@web/../tests/helpers/cleanup";
import { click, getFixture, mount } from "@web/../tests/helpers/utils";
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services";
import { DebugMenuParent } from "@web/../tests/core/debug/debug_manager_tests";
import { fakeCommandService } from "@web/../tests/helpers/mock_services";
const { mount } = owl;
const debugRegistry = registry.category("debug");
let target;
@@ -60,8 +57,7 @@ QUnit.module("Tours", (hooks) => {
};
const env = await makeTestEnv({ mockRPC });
const debugManager = await mount(DebugMenuParent, { env, target });
registerCleanup(() => debugManager.destroy());
const debugManager = await mount(DebugMenuParent, target, { env });
await click(debugManager.el.querySelector("button.dropdown-toggle"));
@@ -79,8 +79,7 @@ odoo.define('web_tour.tour_manager_tests', async function (require) {
function onShowEffect(params) {
assert.deepEqual(params, {
fadeout: "medium",
message: "<strong><b>Good job!</b> You went through all steps of this tour.</strong>",
messageIsHtml: true,
message: owl.markup("<strong><b>Good job!</b> You went through all steps of this tour.</strong>"),
type: "rainbow_man"
});
}
@@ -7,10 +7,11 @@ var view_registry = require('web.view_registry');
var createView = testUtils.createView;
var BaseSettingsView = view_registry.get('base_settings');
const { legacyExtraNextTick } = require("@web/../tests/helpers/utils");
const { getFixture, legacyExtraNextTick } = require("@web/../tests/helpers/utils");
const { createWebClient, doAction } = require("@web/../tests/webclient/helpers");
let serverData;
let target;
QUnit.module('base_settings_tests', {
beforeEach: function () {
this.data = {
@@ -26,6 +27,7 @@ QUnit.module('base_settings_tests', {
}
};
serverData = { models: this.data };
target = getFixture();
}
}, function () {
@@ -137,11 +139,11 @@ QUnit.module('base_settings_tests', {
const webClient = await createWebClient({ serverData, mockRPC });
await doAction(webClient, 1);
await testUtils.dom.click($(webClient.el).find('button[name="4"]'));
await testUtils.dom.click($(target).find('button[name="4"]'));
await legacyExtraNextTick();
await testUtils.dom.click($(".o_control_panel .breadcrumb-item a"));
await legacyExtraNextTick();
assert.hasClass($(webClient.el).find(".o_form_view"), "o_form_editable");
assert.hasClass($(target).find(".o_form_view"), "o_form_editable");
assert.verifySteps([
"load_views", // initial setting action
"onchange", // this is a setting view => create new record
@@ -222,44 +224,44 @@ QUnit.module('base_settings_tests', {
await doAction(webClient, 1);
assert.containsNone(
webClient,
target,
".o_field_boolean input:checked",
"checkbox should not be checked"
);
await testUtils.dom.click($(webClient.el).find("input[type='checkbox']"));
await testUtils.dom.click($(target).find("input[type='checkbox']"));
assert.containsOnce(
webClient,
target,
".o_field_boolean input:checked",
"checkbox should be checked"
);
await testUtils.dom.click($(webClient.el).find('button[name="4"]'));
await testUtils.dom.click($(target).find('button[name="4"]'));
await legacyExtraNextTick();
assert.containsOnce(document.body, ".modal", "should open a warning dialog");
await testUtils.dom.click($(".modal button:contains(Discard)"));
await legacyExtraNextTick();
assert.containsOnce(webClient, ".o_list_view", "should be open list view");
assert.containsOnce(target, ".o_list_view", "should be open list view");
await testUtils.dom.click($(".o_control_panel .breadcrumb-item a"));
await legacyExtraNextTick();
assert.containsNone(
webClient,
target,
".o_field_boolean input:checked",
"checkbox should not be checked"
);
await testUtils.dom.click($(webClient.el).find("input[type='checkbox']"));
await testUtils.dom.click($(webClient.el).find('button[name="4"]'));
await testUtils.dom.click($(target).find("input[type='checkbox']"));
await testUtils.dom.click($(target).find('button[name="4"]'));
await legacyExtraNextTick();
assert.containsOnce(document.body, ".modal", "should open a warning dialog");
await testUtils.dom.click($(".modal button:contains(Stay Here)"));
await legacyExtraNextTick();
assert.containsOnce(webClient, ".o_form_view", "should be remain on form view");
assert.containsOnce(target, ".o_form_view", "should be remain on form view");
await testUtils.dom.click($(webClient.el).find("button[name='execute']"));
await testUtils.dom.click($(target).find("button[name='execute']"));
await legacyExtraNextTick();
assert.containsNone(
document.body,
@@ -267,8 +269,8 @@ QUnit.module('base_settings_tests', {
"should not open a warning dialog"
);
await testUtils.dom.click($(webClient.el).find("input[type='checkbox']"));
await testUtils.dom.click($(webClient.el).find("button[name='cancel']"));
await testUtils.dom.click($(target).find("input[type='checkbox']"));
await testUtils.dom.click($(target).find("button[name='cancel']"));
await legacyExtraNextTick();
assert.containsNone(
document.body,
@@ -449,19 +451,19 @@ QUnit.module('base_settings_tests', {
const webClient = await createWebClient({ serverData, mockRPC });
await doAction(webClient, 1);
assert.strictEqual($(webClient.el).find(".breadcrumb").text(), "First action");
assert.strictEqual($(target).find(".breadcrumb").text(), "First action");
await doAction(webClient, 2);
assert.strictEqual(
$(webClient.el).find(".breadcrumb").text(),
$(target).find(".breadcrumb").text(),
"First actionNew"
);
loadViewsDef = testUtils.makeTestPromise();
await testUtils.dom.click($(webClient.el).find('button[name="3"]'));
await testUtils.dom.click($(target).find('button[name="3"]'));
await legacyExtraNextTick();
assert.strictEqual(
$(webClient.el).find(".breadcrumb").text(),
$(target).find(".breadcrumb").text(),
"First actionNew"
);
@@ -469,7 +471,7 @@ QUnit.module('base_settings_tests', {
await testUtils.nextTick();
await legacyExtraNextTick();
assert.strictEqual(
$(webClient.el).find(".breadcrumb").text(),
$(target).find(".breadcrumb").text(),
"First actionNewOther action"
);
}
@@ -581,19 +583,19 @@ QUnit.module('base_settings_tests', {
await doAction(webClient, 1);
assert.containsNone(
webClient,
target,
".o_field_boolean input:checked",
"checkbox should not be checked"
);
await testUtils.dom.click($(webClient.el).find('input[type="checkbox"]'));
await testUtils.dom.click($(target).find('input[type="checkbox"]'));
assert.containsOnce(
webClient,
target,
".o_field_boolean input:checked",
"checkbox should be checked"
);
await testUtils.dom.click($(webClient.el).find('button[name="4"]'));
await testUtils.dom.click($(target).find('button[name="4"]'));
assert.containsOnce(document.body, ".modal", "should open a warning dialog");
await testUtils.dom.click($(".modal-footer .btn-primary"));