19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:29:53 +01:00
parent 6e54c1af6c
commit 3ca647e428
1087 changed files with 132065 additions and 108499 deletions

View file

@ -0,0 +1,19 @@
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
export function isTabActive(tabText) {
return [
{
content: "Check if the active tab contains the text" + tabText,
trigger: `.pos-leftheader span.text-bg-info:contains(${tabText})`,
},
];
}
export function closePrintingWarning() {
return [
{
...Dialog.confirm(),
content: "acknowledge printing error ( because we don't have printer in the test. )",
},
];
}

View file

@ -0,0 +1,175 @@
/* global posmodel */
const getData = ({ lineProductName, productName, partnerName } = {}) => {
const order = posmodel.models["pos.order"].find((o) => o.pos_reference.includes("device_sync"));
let partner = null;
if (partnerName) {
partner = posmodel.models["res.partner"].find((p) => p.name === partnerName);
}
let line = null;
if (lineProductName) {
line = order.lines.find((l) => l.product_id.display_name === lineProductName);
}
let product = null;
if (productName) {
product = posmodel.models["product.product"].find((p) => p.display_name === productName);
}
return { order, line, product, partner };
};
const notify = async () => {
const orm = posmodel.env.services.orm;
await orm.call("pos.config", "notify_synchronisation", [
posmodel.config.id,
posmodel.session.id,
999,
]);
};
const getLineData = (product, order, quantity) => ({
name: product.display_name,
order_id: order.id,
product_id: product.id,
price_unit: product.lst_price,
price_subtotal: product.lst_price * quantity,
price_subtotal_incl: product.lst_price * quantity,
discount: 0,
qty: quantity,
});
// In the point-of-sale code, we consider that synchronization is necessary
// when the write_date of the local order is smaller than that of the server.
// To prevent the PoS from ignoring our synchronization.
const writeOnOrder = async (order, data) => {
const sec = new Date(order.write_date).getMilliseconds() + 1010;
const timeout = Math.ceil(sec - new Date().getMilliseconds(), 0);
await new Promise((res) => setTimeout(res, timeout));
const orm = posmodel.env.services.orm;
await orm.write("pos.order", [order.id], data);
await notify();
};
/**
* @param {string} productName
* @param {number} quantity
* @returns StepSchema[]
*/
export function createNewLine(productName, quantity) {
return [
{
trigger: "body",
run: async () => {
const { order, product } = getData({ productName });
await writeOnOrder(order, {
lines: [[0, 0, getLineData(product, order, quantity)]],
});
},
},
];
}
/**
* @param {string} productName
* @param {number} quantity
* @returns StepSchema[]
*/
export function changeLineQuantity(productName, quantity) {
return [
{
trigger: "body",
run: async () => {
const { order, line } = getData({ lineProductName: productName });
await writeOnOrder(order, {
lines: [[1, line.id, getLineData(line.product_id, order, quantity)]],
});
},
},
];
}
/**
* @param {string} partnerName
* @returns StepSchema[]
*/
export function changePartner(partnerName) {
return [
{
trigger: "body",
run: async () => {
const { order, partner } = getData({ partnerName });
await writeOnOrder(order, {
partner_id: partner.id,
});
},
},
];
}
export function markOrderAsPaid() {
return [
{
trigger: "body",
run: async () => {
const { order } = getData({});
await writeOnOrder(order, {
state: "paid",
amount_paid: order.amount_total,
amount_return: 0,
amount_tax: 0,
amount_total: 0,
});
},
},
];
}
export function createNewOrderOnTable(tableName, productTuple) {
return [
{
trigger: "body",
run: async () => {
const orm = posmodel.env.services.orm;
const prices = {
amount_paid: 0,
amount_return: 0,
amount_tax: 0,
amount_total: 0,
};
const lines = productTuple.map(([productName, quantity]) => {
const product = posmodel.models["product.product"].find(
(p) => p.display_name === productName
);
const lineData = getLineData(product, false, quantity);
prices.amount_paid += lineData.price_subtotal;
prices.amount_return += lineData.price_subtotal;
return [
0,
0,
{
...lineData,
price_subtotal: lineData.price_subtotal,
price_subtotal_incl: lineData.price_subtotal_incl,
},
];
});
const table = posmodel.models["restaurant.table"].find(
(t) => t.table_number === parseInt(tableName)
);
await orm.create("pos.order", [
{
...prices,
pos_reference: `device_sync_${Math.floor(Math.random() * 9999)}`,
session_id: posmodel.session.id,
table_id: table.id,
lines,
},
]);
await notify();
},
},
];
}

View file

@ -0,0 +1,220 @@
import * as Numpad from "@point_of_sale/../tests/generic_helpers/numpad_util";
import { negate } from "@point_of_sale/../tests/generic_helpers/utils";
export function table({ name, withClass = "", withoutClass, run = () => {}, numOfSeats }) {
let trigger = `.floor-map .table${withClass}`;
if (withoutClass) {
trigger += `:not(${withoutClass})`;
}
if (name) {
trigger += `:has(.label:contains("${name}"))`;
}
return {
content: `Check table with attributes: ${JSON.stringify(arguments[0])}`,
trigger,
run: typeof run === "string" ? run : (helpers) => run(helpers, trigger),
};
}
export const clickTable = (name) => table({ name, run: "click" });
export const hasTable = (name) => table({ name });
export const selectedTableIs = (name) => table({ name, withClass: ".selected" });
export const ctrlClickTable = (name) =>
table({
name,
run: (helpers, trigger) => {
helpers
.queryOne(trigger)
.dispatchEvent(new MouseEvent("click", { bubbles: true, ctrlKey: true }));
},
});
export function clickFloor(name) {
return [
{
content: `click '${name}' floor`,
trigger: `.floor-selector .button-floor:contains("${name}")`,
run: "click",
},
];
}
export function hasFloor(name) {
return [
{
content: `has '${name}' floor`,
trigger: `.floor-selector .button-floor:contains("${name}")`,
},
];
}
export function hasNotFloor(name) {
return [
{
content: `has not '${name}' floor`,
trigger: negate(`.floor-selector .button-floor:contains("${name}")`),
},
];
}
export function clickEditButton(button) {
return [
{
content: "add table",
trigger: `.edit-buttons i[aria-label="${button}"]`,
run: "click",
},
];
}
export function clickSaveEditButton() {
return [
{
content: "add table",
trigger: '.edit-buttons button:contains("Save")',
run: "click",
},
{
trigger: negate(".edit-buttons button:contains('Save')"),
},
];
}
export function clickTableSelectorButton() {
return [
{
content: "click on table selector button",
trigger: ".floor-screen .right-buttons button i.fa-hashtag",
run: "click",
},
];
}
export function goTo(name) {
return [
...clickTableSelectorButton(),
...Numpad.enterValue(name),
{
trigger: ".floor-screen .right-buttons .jump-button",
run: "click",
},
];
}
export function selectedFloorIs(name) {
return [
{
content: `selected floor is '${name}'`,
trigger: `.button-floor.active:contains("${name}")`,
},
];
}
export function orderCountSyncedInTableIs(table, count) {
if (count === 0 || count === "0") {
return [
{
trigger: `.floor-map .table:has(.label:contains("${table}")):not(:has(.order-count))`,
},
];
}
return [
{
trigger: `.floor-map .table:has(.label:contains("${table}")):has(.order-count:contains("${count}"))`,
},
];
}
export function isShown() {
return [
{
trigger: ".floor-map",
},
];
}
export function linkTables(child, parent) {
async function drag_multiple_and_then_drop(helpers, ...drags) {
const dragEffectDelay = async () => {
console.log(helpers.delay);
await new Promise((resolve) => requestAnimationFrame(resolve));
await new Promise((resolve) => setTimeout(resolve, helpers.delay));
};
const element = helpers.anchor;
const { drag } = odoo.loader.modules.get("@odoo/hoot-dom");
const { drop, moveTo } = await drag(element);
await dragEffectDelay();
await helpers.hover(element, {
position: {
top: 20,
left: 20,
},
relative: true,
});
await dragEffectDelay();
for (const [selector, options] of drags) {
console.log("Selector", selector, options);
const target = await helpers.waitFor(selector, {
visible: true,
timeout: 500,
});
await moveTo(target, options);
await dragEffectDelay();
}
await drop();
await dragEffectDelay();
}
return {
content: `Drag table ${child} onto table ${parent} in order to link them`,
trigger: table({ name: child }).trigger,
async run(helpers) {
helpers.delay = 500;
await drag_multiple_and_then_drop(
helpers,
[
table({ name: parent }).trigger,
{
position: "top",
relative: true,
},
],
[
table({ name: parent }).trigger,
{
position: "center",
relative: true,
},
]
);
},
};
}
export function unlinkTables(child, parent) {
return {
content: `Drag table ${child} away from table ${parent} to unlink them`,
trigger: table({ name: child }).trigger,
async run(helpers) {
await helpers.drag_and_drop(`div.floor-map`, {
position: {
bottom: 0,
},
relative: true,
});
},
};
}
export function isChildTable(child) {
return {
content: `Verify that table ${child} is a child table`,
trigger: table({ name: child }).trigger + ` .info.opacity-25`,
};
}
export function clickNewOrder() {
return { trigger: ".new-order", run: "click" };
}
export function addFloor(floorName) {
return [
{
trigger: ".floor-selector button i[aria-label='Add Floor']",
run: "click",
},
{
trigger: ".modal-body textarea",
run: `edit ${floorName}`,
},
{
trigger: ".modal-footer button.btn-primary",
run: "click",
},
...selectedFloorIs(floorName),
];
}

View file

@ -0,0 +1,145 @@
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
import * as TextInputPopup from "@point_of_sale/../tests/generic_helpers/text_input_popup_util";
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
export function clickOrderButton() {
return [
{
content: "click order button",
trigger: ".actionpad .submit-order",
run: "click",
},
];
}
export function orderlinesHaveNoChange() {
return Order.doesNotHaveLine({ withClass: ".has-change" });
}
export function orderlineIsToOrder(name) {
return Order.hasLine({
productName: name,
withClass: ".orderline.has-change",
});
}
export function guestNumberIs(num) {
return [
...ProductScreen.clickControlButtonMore(),
{
content: `guest number is ${num}`,
trigger: ProductScreen.controlButtonTrigger("Guests") + `:contains(${num})`,
},
];
}
export function OrderButtonNotContain(data) {
const steps = [
{
isActive: ["desktop"],
content: "check order button not contain data",
trigger: `.product-screen .submit-order:not(:contains("${data}"))`,
run: function () {}, // it's a check
},
];
return steps;
}
export function clickCourseButton() {
return [
{
content: "click course button",
trigger: `.course-btn`,
run: "click",
},
];
}
export function selectCourseLine(name) {
return [
{
content: `select course ${name}`,
trigger: `.order-course-name:contains(${name})`,
run: "click",
},
];
}
export function fireCourseButton() {
return [
{
content: "fire course button",
trigger: `.actionpad .fire-btn`,
run: "click",
},
];
}
export function fireCourseButtonHighlighted(courseName) {
return [
{
content: "fire course button highlighted",
trigger: `.actionpad .fire-btn.btn-primary:contains('Fire ${courseName}')`,
},
];
}
export function payButtonNotHighlighted() {
return [
{
content: "pay button not highlighted",
trigger:
".actionpad .pay-order-button:not('.highlight'):not('.btn-primary'):contains('Payment')",
},
];
}
export function setTab(name) {
return [
{
content: `set tab to ${name}`,
trigger: `.product-screen .new-tab`,
run: "click",
},
TextInputPopup.inputText(name),
Dialog.confirm(),
];
}
export function releaseTable() {
return [
{
content: "release table",
trigger: ".product-screen .leftpane .unbook-table",
run: "click",
},
];
}
export function addCourse() {
return {
content: `click Course button`,
trigger: ProductScreen.controlButtonTrigger("Course"),
run: "click",
};
}
export function transferCourseTo(destCourse) {
return [
...ProductScreen.clickControlButton("Transfer course"),
{
content: `click ${destCourse} from available courses`,
trigger: `.modal-body button:contains(${destCourse})`,
run: "click",
},
];
}
export function discardOrderWarningDialog() {
return [
{
trigger: `.modal-dialog:contains("It seems that the order has not been sent. Would you like to send it to preparation?")`,
},
Dialog.discard(),
];
}
export function confirmOrderWarningDialog() {
return [
{
trigger: `.modal-dialog:contains("It seems that the order has not been sent. Would you like to send it to preparation?")`,
},
Dialog.confirm(),
];
}

View file

@ -0,0 +1,39 @@
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
export function clickOrderline(productName) {
return Order.hasLine({ productName, run: "click" });
}
export function clickBack() {
return [
{
content: "click back button",
trigger: `.splitbill-screen .button.back`,
run: "click",
},
];
}
export function clickButton(name) {
return [
{
content: `click '${name}' button`,
trigger: `.splitbill-screen .pay-button button:contains("${name}")`,
run: "click",
},
];
}
export function orderlineHas(name, totalQuantity, splitQuantity) {
return Order.hasLine({
productName: name,
quantity: splitQuantity != 0 ? `${splitQuantity} / ${totalQuantity}` : totalQuantity,
});
}
export function subtotalIs(amount) {
return [
{
content: `total amount of split is '${amount}'`,
trigger: `.splitbill-screen .order-info .subtotal:contains("${amount}")`,
run: "click",
},
];
}

View file

@ -0,0 +1,53 @@
export function clickPercentTip(percent) {
return [
{
trigger: `.tip-screen .percentage:contains("${percent}")`,
run: "click",
},
];
}
export function setCustomTip(amount) {
return [
{
trigger: `.tip-screen .custom-amount-form input`,
run: `edit ${amount}`,
},
];
}
export function clickSettle() {
return [
{
trigger: `.button.highlight.next`,
run: "click",
},
];
}
export function isShown() {
return [
{
trigger: ".pos .tip-screen",
},
];
}
export function totalAmountIs(amount) {
return [
{
trigger: `.tip-screen .total-amount:contains("${amount}")`,
},
];
}
export function percentAmountIs(percent, amount) {
return [
{
trigger: `.tip-screen .percentage:contains("${percent}") ~ .amount:contains("${amount}")`,
},
];
}
export function inputAmountIs(amount) {
return [
{
trigger: `.tip-screen .custom-amount-form input[data-amount="${amount}"]`,
},
];
}