[IMP] mail, web: adapt chatter container in new form
task-2871070 Part-of: odoo/odoo#78221
This commit is contained in:
committed by
Géry Debongnie
parent
bc0a0cead6
commit
28200c5ad5
@@ -198,6 +198,7 @@ For more specific needs, you may also assign custom-defined actions
|
||||
'mail/static/src/components/*/*.scss',
|
||||
('include', 'mail.assets_messaging'),
|
||||
'mail/static/src/services/*.js',
|
||||
'mail/static/src/views/**/*.js',
|
||||
'mail/static/src/webclient/commands/*.js',
|
||||
'mail/static/src/widgets/*/*.js',
|
||||
'mail/static/src/widgets/*/*.scss',
|
||||
|
||||
@@ -36,7 +36,7 @@ export class ChatterContainer extends Component {
|
||||
this.chatterId = getChatterNextTemporaryId();
|
||||
this._insertFromProps(this.props);
|
||||
onWillUpdateProps(nextProps => this._willUpdateProps(nextProps));
|
||||
onWillDestroy(this._onWillDestroy);
|
||||
onWillDestroy(() => this._onWillDestroy());
|
||||
}
|
||||
|
||||
_willUpdateProps(nextProps) {
|
||||
@@ -61,10 +61,10 @@ export class ChatterContainer extends Component {
|
||||
*/
|
||||
async _insertFromProps(props) {
|
||||
const messaging = await this.env.services.messaging.get();
|
||||
if (this.__owl__.status === 5 /* destroyed */) {
|
||||
if (owl.status(this) === "destroyed") {
|
||||
return;
|
||||
}
|
||||
const values = { id: this.chatterId, ...props };
|
||||
const values = { id: this.chatterId, ...props, className: undefined };
|
||||
if (values.threadId === undefined) {
|
||||
values.threadId = clear();
|
||||
}
|
||||
@@ -93,6 +93,10 @@ export class ChatterContainer extends Component {
|
||||
Object.assign(ChatterContainer, {
|
||||
components: { Chatter: getMessagingComponent('Chatter') },
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
hasActivities: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
@@ -142,6 +146,10 @@ Object.assign(ChatterContainer, {
|
||||
optional: true,
|
||||
},
|
||||
threadModel: String,
|
||||
webRecord: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
}
|
||||
},
|
||||
template: 'mail.ChatterContainer',
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.ChatterContainer" owl="1">
|
||||
<div class="o_ChatterContainer flex-grow-1 d-flex w-100" t-att-class="{ 'o-isInFormSheetBg mx-auto': props.isInFormSheetBg }" t-attf-class="{{ className }}" data-command-category="mail" t-ref="root">
|
||||
<div class="o_ChatterContainer flex-grow-1 d-flex w-100" t-att-class="{ 'o-isInFormSheetBg mx-auto': props.isInFormSheetBg }" t-attf-class="{{ props.className }}" data-command-category="mail" t-ref="root">
|
||||
<t t-if="chatter">
|
||||
<Chatter record="chatter"/>
|
||||
</t>
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ export class WebClientViewAttachmentViewContainer extends Component {
|
||||
*/
|
||||
async _insertFromProps(props) {
|
||||
const messaging = await this.env.services.messaging.get();
|
||||
if (this.__owl__.status === 5 /* destroyed */) {
|
||||
if (owl.status(this) === "destroyed") {
|
||||
this._deleteRecord();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -160,6 +160,10 @@ registerModel({
|
||||
* @param {string[]} [fieldNames]
|
||||
*/
|
||||
reloadParentView({ fieldNames } = {}) {
|
||||
if (this.webRecord) {
|
||||
this.webRecord.model.load({ resId: this.threadId });
|
||||
return;
|
||||
}
|
||||
if (this.component) {
|
||||
const options = { keepChanges: true };
|
||||
if (fieldNames) {
|
||||
@@ -301,8 +305,10 @@ registerModel({
|
||||
*/
|
||||
_onThreadIsLoadingAttachmentsChanged() {
|
||||
if (!this.thread || !this.thread.isLoadingAttachments) {
|
||||
this._stopAttachmentsLoading();
|
||||
this.update({ isShowingAttachmentsLoading: false });
|
||||
this.update({
|
||||
attachmentsLoaderTimer: clear(),
|
||||
isShowingAttachmentsLoading: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.isPreparingAttachmentsLoading || this.isShowingAttachmentsLoading) {
|
||||
@@ -316,12 +322,6 @@ registerModel({
|
||||
_prepareAttachmentsLoading() {
|
||||
this.update({ attachmentsLoaderTimer: insertAndReplace() });
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_stopAttachmentsLoading() {
|
||||
this.update({ attachmentsLoaderTimer: clear() });
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
activityBoxView: one('ActivityBoxView', {
|
||||
@@ -490,6 +490,7 @@ registerModel({
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
webRecord: attr(),
|
||||
},
|
||||
onChanges: [
|
||||
new OnChange({
|
||||
|
||||
@@ -951,6 +951,10 @@ registerModel({
|
||||
*/
|
||||
async setMainAttachment(attachment) {
|
||||
this.update({ mainAttachment: replace(attachment) });
|
||||
if (this.model === 'account.move.line') {
|
||||
// account.move.line is not actually a thread in python
|
||||
return;
|
||||
}
|
||||
await this.messaging.rpc({
|
||||
model: 'ir.attachment',
|
||||
method: 'register_as_main_attachment',
|
||||
|
||||
@@ -298,6 +298,7 @@ registerModel({
|
||||
for (const threadView of this.threadViews) {
|
||||
threadView.addComponentHint('messages-loaded', { fetchedMessages });
|
||||
}
|
||||
this.messaging.messagingBus.trigger('o-thread-loaded-messages', { thread: this.thread });
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
|
||||
@@ -5,6 +5,10 @@ import { attr, one } from '@mail/model/model_field';
|
||||
|
||||
import { format } from 'web.field_utils';
|
||||
import { session } from '@web/session';
|
||||
import { registry } from '@web/core/registry';
|
||||
import { deserializeDateTime } from '@web/core/l10n/dates';
|
||||
|
||||
const formatters = registry.category("formatters");
|
||||
|
||||
registerModel({
|
||||
name: 'TrackingValueItem',
|
||||
@@ -41,10 +45,7 @@ registerModel({
|
||||
}
|
||||
return format.date(this.value);
|
||||
case 'datetime':
|
||||
if (this.value) {
|
||||
return format.datetime(moment.utc(this.value));
|
||||
}
|
||||
return format.datetime(this.value);
|
||||
return formatters.get("datetime")(deserializeDateTime(this.value), { timezone: true });
|
||||
case 'float':
|
||||
return format.float(this.value);
|
||||
case 'integer':
|
||||
|
||||
@@ -39,7 +39,17 @@ registerModel({
|
||||
* @private
|
||||
*/
|
||||
_onChangeThreadAttachmentsInWebClientView() {
|
||||
if (!this.thread.mainAttachment && this.thread.attachmentsInWebClientView.length > 0) {
|
||||
if (this.thread.model === 'account.move.line') {
|
||||
// account.move.line is not actually a thread in python
|
||||
this.setMainAttachmentFromIndex(0);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this.thread.areAttachmentsLoaded &&
|
||||
!this.thread.isLoadingAttachments &&
|
||||
!this.thread.mainAttachment &&
|
||||
this.thread.attachmentsInWebClientView.length > 0
|
||||
) {
|
||||
this.setMainAttachmentFromIndex(0);
|
||||
}
|
||||
},
|
||||
@@ -57,7 +67,7 @@ registerModel({
|
||||
},
|
||||
onChanges: [
|
||||
new OnChange({
|
||||
dependencies: ['thread.attachmentsInWebClientView'],
|
||||
dependencies: ['thread.areAttachmentsLoaded', 'thread.attachmentsInWebClientView', 'thread.isLoadingAttachments'],
|
||||
methodName: '_onChangeThreadAttachmentsInWebClientView',
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { evaluateExpr } from "@web/core/py_js/py";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { SIZES } from "@web/core/ui/ui_service";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { append, createElement, setAttributes } from "@web/core/utils/xml";
|
||||
import { FormCompiler } from "@web/views/form/form_compiler";
|
||||
|
||||
|
||||
function compileChatter(node, params) {
|
||||
let hasActivities = false;
|
||||
let hasFollowers = false;
|
||||
let hasMessageList = false;
|
||||
let hasParentReloadOnAttachmentsChanged;
|
||||
let hasParentReloadOnFollowersUpdate = false;
|
||||
let hasParentReloadOnMessagePosted = false;
|
||||
let isAttachmentBoxVisibleInitially = false;
|
||||
for (const childNode of node.children) {
|
||||
const options = evaluateExpr(childNode.getAttribute("options") || "{}");
|
||||
switch (childNode.getAttribute('name')) {
|
||||
case 'activity_ids':
|
||||
hasActivities = true;
|
||||
break;
|
||||
case 'message_follower_ids':
|
||||
hasFollowers = true;
|
||||
hasParentReloadOnFollowersUpdate = Boolean(options['post_refresh']);
|
||||
isAttachmentBoxVisibleInitially = isAttachmentBoxVisibleInitially || Boolean(options['open_attachments']);
|
||||
break;
|
||||
case 'message_ids':
|
||||
hasMessageList = true;
|
||||
hasParentReloadOnAttachmentsChanged = options['post_refresh'] === 'always';
|
||||
hasParentReloadOnMessagePosted = Boolean(options['post_refresh']);
|
||||
isAttachmentBoxVisibleInitially = isAttachmentBoxVisibleInitially || Boolean(options['open_attachments']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const chatterContainerXml = createElement("ChatterContainer");
|
||||
setAttributes(chatterContainerXml, {
|
||||
"hasActivities": hasActivities,
|
||||
"hasFollowers": hasFollowers,
|
||||
"hasMessageList": hasMessageList,
|
||||
"hasParentReloadOnAttachmentsChanged": hasParentReloadOnAttachmentsChanged,
|
||||
"hasParentReloadOnFollowersUpdate": hasParentReloadOnFollowersUpdate,
|
||||
"hasParentReloadOnMessagePosted": hasParentReloadOnMessagePosted,
|
||||
"isAttachmentBoxVisibleInitially": isAttachmentBoxVisibleInitially,
|
||||
"threadId": "props.record.resId or undefined",
|
||||
"threadModel": "props.record.resModel",
|
||||
"webRecord": "props.record",
|
||||
});
|
||||
const chatterContainerHookXml = createElement("div");
|
||||
chatterContainerHookXml.classList.add("o_FormRenderer_chatterContainer");
|
||||
append(chatterContainerHookXml, chatterContainerXml);
|
||||
return chatterContainerHookXml;
|
||||
}
|
||||
|
||||
function compileAttachmentPreview(node, params) {
|
||||
const webClientViewAttachmentViewContainerHookXml = createElement("div");
|
||||
webClientViewAttachmentViewContainerHookXml.classList.add('o_attachment_preview');
|
||||
const webClientViewAttachmentViewContainerXml = createElement("WebClientViewAttachmentViewContainer");
|
||||
setAttributes(webClientViewAttachmentViewContainerXml, {
|
||||
"threadId": "props.record.resId or undefined",
|
||||
"threadModel": "props.record.resModel",
|
||||
});
|
||||
append(webClientViewAttachmentViewContainerHookXml, webClientViewAttachmentViewContainerXml);
|
||||
return webClientViewAttachmentViewContainerHookXml;
|
||||
}
|
||||
|
||||
registry.category("form_compilers").add("chatter_compiler", {
|
||||
selector: "div.oe_chatter",
|
||||
fn: compileChatter,
|
||||
});
|
||||
|
||||
registry.category("form_compilers").add("attachment_preview_compiler", {
|
||||
selector: "div.o_attachment_preview",
|
||||
fn: compileAttachmentPreview,
|
||||
});
|
||||
|
||||
patch(FormCompiler.prototype, 'mail', {
|
||||
compile() {
|
||||
// TODO no chatter if in dialog?
|
||||
const res = this._super(...arguments);
|
||||
const chatterContainerHookXml = res.querySelector('.o_FormRenderer_chatterContainer');
|
||||
if (!chatterContainerHookXml) {
|
||||
return res; // no chatter, keep the result as it is
|
||||
}
|
||||
const chatterContainerXml = chatterContainerHookXml.querySelector('ChatterContainer');
|
||||
if (chatterContainerHookXml.parentNode.classList.contains('o_form_sheet')) {
|
||||
setAttributes(chatterContainerXml, {
|
||||
"hasExternalBorder": 'true',
|
||||
"hasMessageListScrollAdjust": 'false',
|
||||
});
|
||||
return res; // if chatter is inside sheet, keep it there
|
||||
}
|
||||
const formSheetBgXml = res.querySelector('.o_form_sheet_bg');
|
||||
const parentXml = formSheetBgXml && formSheetBgXml.parentNode;
|
||||
if (!parentXml) {
|
||||
return res; // miss-config: a sheet-bg is required for the rest
|
||||
}
|
||||
const webClientViewAttachmentViewHookXml = res.querySelector('.o_attachment_preview');
|
||||
// TODO hasAttachmentViewer should also depend on the groups= and/or invisible modifier on o_attachment_preview (see invoice form)
|
||||
if (webClientViewAttachmentViewHookXml) {
|
||||
// in sheet bg (attachment viewer present)
|
||||
setAttributes(webClientViewAttachmentViewHookXml, {
|
||||
't-if': `hasAttachmentViewer() and uiService.size >= ${SIZES.XXL}`,
|
||||
});
|
||||
const sheetBgChatterContainerHookXml = chatterContainerHookXml.cloneNode(true);
|
||||
sheetBgChatterContainerHookXml.classList.add('o-isInFormSheetBg');
|
||||
setAttributes(sheetBgChatterContainerHookXml, {
|
||||
't-if': `hasAttachmentViewer() and uiService.size >= ${SIZES.XXL}`,
|
||||
});
|
||||
append(formSheetBgXml, sheetBgChatterContainerHookXml);
|
||||
const sheetBgChatterContainerXml = sheetBgChatterContainerHookXml.querySelector('ChatterContainer');
|
||||
setAttributes(sheetBgChatterContainerXml, {
|
||||
"isInFormSheetBg": "true",
|
||||
"hasExternalBorder": "true",
|
||||
"hasMessageListScrollAdjust": "false",
|
||||
});
|
||||
}
|
||||
// after sheet bg (standard position, either aside or below)
|
||||
if (webClientViewAttachmentViewHookXml) {
|
||||
setAttributes(chatterContainerHookXml, {
|
||||
't-if': `!(hasAttachmentViewer() and uiService.size >= ${SIZES.XXL})`,
|
||||
't-attf-class': `{{ uiService.size >= ${SIZES.XXL} and !(hasAttachmentViewer() and uiService.size >= ${SIZES.XXL}) ? "o-aside" : "" }}`,
|
||||
});
|
||||
setAttributes(chatterContainerXml, {
|
||||
"isInFormSheetBg": "hasAttachmentViewer()",
|
||||
"hasExternalBorder": `!(uiService.size >= ${SIZES.XXL} and !(hasAttachmentViewer() and uiService.size >= ${SIZES.XXL}))`,
|
||||
"hasMessageListScrollAdjust": `uiService.size >= ${SIZES.XXL} and !(hasAttachmentViewer() and uiService.size >= ${SIZES.XXL})`,
|
||||
});
|
||||
} else {
|
||||
setAttributes(chatterContainerXml, {
|
||||
"isInFormSheetBg": "false",
|
||||
"hasExternalBorder": `uiService.size < ${SIZES.XXL}`,
|
||||
"hasMessageListScrollAdjust": `uiService.size >= ${SIZES.XXL}`,
|
||||
});
|
||||
setAttributes(chatterContainerHookXml, {
|
||||
't-attf-class': `{{ uiService.size >= ${SIZES.XXL} ? "o-aside" : "" }}`,
|
||||
});
|
||||
}
|
||||
append(parentXml, chatterContainerHookXml);
|
||||
return res;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { useModels } from '@mail/component_hooks/use_models';
|
||||
import { ChatterContainer } from "@mail/components/chatter_container/chatter_container";
|
||||
import { WebClientViewAttachmentViewContainer } from '@mail/components/web_client_view_attachment_view_container/web_client_view_attachment_view_container';
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { FormRenderer } from "@web/views/form/form_renderer";
|
||||
|
||||
patch(FormRenderer.prototype, 'mail', {
|
||||
setup() {
|
||||
this._super();
|
||||
if (this.env.services.messaging) {
|
||||
useModels();
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Mail Methods
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @returns {Messaging|undefined}
|
||||
*/
|
||||
getMessaging() {
|
||||
return this.env.services.messaging && this.env.services.messaging.modelManager.messaging;
|
||||
},
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasAttachmentViewer() {
|
||||
if (!this.getMessaging() || !this.props.record.resId) {
|
||||
return false;
|
||||
}
|
||||
const thread = this.getMessaging().models['Thread'].insert({
|
||||
id: this.props.record.resId,
|
||||
model: this.props.record.resModel,
|
||||
});
|
||||
return (
|
||||
thread.attachmentsInWebClientView.length > 0
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Object.assign(FormRenderer.components, {
|
||||
ChatterContainer,
|
||||
WebClientViewAttachmentViewContainer,
|
||||
});
|
||||
@@ -15,10 +15,6 @@ import { doAction, getActionManagerServerData } from "@web/../tests/webclient/he
|
||||
|
||||
import core from 'web.core';
|
||||
|
||||
import legacyViewRegistry from "web.view_registry";
|
||||
import FormView from 'web.FormView';
|
||||
import ListView from 'web.ListView';
|
||||
|
||||
const { App, EventBus } = owl;
|
||||
const { afterNextRender } = App;
|
||||
const modelDefinitionsPromise = new Promise(resolve => {
|
||||
@@ -464,15 +460,13 @@ function getOpenFormView(afterEvent, openView) {
|
||||
},
|
||||
}));
|
||||
const waitMessages = func => afterNextRender(() => afterEvent({
|
||||
eventName: 'o-thread-view-hint-processed',
|
||||
eventName: 'o-thread-loaded-messages',
|
||||
func,
|
||||
message: "should wait until chatter loaded its messages",
|
||||
predicate: ({ hint, threadViewer }) => {
|
||||
predicate: ({ thread }) => {
|
||||
return (
|
||||
hint.type === 'messages-loaded' &&
|
||||
threadViewer &&
|
||||
threadViewer.thread.model === action.res_model &&
|
||||
threadViewer.thread.id === action.res_id
|
||||
thread.model === action.res_model &&
|
||||
thread.id === action.res_id
|
||||
);
|
||||
},
|
||||
}));
|
||||
@@ -530,12 +524,6 @@ function getOpenFormView(afterEvent, openView) {
|
||||
* @returns {Object}
|
||||
*/
|
||||
async function start(param0 = {}) {
|
||||
if (!param0.useWowlListForm) {
|
||||
registry.category("views").remove("list"); // remove new list from registry
|
||||
registry.category("views").remove("form"); // remove new form from registry
|
||||
legacyViewRegistry.add("list", ListView); // add legacy list -> will be wrapped and added to new registry
|
||||
legacyViewRegistry.add("form", FormView); // add legacy form -> will be wrapped and added to new registry
|
||||
}
|
||||
// patch _.debounce and _.throttle to be fast and synchronous.
|
||||
patchWithCleanup(_, {
|
||||
debounce: func => func,
|
||||
|
||||
@@ -4,8 +4,8 @@ import { start, startServer } from '@mail/../tests/helpers/test_utils';
|
||||
import { WEBCLIENT_LOAD_ROUTES } from '@mail/../tests/helpers/webclient_setup';
|
||||
|
||||
import testUtils from 'web.test_utils';
|
||||
import { patchWithCleanup } from '@web/../tests/helpers/utils';
|
||||
import { ViewAdapter } from "@web/legacy/action_adapters";
|
||||
import { clickEdit, patchWithCleanup, selectDropdownItem } from '@web/../tests/helpers/utils';
|
||||
import { ListController } from "@web/views/list/list_controller";
|
||||
|
||||
QUnit.module('mail', {}, function () {
|
||||
QUnit.module('Chatter');
|
||||
@@ -37,7 +37,7 @@ QUnit.test('list activity widget with no activity', async function (assert) {
|
||||
assert.containsOnce(document.body, '.o_mail_activity .o_activity_color_default');
|
||||
assert.strictEqual(document.querySelector('.o_activity_summary').innerText, '');
|
||||
|
||||
assert.verifySteps(['/web/dataset/search_read']);
|
||||
assert.verifySteps(['/web/dataset/call_kw/res.users/web_search_read']);
|
||||
});
|
||||
|
||||
QUnit.test('list activity widget with activities', async function (assert) {
|
||||
@@ -90,7 +90,7 @@ QUnit.test('list activity widget with activities', async function (assert) {
|
||||
assert.containsOnce(secondRow, '.o_mail_activity .o_activity_color_planned.fa-clock-o');
|
||||
assert.strictEqual(secondRow.querySelector('.o_activity_summary').innerText, 'Type 2');
|
||||
|
||||
assert.verifySteps(['/web/dataset/search_read']);
|
||||
assert.verifySteps(['/web/dataset/call_kw/res.users/web_search_read']);
|
||||
});
|
||||
|
||||
QUnit.test('list activity widget with exception', async function (assert) {
|
||||
@@ -130,7 +130,7 @@ QUnit.test('list activity widget with exception', async function (assert) {
|
||||
assert.containsOnce(document.body, '.o_activity_color_today.text-warning.fa-warning');
|
||||
assert.strictEqual(document.querySelector('.o_activity_summary').innerText, 'Warning');
|
||||
|
||||
assert.verifySteps(['/web/dataset/search_read']);
|
||||
assert.verifySteps(['/web/dataset/call_kw/res.users/web_search_read']);
|
||||
});
|
||||
|
||||
QUnit.test('list activity widget: open dropdown', async function (assert) {
|
||||
@@ -190,7 +190,7 @@ QUnit.test('list activity widget: open dropdown', async function (assert) {
|
||||
serverData: { views },
|
||||
});
|
||||
|
||||
patchWithCleanup(ViewAdapter.prototype, {
|
||||
patchWithCleanup(ListController.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
const selectRecord = this.props.selectRecord;
|
||||
@@ -223,8 +223,8 @@ QUnit.test('list activity widget: open dropdown', async function (assert) {
|
||||
assert.strictEqual(document.querySelector('.o_activity_summary').innerText, 'Meet FP');
|
||||
|
||||
assert.verifySteps([
|
||||
'/web/dataset/search_read',
|
||||
'select_record [2,{\"mode\":\"readonly\"}]',
|
||||
'web_search_read',
|
||||
'select_record [2,{"activeIds":[2]}]',
|
||||
'open dropdown',
|
||||
'activity_format',
|
||||
'action_feedback',
|
||||
@@ -283,9 +283,9 @@ QUnit.test('list activity exception widget with activity', async function (asser
|
||||
});
|
||||
|
||||
assert.containsN(document.body, '.o_data_row', 2, "should have two records");
|
||||
assert.doesNotHaveClass(document.querySelector('.o_data_row .o_activity_exception_cell div'), 'fa-warning',
|
||||
assert.doesNotHaveClass(document.querySelector('.o_data_row .o_activity_exception_cell div div'), 'fa-warning',
|
||||
"there is no any exception activity on record");
|
||||
assert.hasClass(document.querySelectorAll('.o_data_row .o_activity_exception_cell div')[1], 'fa-warning',
|
||||
assert.hasClass(document.querySelectorAll('.o_data_row .o_activity_exception_cell div div')[1], 'fa-warning',
|
||||
"there is an exception on a record");
|
||||
});
|
||||
|
||||
@@ -479,7 +479,7 @@ QUnit.test('many2many_tags_email widget can load more than 40 records', async fu
|
||||
const views = {
|
||||
'mail.message,false,form': '<form><field name="partner_ids" widget="many2many_tags"/></form>',
|
||||
};
|
||||
var { click, openView } = await start({
|
||||
var { openView } = await start({
|
||||
serverData: { views },
|
||||
});
|
||||
await openView({
|
||||
@@ -490,13 +490,12 @@ QUnit.test('many2many_tags_email widget can load more than 40 records', async fu
|
||||
|
||||
assert.strictEqual(document.querySelectorAll('.o_field_widget[name="partner_ids"] .badge').length, 100);
|
||||
|
||||
await click('.o_form_button_edit');
|
||||
await clickEdit(document.body);
|
||||
|
||||
assert.hasClass(document.querySelector('.o_legacy_form_view'), 'o_form_editable');
|
||||
assert.containsOnce(document.body, '.o_form_editable');
|
||||
|
||||
// add a record to the relation
|
||||
await testUtils.fields.many2one.clickOpenDropdown('partner_ids');
|
||||
await testUtils.fields.many2one.clickHighlightedItem('partner_ids');
|
||||
await selectDropdownItem(document.body, 'partner_ids', "Public user");
|
||||
|
||||
assert.strictEqual(document.querySelectorAll('.o_field_widget[name="partner_ids"] .badge').length, 101);
|
||||
});
|
||||
|
||||
@@ -867,7 +867,7 @@ QUnit.test('Attachments that have been unlinked from server should be visually u
|
||||
views: [[false, 'form']],
|
||||
},
|
||||
{
|
||||
currentId: resPartnerId1,
|
||||
resId: resPartnerId1,
|
||||
resIds: [resPartnerId1, resPartnerId2],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
startServer,
|
||||
} from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
import { patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import { editInput, editSelect, selectDropdownItem, patchWithCleanup, patchTimeZone } from "@web/../tests/helpers/utils";
|
||||
|
||||
import session from 'web.session';
|
||||
import testUtils from 'web.test_utils';
|
||||
@@ -34,16 +34,17 @@ QUnit.module('tracking_value_tests.js', {
|
||||
</form>`,
|
||||
};
|
||||
this.start = async ({ res_id }) => {
|
||||
const { openView, ...remainder } = await start({
|
||||
const { openFormView, ...remainder } = await start({
|
||||
serverData: { views },
|
||||
});
|
||||
await openView(
|
||||
await openFormView(
|
||||
{
|
||||
res_model: 'mail.test.track.all',
|
||||
res_id,
|
||||
views: [[false, 'form']],
|
||||
},
|
||||
{ mode: 'edit' }
|
||||
{
|
||||
props: { mode: 'edit' },
|
||||
},
|
||||
);
|
||||
return remainder;
|
||||
};
|
||||
@@ -63,7 +64,7 @@ QUnit.test('basic rendering of tracking value (float type)', async function (ass
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ float_field: 12.30 });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editInput(document.querySelector('input[name=float_field]'), 45.67);
|
||||
await editInput(document.body, 'div[name=float_field] input', 45.67);
|
||||
await click('.o_form_button_save');
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
@@ -114,7 +115,7 @@ QUnit.test('rendering of tracked field of type float: from non-0 to 0', async fu
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ float_field: 1 });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editInput(document.querySelector('input[name=float_field]'), 0);
|
||||
await editInput(document.body, 'div[name=float_field] input', 0);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
@@ -130,7 +131,7 @@ QUnit.test('rendering of tracked field of type float: from 0 to non-0', async fu
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ float_field: 0 });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editInput(document.querySelector('input[name=float_field]'), 1);
|
||||
await editInput(document.body, 'div[name=float_field] input', 1);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
@@ -146,7 +147,7 @@ QUnit.test('rendering of tracked field of type integer: from non-0 to 0', async
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ integer_field: 1 });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editInput(document.querySelector('input[name=integer_field]'), 0);
|
||||
await editInput(document.body, 'div[name=integer_field] input', 0);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
@@ -162,7 +163,7 @@ QUnit.test('rendering of tracked field of type integer: from 0 to non-0', async
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ integer_field: 0 });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editInput(document.querySelector('input[name=integer_field]'), 1);
|
||||
await editInput(document.body, 'div[name=integer_field] input', 1);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
@@ -178,7 +179,7 @@ QUnit.test('rendering of tracked field of type monetary: from non-0 to 0', async
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ monetary_field: 1 });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editSelect(document.querySelector('div[name=monetary_field] > input'), 0);
|
||||
await editInput(document.body, 'div[name=monetary_field] input', 0);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
@@ -194,7 +195,7 @@ QUnit.test('rendering of tracked field of type monetary: from 0 to non-0', async
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ monetary_field: 0 });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editSelect(document.querySelector('div[name=monetary_field] > input'), 1);
|
||||
await editInput(document.body, 'div[name=monetary_field] input', 1);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
@@ -242,7 +243,7 @@ QUnit.test('rendering of tracked field of type char: from a string to empty stri
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ char_field: 'Marc' });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editInput(document.querySelector('input[name=char_field]'), '');
|
||||
await editInput(document.body, 'div[name=char_field] input', '');
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
@@ -258,7 +259,7 @@ QUnit.test('rendering of tracked field of type char: from empty string to a stri
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ char_field: '' });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editInput(document.querySelector('input[name=char_field]'), 'Marc');
|
||||
await editInput(document.body, 'div[name=char_field] input', 'Marc');
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
@@ -274,7 +275,7 @@ QUnit.test('rendering of tracked field of type date: from no date to a set date'
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ date_field: false });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editAndTrigger(document.querySelector('.o_datepicker[name=date_field] .o_datepicker_input'), '12/14/2018', ['change']);
|
||||
await testUtils.fields.editAndTrigger(document.querySelector('div[name=date_field] .o_datepicker .o_datepicker_input'), '12/14/2018', ['change']);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
@@ -290,7 +291,7 @@ QUnit.test('rendering of tracked field of type date: from a set date to no date'
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ date_field: '2018-12-14' });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editAndTrigger(document.querySelector('.o_datepicker[name=date_field] .o_datepicker_input'), '', ['change']);
|
||||
await testUtils.fields.editAndTrigger(document.querySelector('div[name=date_field] .o_datepicker .o_datepicker_input'), '', ['change']);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
@@ -300,14 +301,18 @@ QUnit.test('rendering of tracked field of type date: from a set date to no date'
|
||||
});
|
||||
|
||||
QUnit.test('rendering of tracked field of type datetime: from no date and time to a set date and time', async function (assert) {
|
||||
assert.expect(1);
|
||||
assert.expect(2);
|
||||
|
||||
patchTimeZone(180);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ datetime_field: false });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editAndTrigger(document.querySelector('.o_datepicker[name=datetime_field] .o_datepicker_input'), '12/14/2018 13:42:28', ['change']);
|
||||
await testUtils.fields.editAndTrigger(document.querySelector('div[name=datetime_field] .o_datepicker .o_datepicker_input'), '12/14/2018 13:42:28', ['change']);
|
||||
await click('.o_form_button_save');
|
||||
const savedRecord = pyEnv.getData()["mail.test.track.all"].records.find(({id}) => id === mailTestTrackAllId1);
|
||||
assert.strictEqual(savedRecord.datetime_field, '2018-12-14 10:42:28');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"Datetime:None12/14/2018 13:42:28",
|
||||
@@ -318,15 +323,17 @@ QUnit.test('rendering of tracked field of type datetime: from no date and time t
|
||||
QUnit.test('rendering of tracked field of type datetime: from a set date and time to no date and time', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
patchTimeZone(180)
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ datetime_field: '2018-12-14 13:42:28 ' });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editAndTrigger(document.querySelector('.o_datepicker[name=datetime_field] .o_datepicker_input'), '', ['change']);
|
||||
await testUtils.fields.editAndTrigger(document.querySelector('div[name=datetime_field] .o_datepicker .o_datepicker_input'), '', ['change']);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
"Datetime:12/14/2018 13:42:28None",
|
||||
"Datetime:12/14/2018 16:42:28None",
|
||||
"should display the correct content of tracked field of type datetime: from a set date and time to no date and time (Datetime: 12/14/2018 13:42:28 -> None)"
|
||||
);
|
||||
});
|
||||
@@ -338,7 +345,7 @@ QUnit.test('rendering of tracked field of type text: from some text to empty', a
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ text_field: 'Marc' });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editInput(document.querySelector('textarea[name=text_field]'), '');
|
||||
await editInput(document.body, 'div[name=text_field] textarea', '');
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
@@ -354,7 +361,7 @@ QUnit.test('rendering of tracked field of type text: from empty to some text', a
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ text_field: '' });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editInput(document.querySelector('textarea[name=text_field]'), 'Marc');
|
||||
await editInput(document.body, 'div[name=text_field] textarea', 'Marc');
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
@@ -370,7 +377,7 @@ QUnit.test('rendering of tracked field of type selection: from a selection to no
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ selection_field: 'first' });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editSelect(document.querySelector('select[name=selection_field]'), '');
|
||||
await editSelect(document.body, 'div[name=selection_field] select', false);
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
@@ -386,7 +393,7 @@ QUnit.test('rendering of tracked field of type selection: from no selection to a
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({});
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editSelect(document.querySelector('select[name=selection_field]'), '"first"');
|
||||
await editSelect(document.body, 'div[name=selection_field] select', '"first"');
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
@@ -403,7 +410,7 @@ QUnit.test('rendering of tracked field of type many2one: from having a related r
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ many2one_field_id: resPartnerId1 });
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.editAndTrigger(document.querySelector('.o_field_many2one_selection input'), '', ['keyup']);
|
||||
await editInput(document.body, ".o_field_many2one_selection input", '')
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
@@ -420,8 +427,7 @@ QUnit.test('rendering of tracked field of type many2one: from no related record
|
||||
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({});
|
||||
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
|
||||
|
||||
await testUtils.fields.many2one.clickOpenDropdown('many2one_field_id');
|
||||
await testUtils.fields.many2one.clickItem('many2one_field_id', 'Marc');
|
||||
await selectDropdownItem(document.body, "many2one_field_id", "Marc")
|
||||
await click('.o_form_button_save');
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_TrackingValue').textContent,
|
||||
|
||||
@@ -148,3 +148,13 @@ export function getTag(node, lower = false) {
|
||||
const tag = (node && node.nodeName) || "";
|
||||
return lower ? tag.toLowerCase() : tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @param {Object} attributes
|
||||
*/
|
||||
export function setAttributes(node, attributes) {
|
||||
for (const [name, value] of Object.entries(attributes)) {
|
||||
node.setAttribute(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,6 +215,7 @@ function mapStaticListDatapoint(staticList) {
|
||||
data: staticList.records.map(mapRecordDatapoint),
|
||||
groupedBy: [],
|
||||
orderedBy: staticList.orderBy,
|
||||
count: staticList.count,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { SIZES } from "@web/core/ui/ui_service";
|
||||
import {
|
||||
append,
|
||||
combineAttributes,
|
||||
@@ -187,7 +188,7 @@ export class FormCompiler extends ViewCompiler {
|
||||
compileForm(el, params) {
|
||||
const form = createElement("div", {
|
||||
"t-att-class": "props.class",
|
||||
"t-attf-class": `{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}}`,
|
||||
"t-attf-class": `{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-flex {{ uiService.size < ${SIZES.XXL} ? "flex-column" : "flex-nowrap h-100" }}`,
|
||||
});
|
||||
|
||||
const sheetNode = el.querySelector("sheet");
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
import { Notebook } from "@web/core/notebook/notebook";
|
||||
import { Field } from "@web/views/fields/field";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { useDebounced } from "@web/core/utils/timing";
|
||||
import { ButtonBox } from "@web/views/form/button_box/button_box";
|
||||
import { InnerGroup, OuterGroup } from "@web/views/form/form_group/form_group";
|
||||
import { ViewButton } from "@web/views/view_button/view_button";
|
||||
@@ -13,7 +16,7 @@ import { FormCompiler } from "./form_compiler";
|
||||
import { FormLabel } from "./form_label";
|
||||
import { StatusBarButtons } from "./status_bar_buttons/status_bar_buttons";
|
||||
|
||||
const { Component, useSubEnv, useRef, useState, xml } = owl;
|
||||
const { Component, onMounted, onWillUnmount, useSubEnv, useRef, useState, xml } = owl;
|
||||
|
||||
export class FormRenderer extends Component {
|
||||
setup() {
|
||||
@@ -31,6 +34,10 @@ export class FormRenderer extends Component {
|
||||
useBounceButton(useRef("compiled_view_root"), () => {
|
||||
return !record.isInEdition;
|
||||
});
|
||||
this.uiService = useService('ui');
|
||||
this.onResize = useDebounced(this.render, 200);
|
||||
onMounted(() => browser.addEventListener('resize', this.onResize));
|
||||
onWillUnmount(() => browser.removeEventListener('resize', this.onResize));
|
||||
}
|
||||
|
||||
evalDomainFromRecord(record, expr) {
|
||||
|
||||
@@ -772,7 +772,10 @@ export class ListRenderer extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
async onCellClicked(record, column) {
|
||||
async onCellClicked(record, column, ev) {
|
||||
if (ev.target.special_click) {
|
||||
return;
|
||||
}
|
||||
const recordAfterResequence = async () => {
|
||||
const recordIndex = this.props.list.records.indexOf(record);
|
||||
await this.resequencePromise;
|
||||
|
||||
@@ -182,7 +182,7 @@
|
||||
</td>
|
||||
<t t-foreach="state.columns" t-as="column" t-key="column.id">
|
||||
<t t-if="column.type === 'field'">
|
||||
<td t-on-keydown.synthetic="(ev) => this.onCellKeydown(ev, group, record)" class="o_data_cell" t-att-name="column.name" t-att-class="getCellClass(column, record)" t-att-data-tooltip="getCellTitle(column, record)" data-tooltip-delay="1000" t-on-click="() => this.onCellClicked(record, column)" tabindex="-1">
|
||||
<td t-on-keydown.synthetic="(ev) => this.onCellKeydown(ev, group, record)" class="o_data_cell" t-att-name="column.name" t-att-class="getCellClass(column, record)" t-att-data-tooltip="getCellTitle(column, record)" data-tooltip-delay="1000" t-on-click="(ev) => this.onCellClicked(record, column, ev)" tabindex="-1">
|
||||
<t t-if="!evalModifier(column.modifiers.invisible, record)">
|
||||
<t t-if="canUseFormatter(column, record)" t-esc="getFormattedValue(column, record)"/>
|
||||
<Field t-else="" name="column.name" record="record" type="column.widget" fieldInfo="props.archInfo.fieldNodes[column.name]" setDirty="(isDirty) => this.setDirty(isDirty)"/>
|
||||
|
||||
@@ -725,6 +725,7 @@ export async function dragAndDrop(from, to, position) {
|
||||
export async function clickDropdown(target, fieldName) {
|
||||
const dropdownInput = target.querySelector(`[name='${fieldName}'] .dropdown input`);
|
||||
dropdownInput.focus();
|
||||
await nextTick();
|
||||
await click(dropdownInput);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ QUnit.module("Form Compiler", () => {
|
||||
const arch = /*xml*/ `<form><div>lol</div></form>`;
|
||||
const expected = /*xml*/ `
|
||||
<t>
|
||||
<div t-att-class="props.class" t-attf-class="{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<div t-att-class="props.class" t-attf-class="{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-flex {{ uiService.size < 6 ? "flex-column" : "flex-nowrap h-100" }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<div>lol</div>
|
||||
</div>
|
||||
</t>`;
|
||||
@@ -44,7 +44,7 @@ QUnit.module("Form Compiler", () => {
|
||||
const arch = /*xml*/ `<form><div class="someClass">lol<field name="display_name"/></div></form>`;
|
||||
const expected = /*xml*/ `
|
||||
<t>
|
||||
<div t-att-class="props.class" t-attf-class="{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<div t-att-class="props.class" t-attf-class="{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-flex {{ uiService.size < 6 ? "flex-column" : "flex-nowrap h-100" }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<div class="someClass">
|
||||
lol
|
||||
<Field id="'display_name'" name="'display_name'" record="props.record" fieldInfo="props.archInfo.fieldNodes['display_name']"/>
|
||||
@@ -128,7 +128,7 @@ QUnit.module("Form Compiler", () => {
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<t>
|
||||
<div t-att-class="props.class" t-attf-class="{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<div t-att-class="props.class" t-attf-class="{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-flex {{ uiService.size < 6 ? "flex-column" : "flex-nowrap h-100" }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<div class="o_form_statusbar"><StatusBarButtons readonly="!props.record.isInEdition"/></div>
|
||||
<div>someDiv</div>
|
||||
</div>
|
||||
@@ -150,7 +150,7 @@ QUnit.module("Form Compiler", () => {
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<t>
|
||||
<div t-att-class="props.class" t-attf-class="{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}}" t-ref="compiled_view_root">
|
||||
<div t-att-class="props.class" t-attf-class="{{props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-flex {{ uiService.size < 6 ? "flex-column" : "flex-nowrap h-100" }}" t-ref="compiled_view_root">
|
||||
<div class="o_form_sheet_bg">
|
||||
<div class="o_form_statusbar"><StatusBarButtons readonly="!props.record.isInEdition"/></div>
|
||||
<div>someDiv</div>
|
||||
|
||||
+52
-63
@@ -1,77 +1,66 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
afterNextRender,
|
||||
start,
|
||||
startServer,
|
||||
} from '@mail/../tests/helpers/test_utils';
|
||||
import { ViewAdapter } from '@web/legacy/action_adapters';
|
||||
import { patchWithCleanup } from '@web/../tests/helpers/utils';
|
||||
import { afterNextRender, start, startServer } from "@mail/../tests/helpers/test_utils";
|
||||
import FormView from "web.FormView";
|
||||
|
||||
import FormView from 'web.FormView';
|
||||
QUnit.module("website_livechat", {}, function () {
|
||||
QUnit.module("messaging_notification_handler_tests.js");
|
||||
|
||||
QUnit.module('website_livechat', {}, function () {
|
||||
QUnit.module('messaging_notification_handler_tests.js');
|
||||
QUnit.test(
|
||||
"should open chat window on send chat request to website visitor",
|
||||
async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
QUnit.test('should open chat window on send chat request to website visitor', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const websiteVisitorId1 = pyEnv['website.visitor'].create({
|
||||
display_name: "Visitor #11",
|
||||
});
|
||||
const views = {
|
||||
'website.visitor,false,form':
|
||||
`<form>
|
||||
const pyEnv = await startServer();
|
||||
const websiteVisitorId1 = pyEnv["website.visitor"].create({
|
||||
display_name: "Visitor #11",
|
||||
});
|
||||
const views = {
|
||||
"website.visitor,false,form": `<form>
|
||||
<header>
|
||||
<button name="action_send_chat_request" string="Send chat request" class="btn btn-primary" type="button"/>
|
||||
</header>
|
||||
<field name="name"/>
|
||||
</form>`,
|
||||
};
|
||||
const { openView } = await start({
|
||||
serverData: { views },
|
||||
View: FormView,
|
||||
});
|
||||
await openView({
|
||||
res_model: 'website.visitor',
|
||||
res_id: websiteVisitorId1,
|
||||
views: [[false, 'form']],
|
||||
});
|
||||
patchWithCleanup(ViewAdapter.prototype, {
|
||||
_trigger_up: async function({ name, data }) {
|
||||
if (name === 'execute_action') {
|
||||
this.env.services.rpc({
|
||||
route: '/web/dataset/call_button',
|
||||
params: {
|
||||
args: [data.env.resIDs],
|
||||
kwargs: { context: data.env.context },
|
||||
method: data.action_data.name,
|
||||
model: data.env.model,
|
||||
}
|
||||
};
|
||||
|
||||
const { openView, env } = await start({
|
||||
serverData: { views },
|
||||
View: FormView,
|
||||
});
|
||||
|
||||
await openView({
|
||||
res_model: "website.visitor",
|
||||
res_id: websiteVisitorId1,
|
||||
views: [[false, "form"]],
|
||||
});
|
||||
|
||||
// Simulate a click on "Send chat request"
|
||||
// This is a bit of a hack as it doesn't require the button at all to work.
|
||||
await afterNextRender(async () => {
|
||||
await env.services.rpc("/web/dataset/call_button", {
|
||||
args: [websiteVisitorId1],
|
||||
kwargs: { context: env.context },
|
||||
method: "action_send_chat_request",
|
||||
model: "website.visitor",
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await afterNextRender(() =>
|
||||
document.querySelector('button[name="action_send_chat_request"]').click()
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_ChatWindow',
|
||||
"should have a chat window open after sending chat request to website visitor"
|
||||
);
|
||||
assert.hasClass(
|
||||
document.querySelector('.o_ChatWindow'),
|
||||
'o-focused',
|
||||
"chat window of livechat should be focused on open"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_ChatWindowHeader_name').textContent,
|
||||
"Visitor #11",
|
||||
"chat window of livechat should have name of visitor in the name"
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
".o_ChatWindow",
|
||||
"should have a chat window open after sending chat request to website visitor"
|
||||
);
|
||||
assert.hasClass(
|
||||
document.querySelector(".o_ChatWindow"),
|
||||
"o-focused",
|
||||
"chat window of livechat should be focused on open"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector(".o_ChatWindowHeader_name").textContent,
|
||||
"Visitor #11",
|
||||
"chat window of livechat should have name of visitor in the name"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user