19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:31:00 +01:00
parent a1137a1456
commit e1d89e11e3
2789 changed files with 1093187 additions and 605897 deletions

View file

@ -0,0 +1,33 @@
import { describe, test } from "@odoo/hoot";
import { Command, serverState } from "@web/../tests/web_test_helpers";
import { contains, openDiscuss, start, startServer } from "@mail/../tests/mail_test_helpers";
import { defineHrHolidaysModels } from "@hr_holidays/../tests/hr_holidays_test_helpers";
describe.current.tags("desktop");
defineHrHolidaysModels();
test("on leave members are categorised correctly in online/offline", async () => {
const pyEnv = await startServer();
const [partnerId1, partnerId2, partnerId3] = pyEnv["res.partner"].create([
{ name: "Online Partner", im_status: "online" },
{ name: "On Leave Online", im_status: "online" },
{ name: "On Leave Idle", im_status: "away" },
]);
pyEnv["res.users"].write([serverState.userId], { leave_date_to: "2023-01-02" });
pyEnv["res.users"].create({ partner_id: partnerId2, leave_date_to: "2023-01-03" });
pyEnv["res.users"].create({ partner_id: partnerId3, leave_date_to: "2023-01-04" });
const channelId = pyEnv["discuss.channel"].create({
name: "TestChanel",
channel_member_ids: [
Command.create({ partner_id: serverState.partnerId }),
Command.create({ partner_id: partnerId1 }),
Command.create({ partner_id: partnerId2 }),
Command.create({ partner_id: partnerId3 }),
],
channel_type: "channel",
});
await start();
await openDiscuss(channelId);
await contains(".o-discuss-ChannelMemberList h6", { text: "Online - 3" });
await contains(".o-discuss-ChannelMemberList h6", { text: "Offline - 1" });
});

View file

@ -1,27 +0,0 @@
/** @odoo-module **/
import '@mail/../tests/helpers/mock_server'; // ensure mail overrides are applied first
import { patch } from "@web/core/utils/patch";
import { MockServer } from "@web/../tests/helpers/mock_server";
patch(MockServer.prototype, 'hr_holidays', {
/**
* Overrides to add out of office to employees.
*
* @override
*/
_mockResPartnerMailPartnerFormat(ids) {
const partnerFormats = this._super(...arguments);
const partners = this.getRecords(
'res.partner',
[['id', 'in', ids]],
{ active_test: false },
);
for (const partner of partners) {
// Not a real field but ease the testing
partnerFormats.get(partner.id).out_of_office_date_end = partner.out_of_office_date_end;
}
return partnerFormats;
},
});

View file

@ -1,7 +0,0 @@
/** @odoo-module **/
import { insertModelFields } from '@bus/../tests/helpers/model_definitions_helpers';
insertModelFields('res.partner', {
out_of_office_date_end: { type: 'date' },
});

View file

@ -0,0 +1,22 @@
import { defineModels } from "@web/../tests/web_test_helpers";
import { ResUsers } from "@hr_holidays/../tests/mock_server/mock_models/res_users";
import { ResPartner } from "@hr_holidays/../tests/mock_server/mock_models/res_partner";
import { HrEmployee } from "@hr_holidays/../tests/mock_server/mock_models/hr_employee";
import { HrLeave } from "@hr_holidays/../tests/mock_server/mock_models/hr_leave";
import { HrDepartment } from "@hr_holidays/../tests/mock_server/mock_models/hr_department";
import { HrLeaveType } from "@hr_holidays/../tests/mock_server/mock_models/hr_leave_type";
import { hrModels } from "@hr/../tests/hr_test_helpers";
export function defineHrHolidaysModels() {
return defineModels(hrHolidaysModels);
}
export const hrHolidaysModels = {
...hrModels,
ResUsers,
ResPartner,
HrEmployee,
HrDepartment,
HrLeaveType,
HrLeave,
};

View file

@ -0,0 +1,45 @@
import { describe, test } from "@odoo/hoot";
import { Store } from "@mail/core/common/store_service";
import { startServer, start, openDiscuss, contains } from "@mail/../tests/mail_test_helpers";
import { serverState, patchWithCleanup } from "@web/../tests/web_test_helpers";
import { defineHrHolidaysModels } from "@hr_holidays/../tests/hr_holidays_test_helpers";
describe.current.tags("desktop");
defineHrHolidaysModels();
test("change icon on change partner im_status for leave variants", async () => {
const pyEnv = await startServer();
pyEnv["res.partner"].write([serverState.partnerId], { im_status: "online" });
pyEnv["res.users"].write([serverState.userId], { leave_date_to: "2023-01-01" });
const channelId = pyEnv["discuss.channel"].create({ channel_type: "chat" });
patchWithCleanup(Store, { IM_STATUS_DEBOUNCE_DELAY: 0 });
await start();
await openDiscuss(channelId);
await contains(
".o-mail-DiscussContent-header .o-mail-ImStatus .fa-plane[title='On Leave (Online)']"
);
pyEnv["bus.bus"]._sendone("broadcast", "bus.bus/im_status_updated", {
partner_id: serverState.partnerId,
im_status: "leave_offline",
presence_status: "offline",
});
await contains(".o-mail-DiscussContent-header .o-mail-ImStatus .fa-plane[title='On Leave']");
pyEnv["bus.bus"]._sendone("broadcast", "bus.bus/im_status_updated", {
partner_id: serverState.partnerId,
im_status: "leave_away",
presence_status: "away",
});
await contains(
".o-mail-DiscussContent-header .o-mail-ImStatus .fa-plane[title='On Leave (Idle)']"
);
pyEnv["bus.bus"]._sendone("broadcast", "bus.bus/im_status_updated", {
partner_id: serverState.partnerId,
im_status: "leave_online",
presence_status: "online",
});
await contains(
".o-mail-DiscussContent-header .o-mail-ImStatus .fa-plane[title='On Leave (Online)']"
);
});

View file

@ -0,0 +1,59 @@
import { describe, test } from "@odoo/hoot";
import { Command, serverState } from "@web/../tests/web_test_helpers";
import { startServer, start, openDiscuss, contains } from "@mail/../tests/mail_test_helpers";
import { defineHrHolidaysModels } from "@hr_holidays/../tests/hr_holidays_test_helpers";
describe.current.tags("desktop");
defineHrHolidaysModels();
test("on leave & online", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo", im_status: "online" });
pyEnv["res.users"].create({ partner_id: partnerId, leave_date_to: "2023-01-01" });
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ partner_id: serverState.partnerId }),
Command.create({ partner_id: partnerId }),
],
channel_type: "chat",
});
await start();
await openDiscuss(channelId);
await contains(
".o-mail-DiscussContent-header .o-mail-ImStatus i.fa-plane[title='On Leave (Online)']"
);
});
test("on leave & away", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo", im_status: "away" });
pyEnv["res.users"].create({ partner_id: partnerId, leave_date_to: "2023-01-01" });
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ partner_id: serverState.partnerId }),
Command.create({ partner_id: partnerId }),
],
channel_type: "chat",
});
await start();
await openDiscuss(channelId);
await contains(
".o-mail-DiscussContent-header .o-mail-ImStatus i.fa-plane[title='On Leave (Idle)']"
);
});
test("on leave & offline", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo", im_status: "offline" });
pyEnv["res.users"].create({ partner_id: partnerId, leave_date_to: "2023-01-01" });
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ partner_id: serverState.partnerId }),
Command.create({ partner_id: partnerId }),
],
channel_type: "chat",
});
await start();
await openDiscuss(channelId);
await contains(".o-mail-DiscussContent-header .o-mail-ImStatus i.fa-plane[title='On Leave']");
});

View file

@ -0,0 +1,85 @@
import { HrLeave } from "@hr_holidays/../tests/mock_server/mock_models/hr_leave";
import { ResUsers } from "@hr_holidays/../tests/mock_server/mock_models/res_users";
import { defineHrHolidaysModels } from "@hr_holidays/../tests/hr_holidays_test_helpers";
import { mountView, onRpc, patchWithCleanup } from "@web/../tests/web_test_helpers";
import { clickDate } from "@web/../tests/views/calendar/calendar_test_helpers";
import { describe, test } from "@odoo/hoot";
import { mockDate } from "@odoo/hoot-mock";
import { click, waitFor } from "@odoo/hoot-dom";
import { user } from "@web/core/user";
describe.current.tags("desktop");
defineHrHolidaysModels();
test("Test request creator buttons", async() => {
mockDate("2024-01-03 12:00:00", 0);
patchWithCleanup(user, { userId: 100 });
HrLeave._views = {
"form,hr_leave_view_form_dashboard_new_time_off": `
<form>
<field name="state"/>
<field name="holiday_status_id"/>
<field name="employee_id"/>
<field name="user_id"/>
<field name="can_cancel"/>
</form>
`,
};
HrLeave._records = [
{
'id': 1, 'state': 'confirm', 'holiday_status_id': 55, 'employee_id': 100,
'user_id': 100, 'date_from': '2024-01-09 09:00:00', 'date_to': '2024-01-09 18:00:00'
},
{
'id': 2, 'state': 'validate1', 'holiday_status_id': 55, 'employee_id': 100,
'can_cancel': true, 'user_id': 100, 'date_from': '2024-01-10 09:00:00', 'date_to': '2024-01-10 18:00:00'
},
]
ResUsers._records = [
...ResUsers._records,
{ 'id': 100, 'name': "User 1", 'employee_id': 100 },
]
onRpc("get_mandatory_days", () => ({}));
onRpc("get_unusual_days", () => ({}));
onRpc("get_allocation_data_request", () => ({}));
onRpc("get_special_days_data", () => ({bankHolidays: [], mandatoryDays: []}));
onRpc("hr.employee", "get_time_off_dashboard_data", () => (
{has_accrual_allocation: true, allocation_data: {}, allocation_request_amount: 0}
));
await mountView({
type: "calendar",
resModel: "hr.leave",
arch: `
<calendar js_class="time_off_calendar_dashboard"
string="Time Off Request"
form_view_id="hr_leave_view_form_dashboard_new_time_off"
event_open_popup="true"
date_start="date_from"
date_stop="date_to"
quick_create="0"
show_date_picker="0"
show_unusual_days="True"
hide_time="True"
mode="year">
<field name="display_name" string=""/>
<field name="holiday_status_id" filters="1" invisible="1" color="color"/>
<field name="state" invisible="1"/>
<field name="is_hatched" invisible="1" />
<field name="is_striked" invisible="1"/>
</calendar>`,
context: user.context
});
await clickDate("2024-01-09");
await click(".o_cw_popover_link");
await waitFor("button:contains(Delete Time Off)");
await click(".btn-close");
await clickDate("2024-01-10");
await click(".o_cw_popover_link");
await waitFor("button:contains(Cancel Time Off)");
})

View file

@ -0,0 +1,168 @@
import { describe, test, expect, beforeEach } from "@odoo/hoot";
import { click, edit, queryAll, queryOne } from "@odoo/hoot-dom";
import { mountView, onRpc, selectFieldDropdownItem } from "@web/../tests/web_test_helpers";
import { defineHrHolidaysModels } from "@hr_holidays/../tests/hr_holidays_test_helpers";
import { HrLeave } from "@hr_holidays/../tests/mock_server/mock_models/hr_leave";
import { mockTimeZone } from "@odoo/hoot-mock";
describe.current.tags("desktop");
defineHrHolidaysModels();
beforeEach(() => {
mockTimeZone("Europe/Brussels");
HrLeave._records = [
{
id: 12,
employee_id: 100,
department_id: 11,
date_from: "2016-10-20 09:00:00",
date_to: "2016-10-25 18:00:00",
holiday_status_id: 55,
state: "validate",
number_of_days: 5,
number_of_hours: 40,
leave_type_request_unit: "day",
},
{
id: 13,
employee_id: 100,
department_id: 11,
date_from: "2016-10-02 09:00:00",
date_to: "2016-10-02 18:00:00",
holiday_status_id: 55,
state: "validate",
number_of_days: 1,
number_of_hours: 8,
leave_type_request_unit: "day",
},
{
id: 14,
employee_id: 200,
department_id: 11,
date_from: "2016-10-15 09:00:00",
date_to: "2016-10-21 18:00:00",
holiday_status_id: 55,
state: "confirm",
number_of_days: 8,
number_of_hours: 64,
leave_type_request_unit: "day",
},
{
id: 15,
employee_id: 200,
department_id: 11,
date_from: "2016-10-05 10:00:00",
date_to: "2016-10-05 11:00:00",
holiday_status_id: 65,
state: "validate",
number_of_days: 0,
number_of_hours: 1,
leave_type_request_unit: "hour",
},
{
id: 16,
employee_id: 200,
department_id: 11,
date_from: "2016-09-11 09:00:00",
date_to: "2016-09-12 18:00:00",
holiday_status_id: 55,
state: "validate",
number_of_days: 2,
number_of_hours: 16,
leave_type_request_unit: "day",
},
{
id: 17,
employee_id: 100,
department_id: 11,
date_from: "2016-10-16 09:00:00",
date_to: "2016-10-16 11:00:00",
holiday_status_id: 65,
state: "validate",
number_of_days: 0,
number_of_hours: 2,
leave_type_request_unit: "hour",
},
];
});
test("leave stats render correctly", async () => {
await mountView({
type: "form",
resModel: "hr.leave",
resId: 14,
arch: `
<form string="Leave">
<field name="employee_id"/>
<field name="department_id"/>
<field name="date_from"/>
<field name="date_to"/>
<widget name="hr_leave_stats"/>
</form>`,
});
const individualLeaves = queryOne(".o_leave_stats #o_leave_stats_employee");
const DepartmentLeaves = queryOne(".o_leave_stats #o_leave_stats_department");
// Displays leaves with the correct unit
expect(queryAll("span:contains(Legal Leave)", { root: individualLeaves })).toHaveCount(1);
expect(queryAll("span:contains(2 days)", { root: individualLeaves })).toHaveCount(1);
expect(queryAll("span:contains(Unpaid Leave)", { root: individualLeaves })).toHaveCount(1);
expect(queryAll("span:contains(01:00 hours)", { root: individualLeaves })).toHaveCount(1);
// Displays all leaves for that department
expect(queryAll("span:contains(Richard)", { root: DepartmentLeaves })).toHaveCount(2);
expect(queryAll("span:contains(10/16/2016)", { root: DepartmentLeaves })).toHaveCount(1);
expect(queryAll("span:contains(02:00 hours)", { root: DepartmentLeaves })).toHaveCount(1);
expect(queryAll("span:contains(10/20/2016)", { root: DepartmentLeaves })).toHaveCount(1);
expect(queryAll("span:contains(10/25/2016)", { root: DepartmentLeaves })).toHaveCount(1);
expect(
queryAll("div.o_horizontal_separator:contains(R&D)", { root: DepartmentLeaves })
).toHaveCount(1);
});
test("leave stats reload when employee/department changes", async () => {
onRpc(({ args, kwargs, method, model }) => {
if (
model === "hr.leave" &&
method === "search_read" &&
kwargs.domain[0][0] === "department_id"
) {
expect(
kwargs.domain.some(
(x) => JSON.stringify(x) === JSON.stringify(["department_id", "=", 11])
)
).toBe(true);
}
if (
model === "hr.leave" &&
method === "search_read" &&
kwargs.domain[0][0] === "employee_id"
) {
expect(
kwargs.domain.some(
(x) => JSON.stringify(x) === JSON.stringify(["employee_id", "=", 200])
)
).toBe(true);
}
});
await mountView({
type: "form",
resModel: "hr.leave",
arch: `
<form string="Leave">
<field name="employee_id"/>
<field name="department_id"/>
<field name="date_from"/>
<widget name="hr_leave_stats"/>
</form>`,
});
// Set date => shouldn't load data yet (no employee nor department defined)
await click("div[name='date_from'] input");
await edit("2016-10-12 09:00");
// Set employee => should load employee's date
await selectFieldDropdownItem("employee_id", "Jane");
// Set department => should load department's data
await selectFieldDropdownItem("department_id", "R&D");
});

View file

@ -0,0 +1,15 @@
import { fields } from "@web/../tests/web_test_helpers";
import { hrModels } from "@hr/../tests/hr_test_helpers";
export class HrDepartment extends hrModels.HrDepartment {
_name = "hr.department";
id = fields.Integer();
_records = [
{
id: 11,
name: "R&D",
},
];
}

View file

@ -0,0 +1,27 @@
import { hrModels } from "@hr/../tests/hr_test_helpers";
import { fields } from "@web/../tests/web_test_helpers";
export class HrEmployee extends hrModels.HrEmployee {
_name = "hr.employee";
name = fields.Char();
leave_date_to = fields.Date();
user_id = fields.Many2one({ relation: "res.users" });
_get_store_avatar_card_fields() {
return [...super._get_store_avatar_card_fields(), "leave_date_to"];
}
_records = [
{
id: 100,
name: "Richard",
department_id: 11,
},
{
id: 200,
name: "Jane",
department_id: 11,
},
];
}

View file

@ -0,0 +1,36 @@
import { fields, models } from "@web/../tests/web_test_helpers";
export class HrLeave extends models.Model {
_name = "hr.leave";
id = fields.Integer();
employee_id = fields.Many2one({
relation: "hr.employee",
});
user_id = fields.Many2one({
relation: "res.users"
});
department_id = fields.Many2one({
relation: "hr.department",
});
date_from = fields.Datetime();
date_to = fields.Datetime();
holiday_status_id = fields.Many2one({
relation: "hr.leave.type",
});
state = fields.Char();
number_of_days = fields.Integer();
number_of_hours = fields.Integer();
leave_type_request_unit = fields.Selection({
string: "Leave Type Request Unit",
type: "selection",
selection: [
["day", "Day"],
["half_day", "Half Day"],
["hour", "Hours"],
],
});
can_cancel = fields.Boolean();
is_hatched = fields.Boolean();
is_striked = fields.Boolean();
}

View file

@ -0,0 +1,19 @@
import { fields, models } from "@web/../tests/web_test_helpers";
export class HrLeaveType extends models.Model {
_name = "hr.leave.type";
id = fields.Integer();
name = fields.Char();
_records = [
{
id: 55,
name: "Legal Leave",
},
{
id: 65,
name: "Unpaid Leave",
},
];
}

View file

@ -0,0 +1,33 @@
import { hrModels } from "@hr/../tests/hr_test_helpers";
import { fields } from "@web/../tests/web_test_helpers";
import { mailDataHelpers } from "@mail/../tests/mock_server/mail_mock_server";
export class ResPartner extends hrModels.ResPartner {
leave_date_to = fields.Date({ related: false });
compute_im_status(partner) {
/** @type {import("mock_models").ResUsers} */
const ResUsers = this.env["res.users"];
if (partner.main_user_id && ResUsers.browse(partner.main_user_id).leave_date_to) {
if (partner.im_status === "online") {
return "leave_online";
} else if (partner.im_status === "away") {
return "leave_away";
} else {
return "leave_offline";
}
} else {
return super.compute_im_status(partner);
}
}
get _to_store_defaults() {
return [
...super._to_store_defaults,
mailDataHelpers.Store.one(
"main_user_id",
mailDataHelpers.Store.many("employee_ids", "leave_date_to")
),
];
}
}

View file

@ -0,0 +1,6 @@
import { hrModels } from "@hr/../tests/hr_test_helpers";
import { fields } from "@web/../tests/web_test_helpers";
export class ResUsers extends hrModels.ResUsers {
leave_date_to = fields.Date({ related: false });
}

View file

@ -0,0 +1,130 @@
import { describe, test } from "@odoo/hoot";
import {
startServer,
start,
click,
openFormView,
contains,
openDiscuss,
insertText,
} from "@mail/../tests/mail_test_helpers";
import { defineHrHolidaysModels } from "@hr_holidays/../tests/hr_holidays_test_helpers";
import { Command, serverState } from "@web/../tests/web_test_helpers";
import { press } from "@odoo/hoot-dom";
import { mockDate } from "@odoo/hoot-mock";
describe.current.tags("desktop");
defineHrHolidaysModels();
const { DateTime } = luxon;
test("Show 'back on' in avatar card", async () => {
mockDate("2025-04-08 12:00:00");
const pyEnv = await startServer();
const employee = pyEnv["hr.employee"].create({
user_id: serverState.userId,
work_contact_id: serverState.partnerId,
leave_date_to: DateTime.now().plus({ days: 3 }).toISODate(),
});
pyEnv["res.users"].write([serverState.userId], {
employee_ids: [Command.link(employee)],
});
const fakeId = pyEnv["res.fake"].create({ name: "Salutations, voyageur" });
pyEnv["mail.message"].create({
author_id: serverState.partnerId,
body: "not empty",
model: "res.fake",
res_id: fakeId,
subject: "Another Subject",
});
await start();
await openFormView("res.fake", fakeId);
await click(".o-mail-Message-avatar");
await contains(".o_avatar_card span", { text: "Back on Apr 11" });
});
test("Show 'back on' in mention list", async () => {
mockDate("2025-04-08 12:00:00");
const pyEnv = await startServer();
const employee = pyEnv["hr.employee"].create({
user_id: serverState.userId,
leave_date_to: DateTime.now().plus({ days: 3 }).toISODate(),
});
pyEnv["res.users"].write([serverState.userId], {
employee_ids: [Command.link(employee)],
});
const channelId = pyEnv["discuss.channel"].create({
name: "General & good",
channel_member_ids: [Command.create({ partner_id: serverState.partnerId })],
});
await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "@");
await contains(".o-mail-NavigableList-item span", { text: "Back on Apr 11" });
});
test("Show year when 'back on' is on different year than now", async () => {
mockDate("2024-12-20 12:00:00");
const pyEnv = await startServer();
const employee = pyEnv["hr.employee"].create({
user_id: serverState.userId,
work_contact_id: serverState.partnerId,
leave_date_to: DateTime.now().plus({ days: 15 }).toISODate(),
});
pyEnv["res.users"].write([serverState.userId], {
employee_ids: [(6, 0, employee)],
});
const fakeId = pyEnv["res.fake"].create({ name: "Salutations, voyageur" });
pyEnv["mail.message"].create({
author_id: serverState.partnerId,
body: "not empty",
model: "res.fake",
res_id: fakeId,
subject: "Another Subject",
});
await start();
await openFormView("res.fake", fakeId);
await click(".o-mail-Message-avatar");
await contains(".o_avatar_card span", { text: "Back on Jan 4, 2025" });
});
test("Discuss Sidebar shows out of office indication", async () => {
mockDate("2025-04-08 12:00:00");
const pyEnv = await startServer();
pyEnv["hr.employee"].create({
user_id: serverState.userId,
leave_date_to: DateTime.now().plus({ days: 3 }).toISODate(),
});
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [Command.create({ partner_id: serverState.partnerId })],
channel_type: "chat",
});
await start();
await openDiscuss(channelId);
await contains(".o-mail-DiscussSidebarChannel-itemName .text-warning", {
text: "Back on Apr 11",
});
});
test("CTRL+K command shows out of office indication", async () => {
mockDate("2025-04-08 12:00:00");
const pyEnv = await startServer();
pyEnv["hr.employee"].create({
user_id: serverState.userId,
leave_date_to: DateTime.now().plus({ days: 3 }).toISODate(),
});
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [Command.create({ partner_id: serverState.partnerId })],
channel_type: "chat",
});
await start();
await openDiscuss(channelId);
await contains(".o-mail-Discuss[data-active]");
await press(["ctrl", "k"]);
await contains(".o-mail-DiscussCommand .text-warning", {
text: "Back on Apr 11",
});
});

View file

@ -1,114 +0,0 @@
/** @odoo-module **/
import { UPDATE_BUS_PRESENCE_DELAY } from '@bus/im_status_service';
import { start, startServer } from '@mail/../tests/helpers/test_utils';
QUnit.module('hr_holidays', {}, function () {
QUnit.module('components', {}, function () {
QUnit.module('persona_im_status_icon_tests.js');
QUnit.test('on leave & online', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const partnerId = pyEnv['res.partner'].create({ im_status: 'leave_online' });
const mailChannelId = pyEnv['mail.channel'].create({});
pyEnv['mail.message'].create({
author_id: partnerId,
body: 'not empty',
model: 'mail.channel',
res_id: mailChannelId,
});
const { advanceTime, afterNextRender, openDiscuss } = await start({
discuss: {
params: {
default_active_id: mailChannelId,
},
},
hasTimeControl: true,
});
await openDiscuss();
await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY));
assert.hasClass(
document.querySelector('.o_PersonaImStatusIcon_icon'),
'o-online',
"persona IM status icon should have online status rendering"
);
assert.hasClass(
document.querySelector('.o_PersonaImStatusIcon_icon'),
'fa-plane',
"persona IM status icon should have leave status rendering"
);
});
QUnit.test('on leave & away', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const partnerId = pyEnv['res.partner'].create({ im_status: 'leave_away' });
const mailChannelId = pyEnv['mail.channel'].create({});
pyEnv['mail.message'].create({
author_id: partnerId,
body: 'not empty',
model: 'mail.channel',
res_id: mailChannelId,
});
const { advanceTime, afterNextRender, openDiscuss } = await start({
discuss: {
params: {
default_active_id: mailChannelId,
},
},
hasTimeControl: true,
});
await openDiscuss();
await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY));
assert.hasClass(
document.querySelector('.o_PersonaImStatusIcon_icon'),
'o-away',
"persona IM status icon should have away status rendering"
);
assert.hasClass(
document.querySelector('.o_PersonaImStatusIcon_icon'),
'fa-plane',
"persona IM status icon should have leave status rendering"
);
});
QUnit.test('on leave & offline', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const partnerId = pyEnv['res.partner'].create({ im_status: 'leave_offline' });
const mailChannelId = pyEnv['mail.channel'].create({});
pyEnv['mail.message'].create({
author_id: partnerId,
body: 'not empty',
model: 'mail.channel',
res_id: mailChannelId,
});
const { advanceTime, afterNextRender, openDiscuss } = await start({
discuss: {
params: {
default_active_id: mailChannelId,
},
},
hasTimeControl: true,
});
await openDiscuss();
await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY));
assert.hasClass(
document.querySelector('.o_PersonaImStatusIcon_icon'),
'o-offline',
"persona IM status icon should have offline status rendering"
);
assert.hasClass(
document.querySelector('.o_PersonaImStatusIcon_icon'),
'fa-plane',
"persona IM status icon should have leave status rendering"
);
});
});
});

View file

@ -1,103 +0,0 @@
/** @odoo-module **/
import {
start,
startServer,
} from '@mail/../tests/helpers/test_utils';
QUnit.module('mail', {}, function () {
QUnit.module('components', {}, function () {
QUnit.module('thread_icon_tests.js');
QUnit.test('thread icon of a chat when correspondent is on leave & online', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({
im_status: 'leave_online',
name: 'Demo',
});
pyEnv['mail.channel'].create({
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
],
channel_type: 'chat',
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
document.body,
'.o_ThreadIcon_online',
"thread icon should have online status rendering"
);
assert.hasClass(
document.querySelector('.o_ThreadIcon_online'),
'fa-plane',
"thread icon should have leave status rendering"
);
});
QUnit.test('thread icon of a chat when correspondent is on leave & away', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({
im_status: 'leave_away',
name: 'Demo',
});
pyEnv['mail.channel'].create({
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
],
channel_type: 'chat',
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
document.body,
'.o_ThreadIcon_away',
"thread icon should have away status rendering"
);
assert.hasClass(
document.querySelector('.o_ThreadIcon_away'),
'fa-plane',
"thread icon should have leave status rendering"
);
});
QUnit.test('thread icon of a chat when correspondent is on leave & offline', async function (assert) {
assert.expect(2);
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({
im_status: 'leave_offline',
name: 'Demo',
});
pyEnv['mail.channel'].create({
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
],
channel_type: 'chat',
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
document.body,
'.o_ThreadIcon_offline',
"thread icon should have offline status rendering"
);
assert.hasClass(
document.querySelector('.o_ThreadIcon_offline'),
'fa-plane',
"thread icon should have leave status rendering"
);
});
});
});

View file

@ -1,51 +0,0 @@
/** @odoo-module **/
import { start, startServer } from '@mail/../tests/helpers/test_utils';
QUnit.module('hr_holidays', {}, function () {
QUnit.module('components', {}, function () {
QUnit.module('thread_view_tests.js');
QUnit.test('out of office message on direct chat with out of office partner', async function (assert) {
assert.expect(2);
// Returning date of the out of office partner, simulates he'll be back in a month
const returningDate = moment.utc().add(1, 'month');
const pyEnv = await startServer();
// Needed partner & user to allow simulation of message reception
const resPartnerId1 = pyEnv['res.partner'].create({
name: "Foreigner partner",
out_of_office_date_end: returningDate.format("YYYY-MM-DD"),
});
const mailChannelId1 = pyEnv['mail.channel'].create({
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
],
channel_type: 'chat',
});
const { openDiscuss, messaging } = await start({
discuss: {
params: {
default_active_id: `mail.channel_${mailChannelId1}`,
},
},
});
await openDiscuss();
assert.containsOnce(
document.body,
'.o_ThreadView_outOfOffice',
"should have an out of office alert on thread view"
);
const formattedDate = returningDate.toDate().toLocaleDateString(
messaging.locale.language.replace(/_/g, '-'),
{ day: 'numeric', month: 'short' }
);
assert.ok(
document.querySelector('.o_ThreadView_outOfOffice').textContent.includes(formattedDate),
"out of office message should mention the returning date"
);
});
});
});

View file

@ -1,59 +0,0 @@
/** @odoo-module **/
import { click, clickSave, getFixture } from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
let serverData;
let target;
QUnit.module("Fields", (hooks) => {
hooks.beforeEach(() => {
serverData = {
models: {
partner: {
fields: {
product_id: { string: "Product", type: "many2one", relation: "product" },
},
records: [{ id: 1, product_id: false }],
},
product: {
fields: {
name: { string: "Product Name", type: "char" },
},
records: [
{ id: 1, display_name: "a" },
{ id: 2, display_name: "b" },
{ id: 3, display_name: "c" },
],
},
},
};
target = getFixture();
setupViewRegistries();
});
QUnit.module("RadioImageField");
QUnit.test("field is correctly renderered", async function (assert) {
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: '<form><field name="product_id" widget="hr_holidays_radio_image"/></form>',
});
assert.containsOnce(target, ".o_field_widget.o_field_hr_holidays_radio_image");
assert.containsN(target, ".o_radio_input", 3);
assert.containsNone(target, ".o_radio_input:checked");
assert.containsN(target, "img", 3);
await click(target.querySelector("img"));
assert.containsOnce(target, ".o_radio_input:checked");
await clickSave(target);
assert.containsOnce(target, ".o_field_widget.o_field_hr_holidays_radio_image");
assert.containsN(target, ".o_radio_input", 3);
assert.containsN(target, "img", 3);
});
});

View file

@ -1,159 +0,0 @@
/** @odoo-module */
import { selectDropdownItem, editInput, getFixture } from '@web/../tests/helpers/utils';
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
let serverData;
let target;
QUnit.module('leave_stats_widget', (hooks) => {
hooks.beforeEach(() => {
setupViewRegistries();
target = getFixture();
serverData = {
models: {
department: {
fields: {
name: { string: "Name", type: "char" },
},
records: [{id:11, name: "R&D"}],
},
employee: {
fields: {
name: { string: "Name", type: "char" },
department_id: { string: "Department", type: "many2one", relation: 'department' },
},
records: [{
id: 100,
name: "Richard",
department_id: 11,
},{
id: 200,
name: "Jesus",
department_id: 11,
}],
},
'hr.leave.type': {
fields: {
name: { string: "Name", type: "char" }
},
records: [{
id: 55,
name: "Legal Leave",
}]
},
'hr.leave': {
fields: {
employee_id: { string: "Employee", type: "many2one", relation: 'employee' },
department_id: { string: "Department", type: "many2one", relation: 'department' },
date_from: { string: "From", type: "datetime" },
date_to: { string: "To", type: "datetime" },
holiday_status_id: { string: "Leave type", type: "many2one", relation: 'hr.leave.type' },
state: { string: "State", type: "char" },
holiday_type: { string: "Holiday Type", type: "char" },
number_of_days: { string: "State", type: "integer" },
},
records: [{
id: 12,
employee_id: 100,
department_id: 11,
date_from: "2016-10-20 09:00:00",
date_to: "2016-10-25 18:00:00",
holiday_status_id: 55,
state: 'validate',
number_of_days: 5,
holiday_type: 'employee',
},{
id: 13,
employee_id: 100,
department_id: 11,
date_from: "2016-10-02 09:00:00",
date_to: "2016-10-02 18:00:00",
holiday_status_id: 55,
state: 'validate',
number_of_days: 1,
holiday_type: 'employee',
},{
id: 14,
employee_id: 200,
department_id: 11,
date_from: "2016-10-15 09:00:00",
date_to: "2016-10-20 18:00:00",
holiday_status_id: 55,
state: 'validate',
number_of_days: 8,
holiday_type: 'employee',
}]
}
}
};
});
QUnit.test('leave stats renders correctly', async (assert) => {
assert.expect(5);
await makeView({
serverData,
type: "form",
resModel: 'hr.leave',
arch: '<form string="Leave">' +
'<field name="employee_id"/>' +
'<field name="department_id"/>' +
'<field name="date_from"/>' +
'<widget name="hr_leave_stats"/>' +
'</form>',
resId: 12,
mockRPC(route, args) {
if (args.model === 'hr.leave' && args.method === 'search') {
return Promise.resolve(this.data['hr.leave'].records.map(function (record) { return record.id; }));
}
},
});
const $leaveTypeBody = target.querySelector('.o_leave_stats #o_leave_stats_employee');
const $leavesDepartmentBody = target.querySelector('.o_leave_stats #o_leave_stats_department');
assert.containsOnce($leaveTypeBody, 'span:contains(Legal Leave)', "it should have leave type");
assert.containsOnce($leaveTypeBody, 'span:contains(6)', "it should have 6 days");
assert.containsN($leavesDepartmentBody, 'span:contains(Richard)', 2, "it should have 2 leaves for Richard");
assert.containsOnce($leavesDepartmentBody, 'span:contains(Jesus)', "it should have 1 leaves for Jesus");
assert.containsOnce($leavesDepartmentBody, 'div.o_horizontal_separator:contains(R&D)', "it should have R&D title");
});
QUnit.test('leave stats reload when employee/department changes', async (assert) => {
assert.expect(3);
await makeView({
serverData,
type: "form",
resModel: 'hr.leave',
mode: 'edit',
arch: '<form string="Leave">' +
'<field name="employee_id"/>' +
'<field name="department_id"/>' +
'<field name="date_from"/>' +
'<widget name="hr_leave_stats"/>' +
'</form>',
mockRPC(route, args) {
if (args.model === 'hr.leave' && args.method === 'search_read') {
assert.ok(_.some(args.kwargs.domain, _.matcher(['department_id', '=', 11])), "It should load department's leaves data");
}
if (args.model === 'hr.leave' && args.method === 'read_group') {
assert.ok(_.some(args.kwargs.domain, _.matcher(['employee_id', '=', 200])), "It should load employee's leaves data");
}
},
});
// Set date => shouldn't load data yet (no employee nor department defined)
await editInput(
target,
"div[name='date_from'] .o_datepicker_input",
"2016-10-12 09:00:00"
);
// Set employee => should load employee's date
await selectDropdownItem(target, "employee_id", "Jesus");
// Set department => should load department's data
await selectDropdownItem(target, "department_id", "R&D");
});
});

View file

@ -0,0 +1,44 @@
import { defineMailModels } from "@mail/../tests/mail_test_helpers";
import { expect, test } from "@odoo/hoot";
import { clickSave, contains, defineModels, fields, models, mountView } from "@web/../tests/web_test_helpers";
class Partner extends models.Model {
_records = [{ id: 1, product_id: false }];
product_id = fields.Many2one({ relation: "product" });
}
class Product extends models.Model {
_records = [
{ id: 1, name: "a" },
{ id: 2, name: "b" },
{ id: 3, name: "c" },
];
name = fields.Char();
}
defineModels([Partner, Product]);
defineMailModels();
test(`field is correctly renderered`, async () => {
await mountView({
type: "form",
resModel: "partner",
resId: 1,
arch: `<form><field name="product_id" widget="hr_holidays_radio_image"/></form>`,
});
expect(`.o_field_widget.o_field_hr_holidays_radio_image`).toHaveCount(1);
expect(`.o_radio_input`).toHaveCount(3);
expect(`.o_radio_input:checked`).toHaveCount(0);
expect(`img`).toHaveCount(3);
await contains(`img:eq(0)`).click();
expect(`.o_radio_input:checked`).toHaveCount(1);
await clickSave();
expect(`.o_field_widget.o_field_hr_holidays_radio_image`).toHaveCount(1);
expect(`.o_radio_input`).toHaveCount(3);
expect(`.o_radio_input:checked`).toHaveCount(1);
expect(`img`).toHaveCount(3);
});

View file

@ -0,0 +1,64 @@
import { describe, test } from "@odoo/hoot";
import { Command, serverState } from "@web/../tests/web_test_helpers";
import { startServer, start, openDiscuss, contains } from "@mail/../tests/mail_test_helpers";
import { defineHrHolidaysModels } from "@hr_holidays/../tests/hr_holidays_test_helpers";
describe.current.tags("desktop");
defineHrHolidaysModels();
test("thread icon of a chat when correspondent is on leave & online", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ im_status: "online", name: "Demo" });
pyEnv["res.users"].create({ partner_id: partnerId, leave_date_to: "2023-01-01" });
pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ partner_id: serverState.partnerId }),
Command.create({ partner_id: partnerId }),
],
channel_type: "chat",
});
await start();
await openDiscuss();
await contains(".o-mail-DiscussSidebarChannel", {
contains: [".o-mail-ThreadIcon .fa-plane[title='On Leave (Online)']"],
text: "Demo",
});
});
test("thread icon of a chat when correspondent is on leave & away", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ im_status: "away", name: "Demo" });
pyEnv["res.users"].create({ partner_id: partnerId, leave_date_to: "2023-01-01" });
pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ partner_id: serverState.partnerId }),
Command.create({ partner_id: partnerId }),
],
channel_type: "chat",
});
await start();
await openDiscuss();
await contains(".o-mail-DiscussSidebarChannel", {
contains: [".o-mail-ThreadIcon .fa-plane[title='On Leave (Idle)']"],
text: "Demo",
});
});
test("thread icon of a chat when correspondent is on leave & offline", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ im_status: "offline", name: "Demo" });
pyEnv["res.users"].create({ partner_id: partnerId, leave_date_to: "2023-01-01" });
pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ partner_id: serverState.partnerId }),
Command.create({ partner_id: partnerId }),
],
channel_type: "chat",
});
await start();
await openDiscuss();
await contains(".o-mail-DiscussSidebarChannel", {
contains: [".o-mail-ThreadIcon .fa-plane[title='On Leave']"],
text: "Demo",
});
});

View file

@ -0,0 +1,30 @@
import { describe, test } from "@odoo/hoot";
import { Command, serverState } from "@web/../tests/web_test_helpers";
import { startServer, start, openDiscuss, contains } from "@mail/../tests/mail_test_helpers";
import { defineHrHolidaysModels } from "@hr_holidays/../tests/hr_holidays_test_helpers";
describe.current.tags("desktop");
defineHrHolidaysModels();
test("out of office message on direct chat with out of office partner", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo", im_status: "online" });
const userId = pyEnv["res.users"].create({ partner_id: partnerId });
const employee = pyEnv["hr.employee"].create({
user_id: userId,
leave_date_to: "2023-01-01",
});
pyEnv["res.users"].write([userId], {
employee_ids: [Command.link(employee)],
});
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ partner_id: serverState.partnerId }),
Command.create({ partner_id: partnerId }),
],
channel_type: "chat",
});
await start();
await openDiscuss(channelId);
await contains(".alert", { text: "Back on Jan 1, 2023" });
});

View file

@ -0,0 +1,108 @@
import { describe, expect, test } from "@odoo/hoot";
import { mockDate } from "@odoo/hoot-mock";
import { defineModels, fields, getService, models, mountWebClient, onRpc } from "@web/../tests/web_test_helpers";
import { defineHrHolidaysModels } from "./hr_holidays_test_helpers";
describe.current.tags("desktop");
class HrLeave extends models.Model {
_views = {
calendar: `
<calendar js_class="time_off_calendar_dashboard"
string="Time Off Request"
form_view_id="%(hr_holidays.hr_leave_view_form_dashboard_new_time_off)d"
event_open_popup="1"
date_start="date_from"
date_stop="date_to"
quick_create="0"
show_unusual_days="1"
color="color"
hide_time="1"
mode="year"
>
<field name="name"/>
<field name="holiday_status_id" filters="1" invisible="1" color="color"/>
<field name="state" invisible="1"/>
</calendar>
`,
};
color = fields.Integer({ related: "holiday_status_id.color" });
date_from = fields.Datetime();
date_to = fields.Datetime();
department_id = fields.Many2one({ relation: "hr.department" });
employee_id = fields.Many2one({ relation: "hr.employee" });
holiday_status_id = fields.Many2one({ relation: "hr.leave.type" });
holiday_type = fields.Char();
name = fields.Char();
number_of_days = fields.Integer();
state = fields.Char();
}
class HrLeaveType extends models.Model {
name = fields.Char();
color = fields.Integer();
}
defineHrHolidaysModels();
defineModels([HrLeave, HrLeaveType]);
onRpc("hr.employee", "get_time_off_dashboard_data", () => (
{has_accrual_allocation: true, allocation_data: {}, allocation_request_amount: 0}
));
onRpc("hr.employee", "get_mandatory_days", () => ({}));
onRpc("hr.employee", "get_special_days_data", () => ({ mandatoryDays: [], bankHolidays: [] }));
onRpc("hr.leave", "get_unusual_days", () => ({}));
onRpc("hr.leave", "has_access", () => true);
onRpc("hr.leave.type", "has_accrual_allocation", () => true);
test(`test employee is passed to get_time_off_dashboard_data`, async () => {
onRpc("hr.employee", "get_time_off_dashboard_data", ({ kwargs }) => {
expect.step(kwargs.context.employee_id);
});
await mountWebClient();
await getService("action").doAction({
id: 1,
res_model: "hr.leave",
type: "ir.actions.act_window",
views: [[false, "calendar"]],
context: { employee_id: [200] },
domain: [["employee_id", "in", [200]]],
});
expect.verifySteps([200]);
});
test(`test basic rendering`, async () => {
mockDate("2025-03-18 08:00:00");
onRpc("hr.employee", "get_mandatory_days", () => ({ "2025-03-17": 5 }));
onRpc("hr.employee", "get_special_days_data", () => ({
mandatoryDays: [
{
id: -2,
colorIndex: 5,
end: "2025-03-17T23:59:59.999999",
endType: "datetime",
isAllDay: true,
start: "2025-03-17T00:00:00",
startType: "datetime",
title: "Test Mandatory Day",
},
],
bankHolidays: [],
}));
await mountWebClient();
await getService("action").doAction({
id: 1,
res_model: "hr.leave",
type: "ir.actions.act_window",
views: [[false, "calendar"]],
context: { employee_id: [200] },
domain: [["employee_id", "in", [200]]],
});
expect(`.o_calendar_filter:contains("Legend")`).toHaveCount(1);
expect(`.o_calendar_filter:contains("To Approve")`).toHaveCount(1);
expect(`.o_calendar_filter:contains("Mar 17, 2025 : Test Mandatory Day")`).toHaveCount(1);
expect(`.fc-day.hr_mandatory_day_5[data-date="2025-03-17"]`).toHaveCount(1);
});

View file

@ -0,0 +1,81 @@
import { registry } from '@web/core/registry';
import { stepUtils } from "@web_tour/tour_utils";
const today = luxon.DateTime.now();
const pastDateFrom = today.minus({ days: 3 }).toFormat("MM/dd/yyyy");
const pastDateTo = today.minus({ days: 2 }).toFormat("MM/dd/yyyy");
const futureDateTo = today.plus({ days: 2 }).toFormat("MM/dd/yyyy");
const warningText = "The allocated days cannot be used, because the allocation is set to finish in the past.";
registry.category("web_tour.tours").add("time_off_allocation_warning_tour", {
url: "/odoo",
steps: () => [
stepUtils.showAppsMenuItem(),
{
content: "Click Time Off",
trigger: ".o_app[data-menu-xmlid='hr_holidays.menu_hr_holidays_root']",
run: "click",
},
{
content: "Open Management menu",
trigger: ".o-dropdown[data-menu-xmlid='hr_holidays.menu_hr_holidays_management']",
run: "click",
},
{
content: "Go to Allocations",
trigger: ".o-dropdown-item[data-menu-xmlid='hr_holidays.hr_holidays_menu_manager_approve_allocations']",
run: "click",
},
{
content: "Create a new allocation",
trigger: ".o-kanban-button-new",
run: "click",
},
{
content: "Click to select a leave type",
trigger: ".o_field_widget[name='holiday_status_id'] input",
run: "click",
},
{
trigger: ".o-autocomplete--dropdown-menu > li > a[id=holiday_status_id_0_0_0]",
run: "click",
},
{
content: "Open the start date picker",
trigger: ".o_field_widget[name='date_from'] button",
// Past date to trigger the warning
run: "click",
},
{
content: "Edit the start date picker",
trigger: ".o_field_widget[name='date_from'] input",
// Past date to trigger the warning
run: `click && edit ${pastDateFrom}`,
},
{
content: "Edit the end date picker",
trigger: ".o_field_widget[name='date_to'] input",
// Past date to trigger the warning
run: `click && edit ${pastDateTo} && click body`,
},
{
content: "Error regarding allocation to be visible",
trigger: `.o_cell:has(.o_row[name='validity']) + div span:contains(${warningText})`,
},
{
content: "Open the end date picker",
trigger: ".o_field_widget[name='date_to'] button",
run: "click",
},
{
content: "Edit the end date picker",
trigger: ".o_field_widget[name='date_to'] input",
run: `click && edit ${futureDateTo} && click body`,
},
{
content: "Error regarding allocation to be visible",
trigger: `.o_cell:has(.o_row[name='validity']) + div:not(:has(span:not(:contains(${warningText}))))`,
},
...stepUtils.saveForm(),
],
});

View file

@ -0,0 +1,84 @@
import { registry } from "@web/core/registry";
import { stepUtils } from "@web_tour/tour_utils";
registry.category("web_tour.tours").add("time_off_card_tour", {
url: "/odoo",
steps: () => [
stepUtils.showAppsMenuItem(),
{
content: "Open Time Off app",
trigger: '.o_app[data-menu-xmlid="hr_holidays.menu_hr_holidays_root"]',
run: "click",
},
{
content: "Click on the Time Off name",
trigger: '.o_timeoff_name:not(:contains("Pending Requests"))',
run: "click",
},
{
content: "Ensure the list view for Time Off requests is displayed",
trigger: ".o_list_view",
},
{
content: "Navigate back to the previous view",
trigger: ".o_back_button",
run: "click",
},
{
content: "Click on the time off card to open the detailed popover.",
trigger: "span.o_timeoff_details",
run: "click",
},
{
content: "Verify that the popover is displayed after clicking on the time off details.",
trigger: ".o_popover",
},
{
content: "Click on the link containing 'Allocated'.",
trigger: ".o_popover .btn-link:contains('Allocated')",
run: "click",
},
{
content: "Ensure the list view for Time Off requests is displayed",
trigger: ".o_list_view",
},
{
content: "Navigate back to the previous view",
trigger: ".o_back_button",
run: "click",
},
{
content: "Click on the time off card to open the detailed popover.",
trigger: "span.o_timeoff_details",
run: "click",
},
{
content: "Click on the link containing 'Approved'",
trigger: ".o_popover .btn-link:contains('Approved')",
run: "click",
},
{
content: "Ensure the list view for Time Off requests is displayed",
trigger: ".o_list_view",
},
{
content: "Navigate back to the previous view",
trigger: ".o_back_button",
run: "click",
},
{
content: "Click on the time off card to open the detailed popover.",
trigger: "span.o_timeoff_details",
run: "click",
},
{
content: "Click on the link containing 'Planned'",
trigger: ".o_popover .btn-link:contains('Planned')",
run: "click",
},
{
content: "Ensure the list view for Time Off requests is displayed",
trigger: ".o_list_view",
},
],
});

View file

@ -0,0 +1,35 @@
import { registry } from "@web/core/registry";
import { stepUtils } from "@web_tour/tour_utils";
registry.category("web_tour.tours").add("time_off_graph_view_tour", {
url: "/odoo",
steps: () => [
stepUtils.showAppsMenuItem(),
{
content: "Open Time Off app",
trigger: ".o_app[data-menu-xmlid='hr_holidays.menu_hr_holidays_root']",
run: "click",
},
{
trigger: ".fc-daygrid-day-top",
},
{
content: "Open reporting menu",
trigger: ".o-dropdown[data-menu-xmlid='hr_holidays.menu_hr_holidays_report']",
run: "click",
},
{
content: "Open reporting by type",
trigger: ".o-dropdown-item[data-menu-xmlid='hr_holidays.menu_hr_holidays_summary_all']",
run: "click",
},
{
trigger: ".o_graph_canvas_container",
},
{
content: "Open bar chart view",
trigger: "button[data-mode='bar']",
run: "click",
},
],
});

View file

@ -1,47 +1,24 @@
odoo.define('hr_holidays.tour_time_off_request_calendar_view', function (require) {
'use strict';
import { registry } from "@web/core/registry";
import { stepUtils } from "@web_tour/tour_utils";
var tour = require('web_tour.tour');
tour.register('time_off_request_calendar_view', {
test: true,
url: '/web',
},
[
tour.stepUtils.showAppsMenuItem(),
{
content: "Open Time Off app",
trigger: '.o_app[data-menu-xmlid="hr_holidays.menu_hr_holidays_root"]',
},
{
content: "Click on the first Thursday of the year",
trigger: '.fc-day-top.fc-thu',
run: () => {
const el = document.querySelector('.fc-day-top.fc-thu').firstChild;
el.scrollIntoView();
const fromPosition = el.getBoundingClientRect();
fromPosition.x += el.offsetWidth / 2;
fromPosition.y += el.offsetHeight / 2;
el.dispatchEvent(new MouseEvent("mousedown", {
bubbles: true,
which: 1,
button: 0,
clientX: fromPosition.x,
clientY: fromPosition.y}));
el.dispatchEvent(new MouseEvent("mouseup", {
bubbles: true,
which: 1,
button: 0,
clientX: fromPosition.x,
clientY: fromPosition.y }));
}
},
{
content: "Save the leave",
trigger: '.btn:contains("Save")',
run: 'click',
}
]);
registry.category("web_tour.tours").add("time_off_request_calendar_view", {
url: "/odoo",
steps: () => [
stepUtils.showAppsMenuItem(),
{
content: "Open Time Off app",
trigger: '.o_app[data-menu-xmlid="hr_holidays.menu_hr_holidays_root"]',
run: "click",
},
{
content: "Click on the first Thursday of the year",
trigger: ".fc-daygrid-day.fc-day-thu:not(.fc-day-disabled)",
run: "click",
},
{
content: "Save the leave",
trigger: '.o_form_button_save',
run: "click",
},
],
});