[IMP] web: improve debug Menu extensibility

This commit renames the DebugManager to "DebugMenu", changes the
structure of its registry and the signature of its hook (useDebugMenu).

In the previous DebugManager:
- the "debug" registry was flat;
- new items were declared and created with the "useDebugManager" hook;
- these items were arbitrary functions returning a set of items.

The problem with that system was its extensibility, meaning that no
external module could hook on these arbitrary functions passed to the
hook.

In the new DebugMenu:
- the "debug" registry has multiple levels:
  > root: "global" items, found in most debug menus
  > subcategories: each subcategory has a name corresponding to the
    context it's instantiated in (e.g.: the "view" subcategory will only
    contain views-related debug menu items)
- all items are pre-declared in their corresponding category, and either
return a debug menu item or null;
- the hook activates/deactivates categories, and passes a context which
will be used by the item factories (passed as argument).
This commit is contained in:
Julien Mougenot
2021-06-18 21:31:29 +02:00
committed by Aaron Bohy
parent 4377241d8c
commit a688f13cd4
15 changed files with 368 additions and 292 deletions
+50 -42
View File
@@ -7,54 +7,60 @@ import { registry } from "../registry";
const debugRegistry = registry.category("debug");
const { Component } = owl;
let debugElementsId = 1;
export class DebugManager extends Component {
export class DebugMenu extends Component {
setup() {
this.debugFactories = {};
const globalContext = { env: this.env };
this.activeCategories = new Set();
this.contexts = new Map([["global", globalContext]]);
this.debugService = useService("debug");
this.hasAccessRights = false;
// Defined as arrow to be passed as prop
// @ts-ignore
this.beforeOpenDropdown = async () => {
if (!this.accessRights) {
this.accessRights = await this.debugService.getAccessRights();
if (!this.hasAccessRights) {
this.hasAccessRights = true;
const accessRights = await this.debugService.getAccessRights();
Object.assign(globalContext, { accessRights });
}
};
this.env.bus.on("DEBUG-MANAGER:NEW-ITEMS", this, (payload) => {
const { inDialog, elementsId, elementsFactory } = payload;
if (this.isInDialog === inDialog) {
this.debugFactories[elementsId] = elementsFactory;
this.env.bus.on("DEBUG-MANAGER:ADD-CONTEXT", this, (payload) => {
const { category, context, inDialog, itemId } = payload;
if (this.env.inDialog === inDialog) {
this.contexts.set(itemId, context);
this.activeCategories.add(category);
}
});
this.env.bus.on("DEBUG-MANAGER:REMOVE-ITEMS", this, (payload) => {
const { inDialog, elementsId } = payload;
if (this.isInDialog === inDialog) {
delete this.debugFactories[elementsId];
this.env.bus.on("DEBUG-MANAGER:REMOVE-CONTEXT", this, (payload) => {
const { category, inDialog, itemId } = payload;
if (this.env.inDialog === inDialog) {
this.contexts.delete(itemId);
this.activeCategories.delete(category);
}
});
if (!this.isInDialog) {
this.debugFactories.global = () =>
debugRegistry.getAll().map((elFactory) => elFactory(this.env));
}
}
get isInDialog() {
return this.env.inDialog;
}
getElements() {
if (Object.keys(this.debugFactories).length > 0) {
const sortedElements = Object.values(this.debugFactories)
.map((factory) => factory(this.accessRights))
.reduce((acc, elements) => acc.concat(elements))
.sort((x, y) => {
const xSeq = x.sequence ? x.sequence : 1000;
const ySeq = y.sequence ? y.sequence : 1000;
return xSeq - ySeq;
});
return sortedElements;
} else {
return [];
const factories = [];
const context = Object.assign({}, ...this.contexts.values());
// If not in dialog => gets root (generic) factories
if (!this.env.inDialog) {
factories.push(...debugRegistry.getAll());
}
// Retrieves the active categories factories
for (const category of this.activeCategories) {
factories.push(...debugRegistry.category(category).getAll());
}
// Builds, filters and sorts all items
return factories
.map((factory) => factory(context))
.filter(Boolean)
.sort((x, y) => {
const xSeq = x.sequence ? x.sequence : 1000;
const ySeq = y.sequence ? y.sequence : 1000;
return xSeq - ySeq;
});
}
onDropdownItemSelected(ev) {
@@ -68,20 +74,22 @@ export class DebugManager extends Component {
}
}
DebugManager.debugElementsId = 1;
DebugManager.template = "web.DebugManager";
DebugMenu.template = "web.DebugMenu";
export function useDebugManager(elementsFactory) {
const elementsId = DebugManager.debugElementsId++;
export function useDebugMenu(category, context = {}) {
const component = Component.current;
const env = component.env;
const payload = {
elementsId,
elementsFactory,
category,
context,
inDialog: env.inDialog,
itemId: debugElementsId++,
};
useEffect(() => {
env.bus.trigger("DEBUG-MANAGER:NEW-ITEMS", payload);
return () => env.bus.trigger("DEBUG-MANAGER:REMOVE-ITEMS", payload);
}, () => []);
useEffect(
() => {
env.bus.trigger("DEBUG-MANAGER:ADD-CONTEXT", payload);
return () => env.bus.trigger("DEBUG-MANAGER:REMOVE-CONTEXT", payload);
},
() => []
);
}
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="web.DebugManager" owl="1">
<t t-name="web.DebugMenu" owl="1">
<Dropdown class="o_debug_manager"
beforeOpen="beforeOpenDropdown"
t-on-dropdown-item-selected.stop="onDropdownItemSelected"
@@ -4,10 +4,8 @@ import { browser } from "../browser/browser";
import { routeToUrl } from "../browser/router_service";
import { registry } from "../registry";
const debugRegistry = registry.category("debug");
// Backend Debug Manager Items
export function runJSTestsItem(env) {
function runJSTestsItem({ env }) {
const runTestsURL = browser.location.origin + "/web/tests?mod=*";
return {
type: "item",
@@ -20,7 +18,7 @@ export function runJSTestsItem(env) {
};
}
export function runJSTestsMobileItem(env) {
function runJSTestsMobileItem({ env }) {
const runTestsMobileURL = browser.location.origin + "/web/tests/mobile?mod=*";
return {
type: "item",
@@ -33,7 +31,7 @@ export function runJSTestsMobileItem(env) {
};
}
export function openViewItem(env) {
function openViewItem({ env }) {
return {
type: "item",
description: env._t("Open View"),
@@ -48,14 +46,14 @@ export function openViewItem(env) {
}
// Global Debug Manager Items
export function globalSeparator(env) {
function globalSeparator() {
return {
type: "separator",
sequence: 400,
};
}
export function activateAssetsDebugging(env) {
function activateAssetsDebugging({ env }) {
return {
type: "item",
description: env._t("Activate Assets Debugging"),
@@ -66,7 +64,7 @@ export function activateAssetsDebugging(env) {
};
}
export function activateTestsAssetsDebugging(env) {
function activateTestsAssetsDebugging({ env }) {
return {
type: "item",
description: env._t("Activate Tests Assets Debugging"),
@@ -77,7 +75,7 @@ export function activateTestsAssetsDebugging(env) {
};
}
export function regenerateAssets(env) {
export function regenerateAssets({ env }) {
return {
type: "item",
description: env._t("Regenerate Assets Bundles"),
@@ -97,7 +95,7 @@ export function regenerateAssets(env) {
};
}
export function becomeSuperuser(env) {
function becomeSuperuser({ env }) {
const becomeSuperuserULR = browser.location.origin + "/web/become";
return {
type: "item",
@@ -112,7 +110,7 @@ export function becomeSuperuser(env) {
};
}
export function leaveDebugMode(env) {
function leaveDebugMode({ env }) {
return {
type: "item",
description: env._t("Leave the Developer Tools"),
@@ -125,21 +123,16 @@ export function leaveDebugMode(env) {
};
}
const backendDebugManagerItems = [runJSTestsItem, runJSTestsMobileItem, openViewItem];
for (let item of backendDebugManagerItems) {
debugRegistry.add(item.name, item);
}
const globalDebugManagerItems = [
globalSeparator,
activateAssetsDebugging,
regenerateAssets,
becomeSuperuser,
leaveDebugMode,
activateTestsAssetsDebugging,
];
for (let item of globalDebugManagerItems) {
debugRegistry.add(item.name, item);
}
registry
.category("debug")
// Backend
.add("runJSTestsItem", runJSTestsItem)
.add("runJSTestsMobileItem", runJSTestsMobileItem)
.add("openViewItem", openViewItem)
// Global
.add("globalSeparator", globalSeparator)
.add("activateAssetsDebugging", activateAssetsDebugging)
.add("regenerateAssets", regenerateAssets)
.add("becomeSuperuser", becomeSuperuser)
.add("leaveDebugMode", leaveDebugMode)
.add("activateTestsAssetsDebugging", activateTestsAssetsDebugging);
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="web.DebugManager.SetDefaultFooter" owl="1">
<t t-name="web.DebugMenu.SetDefaultFooter" owl="1">
<button class="btn btn-secondary" t-on-click="trigger('dialog-closed')">Close</button>
<button class="btn btn-secondary" t-on-click="saveDefault">Save default</button>
</t>
<t t-name="web.DebugManager.setDefaultBody" owl="1">
<t t-name="web.DebugMenu.setDefaultBody" owl="1">
<table style="width: 100%">
<tr>
<td>
@@ -62,7 +62,7 @@
</table>
</t>
<t t-name="web.DebugManager.getMetadataBody" owl="1">
<t t-name="web.DebugMenu.getMetadataBody" owl="1">
<table class="table table-sm table-striped">
<tr>
<th>ID:</th>
@@ -1,7 +1,7 @@
/** @odoo-module **/
import { registry } from "../registry";
import { DebugManager } from "./debug_menu";
import { DebugMenu } from "./debug_menu";
export function editModelDebug(env, title, model, id) {
return env.services.action.doAction({
@@ -21,9 +21,7 @@ export const debugService = {
start(env, { orm }) {
let accessRightsProm;
if (env.debug !== "") {
registry
.category("systray")
.add("web.debug_mode_menu", DebugManager, { sequence: 100 });
registry.category("systray").add("web.debug_mode_menu", DebugMenu, { sequence: 100 });
}
return {
+15 -22
View File
@@ -1,18 +1,16 @@
/** @odoo-module **/
import core from "web.core";
import Context from "web.Context";
import { useService } from "../core/service_hook";
import { useEffect } from "../core/effect_hook";
import { useBus } from "../core/bus_hook";
import { ViewNotFoundError } from "../webclient/actions/action_service";
import { useDebugManager } from "../core/debug/debug_menu";
import { objectToQuery } from "../core/browser/router_service";
import core from "web.core";
import { ComponentAdapter } from "web.OwlCompatibility";
import { mapDoActionOptionAPI } from "./utils";
import { setupDebugAction, setupDebugViewForm, setupDebugView } from "./debug_manager";
import { cleanDomFromBootstrap } from "./utils";
import { objectToQuery } from "../core/browser/router_service";
import { useBus } from "../core/bus_hook";
import { useDebugMenu } from "../core/debug/debug_menu";
import { Dialog } from "../core/dialog/dialog";
import { useEffect } from "../core/effect_hook";
import { useService } from "../core/service_hook";
import { ViewNotFoundError } from "../webclient/actions/action_service";
import { cleanDomFromBootstrap, mapDoActionOptionAPI } from "./utils";
const { Component, tags } = owl;
@@ -154,9 +152,7 @@ class ActionAdapter extends ComponentAdapter {
export class ClientActionAdapter extends ActionAdapter {
setup() {
super.setup();
useDebugManager((accessRights) =>
setupDebugAction(accessRights, this.wowlEnv, this.props.widgetArgs[0])
);
useDebugMenu("action", { action: this.props.widgetArgs[0] });
this.env = Component.env;
}
@@ -231,16 +227,13 @@ export class ViewAdapter extends ActionAdapter {
this.shouldUpdateWidget = true;
this.magicReload = useMagicLegacyReload();
const envWowl = this.env;
useDebugManager((accessRights) =>
setupDebugAction(accessRights, envWowl, this.props.viewParams.action)
);
useDebugManager((accessRights) =>
setupDebugView(accessRights, envWowl, this, this.props.viewParams.action)
);
useDebugMenu("action", {
action: this.props.viewParams.action,
component: this,
});
useDebugMenu("view");
if (this.props.viewInfo.type === "form") {
useDebugManager((accessRights) =>
setupDebugViewForm(envWowl, this, this.props.viewParams.action)
);
useDebugMenu("form");
}
if (!envWowl.inDialog) {
useBus(envWowl.bus, "ACTION_MANAGER:UPDATE", (info) => {
+143 -79
View File
@@ -1,34 +1,59 @@
/** @odoo-module **/
import { editModelDebug } from "../core/debug/debug_service";
import { formatDateTime, parseDateTime } from "../core/l10n/dates";
import { formatMany2one } from "../fields/format";
import { Dialog } from "../core/dialog/dialog";
import { formatDateTime, parseDateTime } from "../core/l10n/dates";
import { _lt } from "../core/l10n/translation";
import { registry } from "../core/registry";
import { formatMany2one } from "../fields/format";
import { json_node_to_xml } from "../views/view_utils";
const { hooks, tags } = owl;
const { useState } = hooks;
export function setupDebugAction(accessRights, env, action) {
const actionSeparator = {
// Action items
function actionSeparator() {
return {
type: "separator",
sequence: 100,
};
}
let description = env._t("Edit Action");
const editAction = {
function accessSeparator({ accessRights, action }) {
const { canSeeModelAccess, canSeeRecordRules } = accessRights;
if (!action.res_model || (!canSeeModelAccess && !canSeeRecordRules)) {
return null;
}
return {
type: "separator",
sequence: 200,
};
}
function editAction({ action, env }) {
if (!action.id) {
return null;
}
const description = env._t("Edit Action");
return {
type: "item",
description: description,
description,
callback: () => {
editModelDebug(env, description, action.type, action.id);
},
sequence: 110,
};
}
description = env._t("View Fields");
const viewFields = {
function viewFields({ action, env }) {
if (!action.res_model) {
return null;
}
const description = env._t("View Fields");
return {
type: "item",
description: description,
description,
callback: async () => {
const modelId = (
await env.services.orm.search("ir.model", [["model", "=", action.res_model]], {
@@ -51,11 +76,16 @@ export function setupDebugAction(accessRights, env, action) {
},
sequence: 120,
};
}
description = env._t("Manage Filters");
const manageFilters = {
function manageFilters({ action, env }) {
if (!action.res_model) {
return null;
}
const description = env._t("Manage Filters");
return {
type: "item",
description: description,
description,
callback: () => {
// manage_filters
env.services.action.doAction({
@@ -74,8 +104,13 @@ export function setupDebugAction(accessRights, env, action) {
},
sequence: 130,
};
}
const technicalTranslation = {
function technicalTranslation({ action, env }) {
if (!action.res_model) {
return null;
}
return {
type: "item",
description: env._t("Technical Translation"),
callback: async () => {
@@ -88,16 +123,16 @@ export function setupDebugAction(accessRights, env, action) {
},
sequence: 140,
};
}
const accessSeparator = {
type: "separator",
sequence: 200,
};
description = env._t("View Access Rights");
const viewAccessRights = {
function viewAccessRights({ accessRights, action, env }) {
if (!action.res_model || !accessRights.canSeeModelAccess) {
return null;
}
const description = env._t("View Access Rights");
return {
type: "item",
description: description,
description,
callback: async () => {
const modelId = (
await env.services.orm.search("ir.model", [["model", "=", action.res_model]], {
@@ -120,9 +155,14 @@ export function setupDebugAction(accessRights, env, action) {
},
sequence: 210,
};
}
description = env._t("Model Record Rules");
const viewRecordRules = {
function viewRecordRules({ accessRights, action, env }) {
if (!action.res_model || !accessRights.canSeeRecordRules) {
return null;
}
const description = env._t("Model Record Rules");
return {
type: "item",
description: env._t("View Record Rules"),
callback: async () => {
@@ -147,26 +187,6 @@ export function setupDebugAction(accessRights, env, action) {
},
sequence: 220,
};
const result = [actionSeparator];
if (action.id) {
result.push(editAction);
}
if (action.res_model) {
result.push(viewFields);
result.push(manageFilters);
result.push(technicalTranslation);
if (accessRights.canSeeModelAccess || accessRights.canSeeRecordRules) {
result.push(accessSeparator);
if (accessRights.canSeeModelAccess) {
result.push(viewAccessRights);
}
if (accessRights.canSeeRecordRules) {
result.push(viewRecordRules);
}
}
}
return result;
}
class FieldViewGetDialog extends Dialog {}
@@ -207,7 +227,7 @@ class GetMetadataDialog extends Dialog {
this.state.write_date = formatDateTime(parseDateTime(metadata.write_date));
}
}
GetMetadataDialog.bodyTemplate = "web.DebugManager.getMetadataBody";
GetMetadataDialog.bodyTemplate = "web.DebugMenu.getMetadataBody";
GetMetadataDialog.title = _lt("View Metadata");
class SetDefaultDialog extends Dialog {
@@ -351,17 +371,19 @@ class SetDefaultDialog extends Dialog {
this.trigger("dialog-closed");
}
}
SetDefaultDialog.bodyTemplate = "web.DebugManager.setDefaultBody";
SetDefaultDialog.footerTemplate = "web.DebugManager.SetDefaultFooter";
SetDefaultDialog.bodyTemplate = "web.DebugMenu.setDefaultBody";
SetDefaultDialog.footerTemplate = "web.DebugMenu.SetDefaultFooter";
SetDefaultDialog.title = _lt("Set Default");
export function setupDebugView(accessRights, env, component, action) {
const viewId = component.props.viewInfo.view_id;
const viewSeparator = {
function viewSeparator() {
return {
type: "separator",
sequence: 300,
};
const fieldsViewGet = {
}
function fieldsViewGet({ component, env }) {
return {
type: "item",
description: env._t("Fields View Get"),
callback: () => {
@@ -372,57 +394,72 @@ export function setupDebugView(accessRights, env, component, action) {
},
sequence: 340,
};
}
export function editView({ accessRights, action, component, env }) {
if (!accessRights.canEditView) {
return null;
}
const { viewId, viewType } = component.widget;
const displayName = action.views
.find((v) => v.type === component.widget.viewType)
.find((v) => v.type === viewType)
.name.toString();
let description = env._t("Edit View: ") + displayName;
const editView = {
const description = env._t("Edit View: ") + displayName;
return {
type: "item",
description: description,
description,
callback: () => {
editModelDebug(env, description, "ir.ui.view", viewId);
},
sequence: 350,
};
description = env._t("Edit ControlPanelView");
const editControlPanelView = {
}
function editControlPanelView({ accessRights, component, env }) {
if (!accessRights.canEditView) {
return null;
}
const description = env._t("Edit ControlPanelView");
return {
type: "item",
description: description,
description,
callback: () => {
editModelDebug(
env,
description,
"ir.ui.view",
component.props.viewParams.action.controlPanelFieldsView.view_id
component.props.viewInfo.view_id
);
},
sequence: 360,
};
const result = [viewSeparator, fieldsViewGet];
if (accessRights.canEditView) {
result.push(editView);
result.push(editControlPanelView);
}
return result;
}
export function setupDebugViewForm(env, component, action) {
const setDefaults = {
// Form view itemss
function setDefaults({ action, component, env }) {
return {
type: "item",
description: env._t("Set Defaults"),
callback: () => {
env.services.dialog.open(SetDefaultDialog, {
res_model: action.res_model,
component: component,
component,
});
},
sequence: 310,
};
const viewMetadata = {
}
function viewMetadata({ action, component, env }) {
const selectedIds = component.widget.getSelectedIds();
if (selectedIds.length !== 1) {
return null;
}
return {
type: "item",
description: env._t("View Metadata"),
callback: () => {
const selectedIds = component.widget.getSelectedIds();
env.services.dialog.open(GetMetadataDialog, {
res_model: action.res_model,
selectedIds,
@@ -430,12 +467,19 @@ export function setupDebugViewForm(env, component, action) {
},
sequence: 320,
};
}
function manageAttachments({ action, component, env }) {
const selectedIds = component.widget.getSelectedIds();
const description = env._t("Manage Attachments");
const manageAttachments = {
if (selectedIds.length !== 1) {
return null;
}
return {
type: "item",
description: description,
description,
callback: () => {
const selectedId = component.widget.getSelectedIds()[0];
const selectedId = selectedIds[0];
env.services.action.doAction({
res_model: "ir.attachment",
name: description,
@@ -456,10 +500,30 @@ export function setupDebugViewForm(env, component, action) {
},
sequence: 330,
};
const result = [setDefaults];
if (component.widget.getSelectedIds().length === 1) {
result.push(viewMetadata);
result.push(manageAttachments);
}
return result;
}
const debugRegistry = registry.category("debug");
debugRegistry
.category("action")
.add("actionSeparator", actionSeparator)
.add("editAction", editAction)
.add("viewFields", viewFields)
.add("manageFilters", manageFilters)
.add("technicalTranslation", technicalTranslation)
.add("accessSeparator", accessSeparator)
.add("viewAccessRights", viewAccessRights)
.add("viewRecordRules", viewRecordRules)
debugRegistry
.category("view")
.add("viewSeparator", viewSeparator)
.add("fieldsViewGet", fieldsViewGet)
.add("editView", editView)
.add("editControlPanelView", editControlPanelView)
debugRegistry
.category("form")
.add("setDefaults", setDefaults)
.add("viewMetadata", viewMetadata)
.add("manageAttachments", manageAttachments);
@@ -1,7 +1,7 @@
/** @odoo-module **/
import { Dialog } from "../../core/dialog/dialog";
import { DebugManager } from "../../core/debug/debug_menu";
import { DebugMenu } from "../../core/debug/debug_menu";
import { useEffect } from "../../core/effect_hook";
const { hooks } = owl;
@@ -25,7 +25,7 @@ class ActionDialog extends Dialog {
this.title = "title" in this.props ? this.props.title : this.constructor.title;
}
}
ActionDialog.components = { ...Dialog.components, DebugManager };
ActionDialog.components = { ...Dialog.components, DebugMenu };
ActionDialog.template = "web.ActionDialog";
ActionDialog.bodyTemplate = "web.ActionDialogBody";
ActionDialog.props = {
@@ -52,18 +52,21 @@ class LegacyAdaptedActionDialog extends ActionDialog {
const ControllerComponent = this.props && this.props.ActionComponent;
const Controller = ControllerComponent && ControllerComponent.Component;
this.isLegacy = Controller && Controller.isLegacy;
useEffect(() => {
if (this.isLegacy) {
// Retrieve the widget climbing the wrappers
const componentController = this.actionRef.comp;
const controller = componentController.componentRef.comp;
const viewAdapter = controller.controllerRef.comp;
const widget = viewAdapter.widget;
// Render legacy footer buttons
const footer = this.modalRef.el.querySelector("footer");
widget.renderButtons($(footer));
}
}, () => []); // TODO: should this depend on actionRef.comp?
useEffect(
() => {
if (this.isLegacy) {
// Retrieve the widget climbing the wrappers
const componentController = this.actionRef.comp;
const controller = componentController.componentRef.comp;
const viewAdapter = controller.controllerRef.comp;
const widget = viewAdapter.widget;
// Render legacy footer buttons
const footer = this.modalRef.el.querySelector("footer");
widget.renderButtons($(footer));
}
},
() => []
); // TODO: should this depend on actionRef.comp?
}
}
LegacyAdaptedActionDialog.footerTemplate = "web.LegacyAdaptedActionDialogFooter";
@@ -3,7 +3,7 @@
<t t-name="web.ActionDialog" t-inherit="web.Dialog" t-inherit-mode="primary" owl="1">
<xpath expr="//header/h4[contains(concat(' ',normalize-space(@class),' '),' modal-title ')]" position="before">
<DebugManager t-if="env.debug" />
<DebugMenu t-if="env.debug" />
</xpath>
<xpath expr="//main[@class='modal-body']" position="attributes">
<attribute name="t-att-class">
@@ -9,7 +9,7 @@ export default async function startClickEverywhere(xmlId, appsMenusOnly) {
window.clickEverywhere(xmlId, appsMenusOnly);
}
function runClickTestItem(env) {
function runClickTestItem({ env }) {
return {
type: "item",
description: env._t("Run Click Everywhere Test"),
@@ -1,7 +1,7 @@
/** @odoo-module **/
import { browser } from "@web/core/browser/browser";
import { DebugManager, useDebugManager } from "@web/core/debug/debug_menu";
import { DebugMenu, useDebugMenu } from "@web/core/debug/debug_menu";
import { regenerateAssets } from "@web/core/debug/debug_menu_items";
import { registry } from "@web/core/registry";
import { debugService } from "@web/core/debug/debug_service";
@@ -21,7 +21,7 @@ const debugRegistry = registry.category("debug");
let target;
let testConfig;
QUnit.module("DebugManager", (hooks) => {
QUnit.module("DebugMenu", (hooks) => {
hooks.beforeEach(async () => {
target = getFixture();
registry
@@ -38,61 +38,62 @@ QUnit.module("DebugManager", (hooks) => {
testConfig = { mockRPC };
});
QUnit.test("can be rendered", async (assert) => {
debugRegistry.add("item_1", () => {
return {
type: "item",
description: "Item 1",
callback: () => {
assert.step("callback item_1");
},
sequence: 10,
};
});
debugRegistry.add("item_2", () => {
return {
type: "item",
description: "Item 2",
callback: () => {
assert.step("callback item_2");
},
sequence: 5,
};
});
debugRegistry.add("item_3", () => {
return {
type: "item",
description: "Item 3",
callback: () => {
assert.step("callback item_3");
},
};
});
debugRegistry.add("separator", () => {
return {
type: "separator",
sequence: 20,
};
});
debugRegistry.add("separator_2", () => {
return {
type: "separator",
sequence: 7,
hide: true,
};
});
debugRegistry.add("item_4", () => {
return {
type: "item",
description: "Item 4",
callback: () => {
assert.step("callback item_4");
},
hide: true,
sequence: 10,
};
});
debugRegistry
.add("item_1", () => {
return {
type: "item",
description: "Item 1",
callback: () => {
assert.step("callback item_1");
},
sequence: 10,
};
})
.add("item_2", () => {
return {
type: "item",
description: "Item 2",
callback: () => {
assert.step("callback item_2");
},
sequence: 5,
};
})
.add("item_3", () => {
return {
type: "item",
description: "Item 3",
callback: () => {
assert.step("callback item_3");
},
};
})
.add("separator", () => {
return {
type: "separator",
sequence: 20,
};
})
.add("separator_2", () => {
return {
type: "separator",
sequence: 7,
hide: true,
};
})
.add("item_4", () => {
return {
type: "item",
description: "Item 4",
callback: () => {
assert.step("callback item_4");
},
hide: true,
sequence: 10,
};
});
const env = await makeTestEnv(testConfig);
const debugManager = await mount(DebugManager, { env, target });
const debugManager = await mount(DebugMenu, { env, target });
registerCleanup(() => debugManager.destroy());
let debugManagerEl = debugManager.el;
await click(debugManager.el.querySelector("button.o_dropdown_toggler"));
@@ -117,7 +118,7 @@ QUnit.module("DebugManager", (hooks) => {
assert.verifySteps(["callback item_2", "callback item_1", "callback item_3"]);
});
QUnit.test("Don't display the DebugManager if debug mode is disabled", async (assert) => {
QUnit.test("Don't display the DebugMenu if debug mode is disabled", async (assert) => {
const dialogContainer = document.createElement("div");
dialogContainer.classList.add("o_dialog_container");
target.append(dialogContainer);
@@ -132,8 +133,9 @@ QUnit.module("DebugManager", (hooks) => {
});
QUnit.test(
"Display the DebugManager correctly in a ActionDialog if debug mode is enabled",
"Display the DebugMenu correctly in a ActionDialog if debug mode is enabled",
async (assert) => {
assert.expect(8);
const dialogContainer = document.createElement("div");
dialogContainer.classList.add("o_dialog_container");
target.append(dialogContainer);
@@ -147,26 +149,33 @@ QUnit.module("DebugManager", (hooks) => {
sequence: 0,
};
});
const item1 = {
type: "item",
description: "Item 1",
callback: () => {
assert.step("callback item_1");
},
sequence: 10,
};
const item2 = {
type: "item",
description: "Item 2",
callback: () => {
assert.step("callback item_2");
},
sequence: 20,
};
debugRegistry
.category("custom")
.add("item1", () => {
return {
type: "item",
description: "Item 1",
callback: () => {
assert.step("callback item_1");
},
sequence: 10,
};
})
.add("item2", ({ customKey }) => {
return {
type: "item",
description: "Item 2",
callback: () => {
assert.step("callback item_2");
assert.strictEqual(customKey, "abc");
},
sequence: 20,
};
});
class Parent extends Component {
setup() {
useSubEnv({ inDialog: true });
useDebugManager(() => [item1, item2]);
useDebugMenu("custom", { customKey: "abc" });
}
}
Parent.components = { ActionDialog };
@@ -224,7 +233,7 @@ QUnit.module("DebugManager", (hooks) => {
registry.category("services").add("localization", makeFakeLocalizationService());
debugRegistry.add("regenerateAssets", regenerateAssets);
const env = await makeTestEnv(testConfig);
const debugManager = await mount(DebugManager, { env, target });
const debugManager = await mount(DebugMenu, { env, target });
registerCleanup(() => debugManager.destroy());
await click(debugManager.el.querySelector("button.o_dropdown_toggler"));
assert.containsOnce(debugManager.el, "ul.o_dropdown_menu li.o_dropdown_item");
@@ -13,6 +13,7 @@ export function clearRegistryWithCleanup(registry) {
content: {},
elements: null,
entries: null,
subRegistries: {},
// Preserve OnUpdate handlers
subscriptions: { UPDATE: [...registry.subscriptions.UPDATE] },
};
@@ -24,6 +25,7 @@ function cloneRegistryWithCleanup(registry) {
content: { ...registry.content },
elements: null,
entries: null,
subRegistries: {},
// Preserve OnUpdate handlers
subscriptions: { UPDATE: [...registry.subscriptions.UPDATE] },
};
@@ -10,7 +10,7 @@ import { clearRegistryWithCleanup } from "../../helpers/mock_env";
import { createWebClient, doAction, getActionManagerTestConfig } from "./helpers";
import { ClientActionAdapter } from "@web/legacy/action_adapters";
import { useDebugManager } from "@web/core/debug/debug_menu";
import { useDebugMenu } from "@web/core/debug/debug_menu";
import { debugService } from "@web/core/debug/debug_service";
import ControlPanel from "web.ControlPanel";
@@ -101,19 +101,23 @@ QUnit.module("ActionManager", (hooks) => {
patchWithCleanup(ClientActionAdapter.prototype, {
setup() {
useDebugManager((accessRights) => {
assert.step("debugItems executed");
assert.ok(this.widget);
return [];
});
useDebugMenu("custom", { widget: this });
this._super();
},
});
registry
.category("debug")
.category("custom")
.add("item1", ({ widget }) => {
assert.step("debugItems executed");
assert.ok(widget);
return {};
});
registry.category("services").add("debug", debugService);
testConfig.debug = true;
const mockRPC = (route, args) => {
const mockRPC = (route) => {
if (route.includes("check_access_rights")) {
return true;
}
@@ -3,12 +3,13 @@
import { debugService } from "@web/core/debug/debug_service";
import { DialogContainer } from "@web/core/dialog/dialog_service";
import { registry } from "@web/core/registry";
import { editView } from "@web/legacy/debug_manager";
import { clearUncommittedChanges } from "@web/webclient/actions/action_service";
import testUtils from "web.test_utils";
import ListController from "web.ListController";
import AbstractView from "web.AbstractView";
import legacyViewRegistry from "web.view_registry";
import FormView from "web.FormView";
import ListController from "web.ListController";
import testUtils from "web.test_utils";
import legacyViewRegistry from "web.view_registry";
import { clearRegistryWithCleanup } from "../../helpers/mock_env";
import { makeFakeUserService } from "../../helpers/mock_services";
import { click, legacyExtraNextTick, nextTick } from "../../helpers/utils";
@@ -2227,8 +2228,9 @@ QUnit.module("ActionManager", (hooks) => {
assert.expect(2);
serviceRegistry.add("debug", debugService);
registry.category("debug").category("view").add("editView", editView);
testConfig.debug = "1";
const mockRPC = async (route, args) => {
const mockRPC = async (route) => {
if (route.includes("check_access_rights")) {
return true;
}
@@ -5,13 +5,13 @@ import { registry } from "@web/core/registry";
import ToursDialog from "@web_tour/debug/tour_dialog_component";
import utils from "web_tour.utils";
function debugDisableTourItem(env) {
function disableTours({ env }) {
if (!env.services.user.isSystem) {
return {};
return null;
}
const activeTours = env.services.tour.getActiveTours();
if (activeTours.length === 0) {
return {};
return null;
}
return {
type: "item",
@@ -23,13 +23,13 @@ function debugDisableTourItem(env) {
}
browser.location.reload();
},
sequence: 33,
sequence: 50,
};
}
function debugStartTourItem(env) {
function startTour({ env }) {
if (!env.services.user.isSystem) {
return {};
return null;
}
return {
type: "item",
@@ -37,11 +37,11 @@ function debugStartTourItem(env) {
callback: async () => {
env.services.dialog.open(ToursDialog);
},
sequence: 32,
sequence: 60,
};
}
registry
.category("debug")
.add("web_tour.start_tour", debugStartTourItem)
.add("web_tour.disable_tour", debugDisableTourItem);
.add("web_tour.startTour", startTour)
.add("web_tour.disableTours", disableTours);