From 5da13e1d5a9ff61024d1ea957f6030150ea8712e Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Tue, 4 Oct 2022 13:17:26 +0000 Subject: [PATCH] [REF] web: remove legacy fields/widgets compatibility layer This commit removes the code that wrapped legacy fields and widgets (coming from the legacy registries) into owl components and added them to the wowl registries. This temporary compatibility layer, which was incomplete and only allowed us to merge the new views without converting all fields/widgets in once without breaking everything, is no longer necessary as we now have converted all of them into owl components. X-original-commit: 555959c87e25faa71396f61479e8bb41b66ac164 Part-of: odoo/odoo#102606 --- addons/web/__manifest__.py | 3 - addons/web/static/src/legacy/legacy_fields.js | 377 ------------------ .../web/static/src/legacy/legacy_fields.scss | 7 - .../static/src/legacy/legacy_view_widgets.js | 103 ----- addons/web/static/src/legacy/scss/views.scss | 6 - .../static/src/views/fields/field_tooltip.js | 9 +- .../views/fields/many2many_field_tests.js | 39 -- .../views/fields/one2many_field_tests.js | 253 ------------ .../tests/views/form/form_view_tests.js | 192 --------- .../static/tests/views/kanban_view_tests.js | 255 +----------- 10 files changed, 2 insertions(+), 1242 deletions(-) delete mode 100644 addons/web/static/src/legacy/legacy_fields.js delete mode 100644 addons/web/static/src/legacy/legacy_fields.scss delete mode 100644 addons/web/static/src/legacy/legacy_view_widgets.js 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($('