Initial commit: Technical packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:51 +02:00
commit 3473fa71a0
873 changed files with 297766 additions and 0 deletions

View file

@ -0,0 +1,49 @@
/** @odoo-module **/
import { start, startServer } from '@mail/../tests/helpers/test_utils';
QUnit.module('calendar', () => {
QUnit.module('components', () => {
QUnit.module('activity_tests.js');
QUnit.test('activity click on Reschedule', async function (assert) {
assert.expect(1);
const pyEnv = await startServer();
const resPartnerId = pyEnv['res.partner'].create({});
const meetingActivityTypeId = pyEnv['mail.activity.type'].create({ icon: 'fa-calendar', name: "Meeting" });
const calendarAttendeeId = pyEnv['calendar.attendee'].create({ partner_id: resPartnerId });
const calendaMeetingId = pyEnv['calendar.event'].create({
res_model: "calendar.event",
name: "meeting1",
start: "2022-07-06 06:30:00",
attendee_ids: [calendarAttendeeId],
});
pyEnv['mail.activity'].create({
name: "Small Meeting",
activity_type_id: meetingActivityTypeId,
can_write: true,
res_id: resPartnerId,
res_model: 'res.partner',
calendar_event_id: calendaMeetingId,
});
const { click, openFormView } = await start();
await openFormView(
{
res_model: 'res.partner',
res_id: resPartnerId,
},
);
await click('.o_Activity_editButton');
assert.containsOnce(
document.body,
'.o_calendar_view',
"should have opened calendar view"
);
});
});
});

View file

@ -0,0 +1,120 @@
/** @odoo-module */
import { startServer } from "@bus/../tests/helpers/mock_python_environment";
import { calendarNotificationService } from "@calendar/js/services/calendar_notification_service";
import { click, contains } from "@web/../tests/utils";
import { registry } from "@web/core/registry";
import { start } from "@mail/../tests/helpers/test_utils";
const serviceRegistry = registry.category("services");
QUnit.module("Calendar Notification", (hooks) => {
hooks.beforeEach(() => {
serviceRegistry.add("calendarNotification", calendarNotificationService);
});
QUnit.test(
"can listen on bus and display notifications in DOM and click OK",
async (assert) => {
const pyEnv = await startServer();
const mockRPC = (route, args) => {
if (route === "/calendar/notify") {
return Promise.resolve([]);
}
if (route === "/calendar/notify_ack") {
assert.step("notifyAck");
return Promise.resolve(true);
}
};
await start({ mockRPC });
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "calendar.alarm", [
{
alarm_id: 1,
event_id: 2,
title: "Meeting",
message: "Very old meeting message",
timer: 0,
notify_at: "1978-04-14 12:45:00",
},
]);
await contains(".o_notification", { text: "Very old meeting message" });
await click(".o_notification_buttons button", { text: "OK" });
await contains(".o_notification", { count: 0 });
assert.verifySteps(["notifyAck"]);
}
);
QUnit.test(
"can listen on bus and display notifications in DOM and click Detail",
async (assert) => {
const pyEnv = await startServer();
const mockRPC = (route, args) => {
if (route === "/calendar/notify") {
return Promise.resolve([]);
}
};
const fakeActionService = {
name: "action",
start() {
return {
doAction(actionId) {
assert.step(actionId.type);
return Promise.resolve(true);
},
loadState(state, options) {
return Promise.resolve(true);
},
};
},
};
serviceRegistry.add("action", fakeActionService, { force: true });
await start({ mockRPC });
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "calendar.alarm", [
{
alarm_id: 1,
event_id: 2,
title: "Meeting",
message: "Very old meeting message",
timer: 0,
notify_at: "1978-04-14 12:45:00",
},
]);
await contains(".o_notification", { text: "Very old meeting message" });
await click(".o_notification_buttons button", { text: "Details" });
await contains(".o_notification", { count: 0 });
assert.verifySteps(["ir.actions.act_window"]);
}
);
QUnit.test(
"can listen on bus and display notifications in DOM and click Snooze",
async (assert) => {
const pyEnv = await startServer();
const mockRPC = (route, args) => {
if (route === "/calendar/notify") {
return Promise.resolve([]);
}
if (route === "/calendar/notify_ack") {
assert.step("notifyAck");
return Promise.resolve(true);
}
};
await start({ mockRPC });
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "calendar.alarm", [
{
alarm_id: 1,
event_id: 2,
title: "Meeting",
message: "Very old meeting message",
timer: 0,
notify_at: "1978-04-14 12:45:00",
},
]);
await contains(".o_notification", { text: "Very old meeting message" });
await click(".o_notification button", { text: "Snooze" });
await contains(".o_notification", { count: 0 });
assert.verifySteps([], "should only close the notification withtout calling a rpc");
}
);
});

View file

@ -0,0 +1,129 @@
/** @odoo-module **/
import { getFixture } from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
let serverData;
let target;
QUnit.module(
"calendar",
{
beforeEach: function () {
target = getFixture();
serverData = {
models: {
event: {
fields: {
partner_ids: {
string: "Partners",
type: "many2many",
relation: "partner",
},
},
records: [
{
id: 14,
partner_ids: [1, 2],
},
],
},
partner: {
fields: {
name: { string: "Name", type: "char" },
},
records: [
{
id: 1,
name: "Jesus",
},
{
id: 2,
name: "Mahomet",
},
],
},
},
};
setupViewRegistries();
},
},
function () {
QUnit.test("Many2ManyAttendee: basic rendering", async function (assert) {
await makeView({
type: "form",
resModel: "event",
serverData,
resId: 14,
arch: `
<form>
<field name="partner_ids" widget="many2manyattendee"/>
</form>`,
mockRPC(route, args) {
if (args.method === "get_attendee_detail") {
assert.step(args.method);
assert.strictEqual(
args.model,
"res.partner",
"the method should only be called on res.partner"
);
assert.deepEqual(
args.args[0],
[1, 2],
"the partner ids should be passed as argument"
);
assert.deepEqual(
args.args[1],
[14],
"the event id should be passed as argument"
);
return Promise.resolve([
{ id: 1, name: "Jesus", status: "accepted", color: 0 },
{ id: 2, name: "Mahomet", status: "tentative", color: 0 },
]);
}
},
});
assert.hasClass(
target.querySelector('.o_field_widget[name="partner_ids"] div'),
"o_field_tags"
);
assert.containsN(
target,
'.o_field_widget[name="partner_ids"] .badge',
2,
"there should be 2 tags"
);
const badges = target.querySelectorAll('.o_field_widget[name="partner_ids"] .badge');
assert.strictEqual(
badges[0].textContent.trim(),
"Jesus",
"the tag should be correctly named"
);
assert.hasClass(
badges[0].querySelector("img"),
"o_attendee_border_accepted",
"Jesus should attend the meeting"
);
assert.strictEqual(
badges[1].textContent.trim(),
"Mahomet",
"the tag should be correctly named"
);
assert.hasClass(
badges[1].querySelector("img"),
"o_attendee_border_tentative",
"Mohamet should still confirm his attendance to the meeting"
);
assert.containsOnce(badges[0], "img", "should have img tag");
assert.hasAttrValue(
badges[0].querySelector("img"),
"data-src",
"/web/image/partner/1/avatar_128",
"should have correct avatar image"
);
assert.verifySteps(["get_attendee_detail"]);
});
}
);

View file

@ -0,0 +1,93 @@
/** @odoo-module **/
// ensure mail override is applied first.
import '@mail/../tests/helpers/mock_server';
import { patch } from '@web/core/utils/patch';
import { MockServer } from '@web/../tests/helpers/mock_server';
import { datetime_to_str } from 'web.time';
patch(MockServer.prototype, 'calendar', {
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @override
*/
async _performRPC(route, args) {
// mail.activity methods
if (args.model === 'mail.activity' && args.method === 'action_create_calendar_event') {
return {
type: 'ir.actions.act_window',
name: "Meetings",
res_model: 'calendar.event',
view_mode: 'calendar',
views: [[false, 'calendar']],
target: 'current',
};
}
// calendar.event methods
if (args.model === 'calendar.event' && args.method === 'check_access_rights') {
return true;
}
return this._super(...arguments);
},
//--------------------------------------------------------------------------
// Private Mocked Methods
//--------------------------------------------------------------------------
/**
* Simulates `_systray_get_calendar_event_domain` on `res.users`.
*
* @private
*/
_mockResUsers_SystrayGetCalendarEventDomain() {
const startDate = new Date();
startDate.setUTCHours(0, 0, 0, 0);
const endDate = new Date();
endDate.setUTCHours(23, 59, 59, 999);
const currentPartnerAttendeeIds = this.pyEnv['calendar.attendee'].search([['partner_id', '=', this.currentPartnerId]]);
return [
'&',
'|',
'&',
'|',
['start', '>=', datetime_to_str(startDate)],
['stop', '>=', datetime_to_str(startDate)],
['start', '<=', datetime_to_str(endDate)],
'&',
['allday', '=', true],
['start_date', '=', datetime_to_str(startDate)],
['attendee_ids', 'in', currentPartnerAttendeeIds],
];
},
/**
* Simulates `systray_get_activities` on `res.users`.
*
* @override
*/
_mockResUsersSystrayGetActivities() {
const activities = this._super(...arguments);
const meetingsLines = this.pyEnv['calendar.event'].searchRead(
this._mockResUsers_SystrayGetCalendarEventDomain(),
{
fields: ['id', 'start', 'name', 'allday', 'attendee_status'],
order: 'start',
}
).filter(meetingLine => meetingLine['attendee_status'] !== 'declined');
if (meetingsLines.length) {
activities.unshift({
id: 'calendar.event', // for simplicity
meetings: meetingsLines,
model: 'calendar.event',
name: 'Today\'s Meetings',
type: 'meeting',
});
}
return activities;
},
});

View file

@ -0,0 +1,5 @@
/** @odoo-module **/
import { addModelNamesToFetch } from '@bus/../tests/helpers/model_definitions_helpers';
addModelNamesToFetch(['calendar.event', 'calendar.attendee']);

View file

@ -0,0 +1,8 @@
/** @odoo-module **/
import { registry } from '@web/core/registry';
const viewArchsRegistry = registry.category('bus.view.archs');
const calendarArchsRegistry = viewArchsRegistry.category('calendar');
calendarArchsRegistry.add('default', '<calendar date_start="start"/>');

View file

@ -0,0 +1,129 @@
/** @odoo-module **/
import FormView from "web.FormView";
import testUtils from "web.test_utils";
var createView = testUtils.createView;
QUnit.module(
"Legacy calendar",
{
beforeEach: function () {
this.data = {
event: {
fields: {
partner_ids: { string: "Partners", type: "many2many", relation: "partner" },
},
records: [
{
id: 14,
partner_ids: [1, 2],
},
],
},
partner: {
fields: {
name: { string: "Name", type: "char" },
},
records: [
{
id: 1,
name: "Jesus",
},
{
id: 2,
name: "Mahomet",
},
],
},
};
},
},
function () {
QUnit.test("many2manyattendee widget: basic rendering", async function (assert) {
assert.expect(12);
var form = await createView({
View: FormView,
model: "event",
data: this.data,
res_id: 14,
debug: 1,
arch:
"<form>" + '<field name="partner_ids" widget="many2manyattendee"/>' + "</form>",
mockRPC: function (route, args) {
if (args.method === "get_attendee_detail") {
assert.strictEqual(
args.model,
"res.partner",
"the method should only be called on res.partner"
);
assert.deepEqual(
args.args[0],
[1, 2],
"the partner ids should be passed as argument"
);
assert.deepEqual(
args.args[1],
[14],
"the event id should be passed as argument"
);
return Promise.resolve([
{ id: 1, name: "Jesus", status: "accepted", color: 0 },
{ id: 2, name: "Mahomet", status: "tentative", color: 0 },
]);
}
return this._super.apply(this, arguments);
},
});
assert.hasClass(form.$('.o_field_widget[name="partner_ids"]'), "o_field_many2manytags");
assert.containsN(
form,
'.o_field_widget[name="partner_ids"] .badge',
2,
"there should be 2 tags"
);
assert.strictEqual(
form.$('.o_field_widget[name="partner_ids"] .badge:first').text().trim(),
"Jesus",
"the tag should be correctly named"
);
assert.hasClass(
form.$('.o_field_widget[name="partner_ids"] .badge:first img'),
"o_attendee_border_accepted",
"Jesus should attend the meeting"
);
assert.strictEqual(
form.$('.o_field_widget[name="partner_ids"] .badge[data-id="2"]').text().trim(),
"Mahomet",
"the tag should be correctly named"
);
assert.hasClass(
form.el.querySelector(
'.o_field_widget[name="partner_ids"] .badge[data-id="2"] img'
),
"o_attendee_border_tentative",
"Mohamet should still confirm his attendance to the meeting"
);
assert.hasClass(
form.el.querySelector(".o_field_many2manytags"),
"avatar",
"should have avatar class"
);
assert.containsOnce(
form,
".o_field_many2manytags.avatar.o_field_widget .badge:first img",
"should have img tag"
);
assert.hasAttrValue(
form.$(".o_field_many2manytags.avatar.o_field_widget .badge:first img"),
"data-src",
"/web/image/partner/1/avatar_128",
"should have correct avatar image"
);
form.destroy();
});
}
);

View file

@ -0,0 +1,48 @@
/** @odoo-module **/
import { start, startServer } from '@mail/../tests/helpers/test_utils';
import { patchDate, patchWithCleanup } from "@web/../tests/helpers/utils";
QUnit.module('calendar', {}, function () {
QUnit.module('ActivityMenu');
QUnit.test('activity menu widget:today meetings', async function (assert) {
assert.expect(6);
patchDate(2018, 3, 20, 6, 0, 0);
const pyEnv = await startServer();
const calendarAttendeeId1 = pyEnv['calendar.attendee'].create({ partner_id: pyEnv.currentPartnerId });
pyEnv['calendar.event'].create([
{
res_model: "calendar.event",
name: "meeting1",
start: "2018-04-20 06:30:00",
attendee_ids: [calendarAttendeeId1],
},
{
res_model: "calendar.event",
name: "meeting2",
start: "2018-04-20 09:30:00",
attendee_ids: [calendarAttendeeId1],
},
]);
const { click, env } = await start();
assert.containsOnce(document.body, '.o_ActivityMenuView', 'should contain an instance of widget');
await click('.dropdown-toggle[title="Activities"]');
patchWithCleanup(env.services.action, {
doAction(action) {
assert.strictEqual(action, "calendar.action_calendar_event", 'should open meeting calendar view in day mode');
},
});
assert.ok(document.querySelector('.o_meeting_filter'), "should be a meeting");
assert.containsN(document.body, '.o_meeting_filter', 2, 'there should be 2 meetings');
assert.hasClass(document.querySelector('.o_meeting_filter'), 'o_meeting_bold', 'this meeting is yet to start');
assert.doesNotHaveClass(document.querySelectorAll('.o_meeting_filter')[1], 'o_meeting_bold', 'this meeting has been started');
await click('.o_ActivityMenuView_activityGroup');
});
});

View file

@ -0,0 +1,155 @@
/** @odoo-module **/
import tour from 'web_tour.tour';
const todayDate = function() {
let now = new Date();
let year = now.getFullYear();
let month = String(now.getMonth() + 1).padStart(2, '0');
let day = String(now.getDate()).padStart(2, '0');
return `${month}/${day}/${year} 10:00:00`;
};
tour.register('calendar_appointments_hour_tour', {
url: '/web',
test: true,
}, [
tour.stepUtils.showAppsMenuItem(),
{
trigger: '.o_app[data-menu-xmlid="calendar.mail_menu_calendar"]',
content: 'Open Calendar',
run: 'click',
},
{
trigger: '.o-calendar-button-new',
content: 'Create a new event',
run: 'click',
},
{
trigger: '#name',
content: 'Give a name to the new event',
run: 'text TEST EVENT',
},
{
trigger: '#start',
content: 'Give a date to the new event',
run: `text ${todayDate()}`,
},
{
trigger: '.fa-cloud-upload',
content: 'Save the new event',
run: 'click',
},
{
trigger: '.dropdown-item:contains("Calendar")',
content: 'Go back to Calendar view',
run: 'click',
},
{
trigger: '.dropdown-toggle:contains("Week")',
content: 'Click to change calendar view',
run: 'click',
},
{
trigger: '.dropdown-item:contains("Month")',
content: 'Change the calendar view to Month',
run: 'click',
},
{
trigger: '.fc-day-header:contains("Monday")',
content: 'Change the calendar view to week',
},
{
trigger: '.fc-time:contains("10:00")',
content: 'Check the time is properly displayed',
},
{
trigger: '.o_event_title:contains("TEST EVENT")',
content: 'Check the event title',
},
]);
tour.register('test_calendar_delete_tour', {
test: true,
},
[
{
content: 'Select filter (everybody)',
trigger: 'div[data-value="all"] input',
},
{
content: 'Click on the event (focus + waiting)',
trigger: 'a .fc-content:contains("Test Event")',
async run() {
$('a .fc-content:contains("Test Event")').click();
await new Promise((r) => setTimeout(r, 1000));
$('a .fc-content:contains("Test Event")').click();
}
},
{
content: 'Delete the event',
trigger: '.o_cw_popover_delete',
},
{
content: 'Validate the deletion',
trigger:'button:contains("Ok")',
async run() {
$('button:contains("Ok")').click();
await new Promise((r) => setTimeout(r, 1000));
}
},
]);
tour.register('test_calendar_decline_tour', {
test: true,
},
[
{
content: 'Click on the event (focus + waiting)',
trigger: 'a .fc-content:contains("Test Event")',
async run() {
$('a .fc-content:contains("Test Event")').click();
await new Promise((r) => setTimeout(r, 1000));
$('a .fc-content:contains("Test Event")').click();
}
},
{
content: 'Delete the event',
trigger: '.o_cw_popover_delete',
},
{
content: 'Wait declined status',
trigger: '.o_attendee_status_declined',
},
]);
tour.register('test_calendar_decline_with_everybody_filter_tour', {
test: true,
},
[
{
content: 'Select filter (everybody)',
trigger: 'div[data-value="all"] input',
},
{
content: 'Click on the event (focus + waiting)',
trigger: 'a .fc-content:contains("Test Event")',
async run() {
$('a .fc-content:contains("Test Event")').click();
await new Promise((r) => setTimeout(r, 1000));
$('a .fc-content:contains("Test Event")').click();
}
},
{
content: 'Delete the event',
trigger: '.o_cw_popover_delete',
},
{
content: 'Select filter (everybody)',
trigger: 'div[data-value="all"] input',
},
{
content: 'Wait declined status',
trigger: '.o_attendee_status_declined',
},
]);