diff --git a/addons/web/__manifest__.py b/addons/web/__manifest__.py index 7cdcf22df13..1f766f18fbb 100644 --- a/addons/web/__manifest__.py +++ b/addons/web/__manifest__.py @@ -187,7 +187,6 @@ This module provides the core of the Odoo Web Client. 'web/static/src/core/utils/transitions.scss', 'web/static/src/core/**/*', - 'web/static/src/legacy/legacy_fields.scss', 'web/static/src/search/**/*', 'web/static/src/webclient/icons.scss', # variables required in list_controller.scss 'web/static/src/views/**/*', @@ -244,8 +243,6 @@ This module provides the core of the Odoo Web Client. 'web/static/src/legacy/legacy_dialog.js', 'web/static/src/legacy/legacy_load_views.js', 'web/static/src/legacy/legacy_views.js', - 'web/static/src/legacy/legacy_fields.js', - 'web/static/src/legacy/legacy_view_widgets.js', 'web/static/src/legacy/legacy_promise_error_handler.js', 'web/static/src/legacy/legacy_rpc_error_handler.js', 'web/static/src/legacy/root_widget.js', diff --git a/addons/web/static/src/legacy/legacy_fields.js b/addons/web/static/src/legacy/legacy_fields.js deleted file mode 100644 index c3633f766b6..00000000000 --- a/addons/web/static/src/legacy/legacy_fields.js +++ /dev/null @@ -1,377 +0,0 @@ -/** @odoo-module **/ - -import { registry } from "@web/core/registry"; -import { sprintf } from "@web/core/utils/strings"; -import { Domain } from "web.Domain"; -import legacyFieldRegistry from "web.field_registry"; -import { ComponentAdapter } from "web.OwlCompatibility"; -import { useWowlService } from "@web/legacy/utils"; -import { RPCError } from "@web/core/network/rpc_service"; -import { useTranslationDialog } from "@web/views/fields/translation_button"; - -const { Component, useEffect, xml } = owl; -const fieldRegistry = registry.category("fields"); - -const legacyFieldTemplate = xml` - `; - -// ----------------------------------------------------------------------------- -// FieldAdapter -// ----------------------------------------------------------------------------- - -class FieldAdapter extends ComponentAdapter { - setup() { - super.setup(); - this.translationDialog = useTranslationDialog(); - this.wowlEnv = this.env; - this.env = Component.env; - this.orm = useWowlService("orm"); - useEffect(() => { - if (!this.widgetEl || !this.widgetEl.parentElement) { - return; - } - - const fieldId = this.props.id; - if (!this.widgetEl.querySelector(`#${fieldId}`)) { - const $el = this.widget.getFocusableElement(); - if ($el && $el[0]) { - $el[0].setAttribute("id", fieldId); - } - } - - // if classNames are given to the field, we only want - // to add those classes to the legacy field without - // the parent element affecting the style of the field - const fieldClassnames = - this.props.fieldParams.options && - this.props.fieldParams.options.attrs && - this.props.fieldParams.options.attrs.class; - if (fieldClassnames) { - for (const className of fieldClassnames.split(" ")) { - this.widgetEl.parentElement.classList.remove(className); - this.widgetEl.classList.add(className); - } - this.widgetEl.parentElement.style.display = "contents"; - } - this.widgetEl.parentElement.classList.remove("o_text_overflow"); - this.widgetEl.classList.add("o_legacy_field_widget"); - }); - this.lastFieldChangedEvent = null; - } - - /** - * @override - */ - get widgetArgs() { - const { name, record, options } = this.props.fieldParams; - return [name, record, options]; - } - - async updateWidget(nextProps) { - const { name, record, options } = nextProps.fieldParams; - if (this.widget.mode !== options.mode) { - // the mode changed, we need to instantiate a new FieldWidget - if (this.oldWidget) { - this.widget.destroy(); // we were already updating -> abort, and start over - } else { - this.oldWidget = this.widget; - } - this.widget = new this.props.Component(this, name, record, options); - this.widgetProm = this.widget._widgetRenderAndInsert(() => {}); - return this.widgetProm; - } else { - // the mode is the same, simply reset the FieldWidget with the new record - await this.widgetProm; - return this.widget.reset(record, this.lastFieldChangedEvent, true); - } - } - - renderWidget() { - if (this.oldWidget) { - const parentEl = this.oldWidget.el.parentElement; - parentEl.replaceChild(this.widget.el, this.oldWidget.el); - this.widgetEl = this.widget.el; - if (this.widget.on_attach_callback) { - this.widget.on_attach_callback(); - } - this.oldWidget.destroy(); - this.oldWidget = null; - } - } - - async _trigger_up(ev) { - const evType = ev.name; - const payload = ev.data; - if (evType === "set_dirty") { - // TODO (not yet handled in the Record datapoint) - } else if (evType === "field_changed") { - const { name, record } = this.props.fieldParams; - const proms = []; - for (const fieldName in payload.changes) { - let value = payload.changes[fieldName]; - const fieldType = record.fields[fieldName].type; - if (fieldType === "many2one" && value) { - value = [value.id, value.display_name]; - } else if (fieldType === "one2many" || fieldType === "many2many") { - // TODO map all operations - if (value.operation === "ADD_M2M") { - const valueIds = value.ids; // can be a lot of stuff - let newIds = []; - if (Array.isArray(valueIds)) { - if (typeof valueIds[0] === "number") { - newIds = valueIds; // not sure if it is a real case: a list of ids - } else if (valueIds.length && "id" in valueIds[0]) { - newIds = valueIds.map((r) => r.id); - } - } else if ("id" in valueIds && valueIds.id) { - newIds = [valueIds.id]; - } else if ("id" in valueIds) { - const fieldName = this.props.fieldParams.name; - let newId; - try { - [newId] = await this.orm.call( - this.props.record.fields[fieldName].relation, - "name_create", - [valueIds.display_name], - { - context: this.props.record.getFieldContext(fieldName), - } - ); - } catch (e) { - if (!(e instanceof RPCError)) { - throw e; - } - if (payload.onFailure) { - // wrap error for guardedCatch compatibility in legacy code - payload.onFailure({ - message: e, - event: $.Event(), - legacy: true, - }); - } - return; - } - newIds = [newId]; - } - value = { - resIds: [...record.data[name].res_ids, ...newIds], - operation: "REPLACE_WITH", - }; - } - } else if (fieldType === "date" || fieldType === "datetime") { - // from moment to luxon - value = value ? luxon.DateTime.fromISO(value.toISOString()) : false; - } - proms.push(this.props.update(fieldName, value)); - } - this.lastFieldChangedEvent = ev; - await Promise.all(proms); - if (payload.onSuccess) { - payload.onSuccess(); - } - // TODO: handle onFailure? - } else if (evType === "reload") { - const record = this.props.record; - if (payload.db_id === record.id) { - await record.model.reloadRecords(record); - } else { - await record.model.root.load(); - record.model.notify(); - } - if (payload.onSuccess) { - payload.onSuccess(); - } - } else if (evType === "history_back") { - return this.wowlEnv.config.historyBack(); - } else if (evType === "translate") { - const { fieldParams, record, update } = this.props; - return this.translationDialog({ - fieldName: fieldParams.name, - record, - updateField: update, - }); - } - super._trigger_up(...arguments); - } -} - -// ----------------------------------------------------------------------------- -// Helpers to map wowl datapoints to basic model datapoints -// ----------------------------------------------------------------------------- - -let nextId = 1; -function createMany2OneDatapoint([resId, displayName]) { - return { - id: `m2o_${nextId++}`, - data: { id: resId, display_name: displayName }, - fields: { - display_name: { type: "char" }, - id: { type: "integer" }, - }, - res_id: resId, - ref: resId, - }; -} - -function mapDatapoint(datapoint) { - return { - id: datapoint.id, - model: datapoint.resModel, - fields: datapoint.fields, - fieldsInfo: { - default: datapoint.activeFields, // TODO: how to handle viewTypes? - }, - getContext: () => [], // TODO: not yet handled in wowl - getDomain: () => [], // TODO: not yet handled in wowl - getFieldNames: () => datapoint.fieldNames, - }; -} - -function mapStaticListDatapoint(staticList) { - return { - ...mapDatapoint(staticList), - res_ids: staticList.currentIds, - data: staticList.records.map(mapRecordDatapoint), - groupedBy: [], - orderedBy: staticList.orderBy, - count: staticList.count, - }; -} - -export function mapRecordDatapoint(record) { - const data = Object.assign({}, record.data); - for (const fieldName of record.fieldNames) { - const fieldType = record.fields[fieldName].type; - switch (fieldType) { - case "one2many": - case "many2many": - data[fieldName] = mapStaticListDatapoint(data[fieldName]); - break; - case "many2one": - data[fieldName] = data[fieldName] && createMany2OneDatapoint(data[fieldName]); - break; - case "date": - case "datetime": - // from luxon to moment - data[fieldName] = data[fieldName] ? moment(data[fieldName].toISO()) : false; - } - } - data.id = record.resId; - const basicModelRecord = { - ...mapDatapoint(record), - res_id: record.resId, - data, - }; - Object.defineProperty(basicModelRecord, "evalContext", { - get: () => record.evalContext, - }); - Object.defineProperty(basicModelRecord, "isDirty", { - get: () => record.isDirty, - }); - Object.defineProperty(basicModelRecord, "isNew", { - get: () => record.isDirty, - }); - basicModelRecord.evalModifiers = (modifiers) => { - let evalContext = null; - const evaluated = {}; - for (const k of ["invisible", "column_invisible", "readonly", "required"]) { - const mod = modifiers[k]; - if (mod === undefined || mod === false || mod === true) { - if (k in modifiers) { - evaluated[k] = !!mod; - } - continue; - } - try { - evalContext = evalContext || basicModelRecord.evalContext; - evaluated[k] = new Domain(mod, evalContext).compute(evalContext); - } catch (e) { - throw new Error(sprintf('for modifier "%s": %s', k, e.message)); - } - } - return evaluated; - }; - return basicModelRecord; -} - -// ----------------------------------------------------------------------------- -// Register legacy field widgets to the wowl field registry (wrapped in a Component) -// ----------------------------------------------------------------------------- - -function registerField(name, LegacyFieldWidget) { - class LegacyField extends Component { - setup() { - this.FieldWidget = LegacyFieldWidget; - } - - get fieldParams() { - const { name, record } = this.props; - let legacyRecord; - const fieldInfo = record.activeFields[name]; - let views = {}; - const modifiers = JSON.parse(fieldInfo.rawAttrs.modifiers || "{}"); - const hasReadonlyModifier = record.isReadonly(name); - if (record.model.__bm__) { - legacyRecord = record.model.__bm__.get(record.__bm_handle__); - const form = legacyRecord.fieldsInfo.form; - views = form && form[name].views; - } else { - legacyRecord = mapRecordDatapoint(record); - } - const options = { - attrs: { - ...fieldInfo.rawAttrs, - modifiers, - options: fieldInfo.options, - widget: fieldInfo.widget, - views, - mode: fieldInfo.viewMode, - }, - viewType: legacyRecord.viewType, - mode: this.props.readonly || hasReadonlyModifier ? "readonly" : record.mode, - hasReadonlyModifier, - }; - return { name, record: legacyRecord, options }; - } - - update(fieldName, value) { - if (fieldName === this.props.name) { - const record = this.props.record; - const fieldType = record.fields[fieldName].type; - if (["one2many", "many2many"].includes(fieldType) && !record.model.__bm__) { - const staticList = this.props.value; - switch (value.operation) { - case "REPLACE_WITH": { - return staticList.replaceWith(value.resIds); - } - case "FORGET": { - return staticList.delete(value.ids); - } - } - } - return this.props.update(value); - } else { - return this.props.record.update({ [fieldName]: value }); - } - } - } - LegacyField.template = legacyFieldTemplate; - LegacyField.components = { FieldAdapter }; - LegacyField.legacySpecialData = LegacyFieldWidget.prototype.specialData; - LegacyField.fieldsToFetch = LegacyFieldWidget.prototype.fieldsToFetch || {}; - LegacyField.fieldDependencies = LegacyFieldWidget.prototype.fieldDependencies || {}; - LegacyField.useSubView = LegacyFieldWidget.prototype.useSubview; - LegacyField.noLabel = LegacyFieldWidget.prototype.noLabel || false; - if (!fieldRegistry.contains(name)) { - if (odoo.debug) { - console.log(`Fields: using legacy ${name} FieldWidget`); - } - fieldRegistry.add(name, LegacyField); - } -} - -// register fields already in the legacy registry, and listens to future registrations -for (const [name, FieldWidget] of Object.entries(legacyFieldRegistry.entries())) { - registerField(name, FieldWidget); -} -legacyFieldRegistry.onAdd(registerField); diff --git a/addons/web/static/src/legacy/legacy_fields.scss b/addons/web/static/src/legacy/legacy_fields.scss deleted file mode 100644 index ccf7e5f80f9..00000000000 --- a/addons/web/static/src/legacy/legacy_fields.scss +++ /dev/null @@ -1,7 +0,0 @@ -.o_debug { - .o_form_view, .o_list_view, .o_kanban_view { - .o_legacy_field_widget > * { - background-color: rgba(255, 0, 0, 0.1); - } - } -} diff --git a/addons/web/static/src/legacy/legacy_view_widgets.js b/addons/web/static/src/legacy/legacy_view_widgets.js deleted file mode 100644 index c4f12d7338c..00000000000 --- a/addons/web/static/src/legacy/legacy_view_widgets.js +++ /dev/null @@ -1,103 +0,0 @@ -/** @odoo-module **/ - -import { registry } from "@web/core/registry"; -import { ComponentAdapter } from "web.OwlCompatibility"; -import legacyWidgetRegistry from "web.widget_registry"; -import { mapRecordDatapoint } from "./legacy_fields"; - -const { Component, useEffect, xml } = owl; -const viewWidgetRegistry = registry.category("view_widgets"); - -const legacyWidgetTemplate = xml` - `; - -// ---------------------------------------------------------------------------- -// ViewWidgetAdapter -// ---------------------------------------------------------------------------- - -class ViewWidgetAdapter extends ComponentAdapter { - setup() { - super.setup(); - this.wowlEnv = this.env; - this.env = Component.env; - useEffect(() => { - this.widgetEl.parentElement.classList.remove("o_text_overflow"); - this.widgetEl.classList.add("o_legacy_widget"); - }); - } - - /** - * @override - */ - get widgetArgs() { - return this.getParams(this.props); - } - - getParams(props) { - const { record, node, options, readonly } = props.viewWidgetParams; - return [record, node, { mode: readonly ? "readonly" : "edit", ...options }]; - } - - updateWidget(nextProps) { - if (this.oldWidget) { - this.widget.destroy(); // we were already updating -> abort, and start over - } else { - this.oldWidget = this.widget; - } - this.widget = new this.props.Component(this, ...this.getParams(nextProps)); - return this.widget._widgetRenderAndInsert(() => {}); - } - - renderWidget() { - if (this.oldWidget) { - const parentEl = this.oldWidget.el.parentElement; - parentEl.replaceChild(this.widget.el, this.oldWidget.el); - this.widgetEl = this.widget.el; - if (this.widget.on_attach_callback) { - this.widget.on_attach_callback(); - } - this.oldWidget.destroy(); - this.oldWidget = null; - } - } -} - -// ---------------------------------------------------------------------------- -// Register legacy widgets to the wowl widget registry (wrapped in a Component) -// ---------------------------------------------------------------------------- - -function registerWidget(name, LegacyWidgetWidget) { - class LegacyViewWidget extends Component { - setup() { - this.ViewWidget = LegacyWidgetWidget; - } - get viewWidgetParams() { - const { record } = this.props; - let legacyRecord; - if (record.model.__bm__) { - legacyRecord = record.model.__bm__.get(record.__bm_handle__); - } else { - legacyRecord = mapRecordDatapoint(record); - } - const options = { - viewType: legacyRecord.viewType, - mode: record.mode, - }; - return { ...this.props, record: legacyRecord, options }; - } - } - LegacyViewWidget.template = legacyWidgetTemplate; - LegacyViewWidget.components = { ViewWidgetAdapter }; - if (!viewWidgetRegistry.contains(name)) { - if (odoo.debug) { - console.log(`View widgets: using legacy ${name} Widget`); - } - viewWidgetRegistry.add(name, LegacyViewWidget); - } -} - -// register widgets already in the legacy registry, and listens to future registrations -for (const [name, Widget] of Object.entries(legacyWidgetRegistry.entries())) { - registerWidget(name, Widget); -} -legacyWidgetRegistry.onAdd(registerWidget); diff --git a/addons/web/static/src/legacy/scss/views.scss b/addons/web/static/src/legacy/scss/views.scss index 943f6ed0e70..67a2070ee35 100644 --- a/addons/web/static/src/legacy/scss/views.scss +++ b/addons/web/static/src/legacy/scss/views.scss @@ -3,12 +3,6 @@ * all renderers. For field default rules, see the fields.scss file. */ -.o_debug .o_legacy_form_view, -.o_debug .o_legacy_kanban_view, -.o_debug .o_legacy_list_view { - border-top: 3px solid red; -} - // Invisible modifier (can be inside the view, the button area, ...) .o_invisible_modifier { display: none!important; diff --git a/addons/web/static/src/views/fields/field_tooltip.js b/addons/web/static/src/views/fields/field_tooltip.js index 9f62ebe4bba..439b4433ac4 100644 --- a/addons/web/static/src/views/fields/field_tooltip.js +++ b/addons/web/static/src/views/fields/field_tooltip.js @@ -1,16 +1,9 @@ /** @odoo-module **/ -import fieldRegistry from "web.field_registry"; - export function getTooltipInfo(params) { let widgetDescription = undefined; if (params.fieldInfo.widget) { - if (params.fieldInfo.FieldComponent.name === "LegacyField") { - const widget = fieldRegistry.get(params.fieldInfo.widget); - widgetDescription = widget.prototype.description; - } else { - widgetDescription = params.fieldInfo.FieldComponent.displayName; - } + widgetDescription = params.fieldInfo.FieldComponent.displayName; } const info = { diff --git a/addons/web/static/tests/views/fields/many2many_field_tests.js b/addons/web/static/tests/views/fields/many2many_field_tests.js index bf30b81c06c..172d32bbe54 100644 --- a/addons/web/static/tests/views/fields/many2many_field_tests.js +++ b/addons/web/static/tests/views/fields/many2many_field_tests.js @@ -20,9 +20,6 @@ import { session } from "@web/session"; import { companyService } from "@web/webclient/company_service"; import { registry } from "@web/core/registry"; -import legacyFieldRegistry from "web.field_registry"; -import { FieldMany2ManyTags } from "web.relational_fields"; - let target; let serverData; @@ -2123,40 +2120,4 @@ QUnit.module("Fields", (hooks) => { ); } ); - - QUnit.test("many2many legacy field in list add a record", async (assert) => { - const myM2M = FieldMany2ManyTags.extend({}); - legacyFieldRegistry.add("many2many_tags_legacy", myM2M); - - await makeView({ - type: "list", - resModel: "partner", - serverData, - arch: ` - - - `, - mockRPC(route, args) { - if (args.method === "write") { - assert.step(`write: ${JSON.stringify(args.args[1])}`); - } - }, - }); - - assert.containsNone(target, ".o_badge_text"); - await click(target.querySelectorAll(".o_data_cell")[0]); - await click(target, ".o_legacy_field_widget input"); - await click(document.querySelectorAll(".ui-autocomplete .dropdown-item")[0]); - - assert.strictEqual(target.querySelector(".o_badge_text").textContent, "gold"); - await click(target); - assert.verifySteps([`write: {"timmy":[[6,false,[12]]]}`]); - - await click(target.querySelectorAll(".o_data_cell")[0]); - await click(target.querySelector(".badge .o_delete")); - await click(target); - assert.containsNone(target, ".o_badge_text"); - assert.verifySteps([`write: {"timmy":[[6,false,[]]]}`]); - delete legacyFieldRegistry.map["many2many_tags_legacy"]; - }); }); diff --git a/addons/web/static/tests/views/fields/one2many_field_tests.js b/addons/web/static/tests/views/fields/one2many_field_tests.js index 28e3a403fa8..3371cc8e7b8 100644 --- a/addons/web/static/tests/views/fields/one2many_field_tests.js +++ b/addons/web/static/tests/views/fields/one2many_field_tests.js @@ -1,6 +1,5 @@ /** @odoo-module **/ -import AbstractField from "web.AbstractField"; import { addRow, click, @@ -26,9 +25,7 @@ import { import BasicModel from "web.BasicModel"; import { browser } from "@web/core/browser/browser"; import { createWebClient, doAction } from "@web/../tests/webclient/helpers"; -import { FieldOne2Many } from "web.relational_fields"; import { getNextTabableElement } from "@web/core/utils/ui"; -import fieldRegistry from "web.field_registry"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; import { registerCleanup } from "@web/../tests/helpers/cleanup"; import { registry } from "@web/core/registry"; @@ -5504,138 +5501,6 @@ QUnit.module("Fields", (hooks) => { await click(target, ".oe_kanban_action_button"); }); - QUnit.test( - "one2many kanban with edit type action and widget with specialData", - async function (assert) { - assert.expect(3); - - patchWithCleanup(BasicModel.prototype, { - _fetchSpecialDataForMyWidget() { - assert.step("_fetchSpecialDataForMyWidget"); - return Promise.resolve(); - }, - }); - const MyWidget = AbstractField.extend({ - specialData: "_fetchSpecialDataForMyWidget", - className: "my_widget", - }); - fieldRegistry.add("specialWidget", MyWidget); - - await makeView({ - type: "form", - resModel: "partner", - serverData, - // turtle_int is displayed without widget in the kanban, and with - // widget required specialData in the form - arch: ` -
- - - - - -
- - - - Edit -
-
-
-
- - - - - -
- `, - resId: 1, - }); - - await click(target.querySelector(".oe_kanban_action")); - assert.containsOnce(target, ".modal .my_widget"); - assert.verifySteps(["_fetchSpecialDataForMyWidget"]); - } - ); - - QUnit.test("one2many list with onchange and widget using SpecialData", async function (assert) { - patchWithCleanup(BasicModel.prototype, { - _fetchSpecialDataForMyWidget() { - assert.step("_fetchSpecialDataForMyWidget"); - return Promise.resolve(); - }, - }); - const MyWidget = AbstractField.extend({ - specialData: "_fetchSpecialDataForMyWidget", - className: "my_widget", - }); - fieldRegistry.add("specialWidget", MyWidget); - - serverData.models.partner.onchanges = { - turtles: function (obj) { - var virtualID = obj.turtles[1][1]; - obj.turtles = [ - [5], // delete all - [ - 0, - virtualID, - { - display_name: "coucou", - product_id: [37, "xphone"], - turtle_bar: false, - turtle_foo: "has changed", - turtle_int: 42, - turtle_qux: 9.8, - partner_ids: [], - turtle_ref: "product,37", - }, - ], - ]; - }, - }; - await makeView({ - type: "form", - resModel: "partner", - serverData, - // turtle_int is displayed without widget in the list, and with - // widget required specialData in the form - arch: ` -
- - - - - - - - - - - -
- `, - resId: 1, - }); - - await addRow(target, ".o_field_one2many"); - assert.containsOnce(target, ".modal"); - await click(target.querySelector(".modal-footer button")); - - assert.deepEqual( - [...target.querySelectorAll(".o_field_one2many tbody tr .o_data_cell")].map( - (el) => el.innerText - ), - ["coucou", "has changed", "42"], - "the onchange should create one new record and remove the existing" - ); - - await click(target.querySelector(".o_field_one2many .o_list_renderer tbody tr td")); - - await clickSave(target.querySelector(".modal")); - assert.verifySteps(["_fetchSpecialDataForMyWidget"], "should only fetch special data once"); - }); - QUnit.test("one2many without inline tree arch", async function (assert) { serverData.models.partner.records[0].turtles = [2, 3]; serverData.views = { @@ -10590,46 +10455,6 @@ QUnit.module("Fields", (hooks) => { await clickSave(target); }); - QUnit.test( - "one2many: internal state is updated after another field changes", - async function (assert) { - // The FieldOne2Many is configured such that it is reset at any field change. - // The MatrixProductConfigurator feature relies on that, and requires that its - // internal state is correctly updated. This white-box test artificially checks that. - let o2m; - // keep using the legacy version considering the above comment - const MyFieldOne2Many = FieldOne2Many.extend({ - init() { - this._super(...arguments); - o2m = this; - }, - }); - fieldRegistry.add("myone2many", MyFieldOne2Many); - registerCleanup(() => { - delete fieldRegistry.map.myone2many; - }); - - await makeView({ - type: "form", - resModel: "partner", - serverData, - arch: ` -
- - - - - `, - }); - - assert.strictEqual(o2m.recordData.display_name, false); - - await editInput(target, ".o_field_widget[name=display_name] input", "val"); - - assert.strictEqual(o2m.recordData.display_name, "val"); - } - ); - QUnit.test("nested one2many, onchange, no command value", async function (assert) { // This test ensures that we always send all values to onchange rpcs for nested // one2manys, even if some field hasn't changed. In this particular test case, @@ -10695,84 +10520,6 @@ QUnit.module("Fields", (hooks) => { await click(target.querySelector(".o_data_row .o_field_boolean input")); }); - QUnit.test("update a one2many from a custom field widget", async function (assert) { - // In this test, we define a custom field widget to render/update a one2many - // field. For the update part, we ensure that updating primitive fields of a sub - // record works. There is no guarantee that updating a relational field on the sub - // record would work. Deleting a sub record works as well. However, creating sub - // records isn't supported. There are obviously a lot of limitations, but the code - // hasn't been designed to support all this. This test simply encodes what can be - // done, and this comment explains what can't (and won't be implemented in stable - // versions). - serverData.models.partner.records[0].p = [1, 2]; - const MyRelationalField = AbstractField.extend({ - events: { - "click .update": "_onUpdate", - "click .delete": "_onDelete", - }, - async _render() { - const records = await this._rpc({ - method: "read", - model: "partner", - args: [this.value.res_ids], - }); - this.$el.text(records.map((r) => `${r.display_name}/${r.int_field}`).join(", ")); - this.$el.append($('