Initial commit: Sale packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:49 +02:00
commit 14e3d26998
6469 changed files with 2479670 additions and 0 deletions

View file

@ -0,0 +1,34 @@
odoo.define('point_of_sale.test_env', async function (require) {
'use strict';
/**
* Many components in PoS are dependent on the PosGlobalState instance (pos).
* Therefore, for unit tests that require pos in the Components' env, we
* prepared here a test env maker (makePosTestEnv) based on
* makeTestEnvironment of web.
*/
const makeTestEnvironment = require('web.test_env');
const env = require('point_of_sale.env');
const { PosGlobalState } = require('point_of_sale.models');
const cleanup = require("@web/../tests/helpers/cleanup");
// We override this method in the pos unit tests to prevent the unnecessary error in the web tests.
cleanup.registerCleanup = () => {}
await env.session.is_bound;
const pos = PosGlobalState.create({ env });
await pos.load_server_data();
/**
* @param {Object} env default env
* @param {Function} providedRPC mock rpc
* @param {Function} providedDoAction mock do_action
*/
function makePosTestEnv(env = {}, providedRPC = null, providedDoAction = null) {
env = Object.assign(env, { pos });
return makeTestEnvironment(env, providedRPC);
}
return makePosTestEnv;
});

View file

@ -0,0 +1,414 @@
odoo.define('point_of_sale.tests.ComponentRegistry', function(require) {
'use strict';
const Registries = require('point_of_sale.Registries');
QUnit.module('unit tests for ComponentRegistry', {
before() {},
});
QUnit.test('basic extend', async function(assert) {
assert.expect(5);
class A {
constructor() {
assert.step('A');
}
}
Registries.Component.add(A);
let A1 = x =>
class extends x {
constructor() {
super();
assert.step('A1');
}
};
Registries.Component.extend(A, A1);
Registries.Component.freeze();
const RegA = Registries.Component.get(A);
let a = new RegA();
assert.verifySteps(['A', 'A1']);
assert.ok(a instanceof RegA);
assert.ok(RegA.name === 'A');
});
QUnit.test('addByExtending', async function(assert) {
assert.expect(8);
class A {
constructor() {
assert.step('A');
}
}
Registries.Component.add(A);
let B = x =>
class extends x {
constructor() {
super();
assert.step('B');
}
};
Registries.Component.addByExtending(B, A);
let A1 = x =>
class extends x {
constructor() {
super();
assert.step('A1');
}
};
Registries.Component.extend(A, A1);
let A2 = x =>
class extends x {
constructor() {
super();
assert.step('A2');
}
};
Registries.Component.extend(A, A2);
Registries.Component.freeze();
const RegA = Registries.Component.get(A);
const RegB = Registries.Component.get(B);
let b = new RegB();
assert.verifySteps(['A', 'A1', 'A2', 'B']);
assert.ok(b instanceof RegA);
assert.ok(b instanceof RegB);
assert.ok(RegB.name === 'B');
});
QUnit.test('extend the one that is added by extending', async function(assert) {
assert.expect(6);
class A {
constructor() {
assert.step('A');
}
}
Registries.Component.add(A);
let B = x =>
class extends x {
constructor() {
super();
assert.step('B');
}
};
Registries.Component.addByExtending(B, A);
let B1 = x =>
class extends x {
constructor() {
super();
assert.step('B1');
}
};
Registries.Component.extend(B, B1);
let B2 = x =>
class extends x {
constructor() {
super();
assert.step('B2');
}
};
Registries.Component.extend(B, B2);
let A1 = x =>
class extends x {
constructor() {
super();
assert.step('A1');
}
};
Registries.Component.extend(A, A1);
Registries.Component.freeze();
const RegB = Registries.Component.get(B);
new RegB();
assert.verifySteps(['A', 'A1', 'B', 'B1', 'B2']);
});
QUnit.test('addByExtending based on added by extending', async function(assert) {
assert.expect(10);
class A {
constructor() {
assert.step('A');
}
}
Registries.Component.add(A);
let B = x =>
class extends x {
constructor() {
super();
assert.step('B');
}
};
Registries.Component.addByExtending(B, A);
let A1 = x =>
class extends x {
constructor() {
super();
assert.step('A1');
}
};
Registries.Component.extend(A, A1);
let C = x =>
class extends x {
constructor() {
super();
assert.step('C');
}
};
Registries.Component.addByExtending(C, B);
let B7 = x =>
class extends x {
constructor() {
super();
assert.step('B7');
}
};
Registries.Component.extend(B, B7);
Registries.Component.freeze();
const RegA = Registries.Component.get(A);
const RegB = Registries.Component.get(B);
const RegC = Registries.Component.get(C);
let c = new RegC();
assert.verifySteps(['A', 'A1', 'B', 'B7', 'C']);
assert.ok(c instanceof RegA);
assert.ok(c instanceof RegB);
assert.ok(c instanceof RegC);
assert.ok(RegC.name === 'C');
});
QUnit.test('deeper inheritance', async function(assert) {
assert.expect(9);
class A {
constructor() {
assert.step('A');
}
}
Registries.Component.add(A);
let B = x =>
class extends x {
constructor() {
super();
assert.step('B');
}
};
Registries.Component.addByExtending(B, A);
let A1 = x =>
class extends x {
constructor() {
super();
assert.step('A1');
}
};
Registries.Component.extend(A, A1);
let C = x =>
class extends x {
constructor() {
super();
assert.step('C');
}
};
Registries.Component.addByExtending(C, B);
let B2 = x =>
class extends x {
constructor() {
super();
assert.step('B2');
}
};
Registries.Component.extend(B, B2);
let B3 = x =>
class extends x {
constructor() {
super();
assert.step('B3');
}
};
Registries.Component.extend(B, B3);
let A9 = x =>
class extends x {
constructor() {
super();
assert.step('A9');
}
};
Registries.Component.extend(A, A9);
let E = x =>
class extends x {
constructor() {
super();
assert.step('E');
}
};
Registries.Component.addByExtending(E, C);
Registries.Component.freeze();
// |A| => A9 -> A1 -> A
// |B| => B3 -> B2 -> B -> |A|
// |C| => C -> |B|
// |E| => E -> |C|
new (Registries.Component.get(E))();
assert.verifySteps(['A', 'A1', 'A9', 'B', 'B2', 'B3', 'C', 'E']);
});
QUnit.test('mixins?', async function(assert) {
assert.expect(12);
class A {
constructor() {
assert.step('A');
}
}
Registries.Component.add(A);
let Mixin = x =>
class extends x {
constructor() {
super();
assert.step('Mixin');
}
mixinMethod() {
return 'mixinMethod';
}
get mixinGetter() {
return 'mixinGetter';
}
};
// use the mixin when declaring B.
let B = x =>
class extends Mixin(x) {
constructor() {
super();
assert.step('B');
}
};
Registries.Component.addByExtending(B, A);
let A1 = x =>
class extends x {
constructor() {
super();
assert.step('A1');
}
};
Registries.Component.extend(A, A1);
Registries.Component.freeze();
B = Registries.Component.get(B);
const b = new B();
assert.verifySteps(['A', 'A1', 'Mixin', 'B']);
// instance of B should have the mixin properties
assert.strictEqual(b.mixinMethod(), 'mixinMethod');
assert.strictEqual(b.mixinGetter, 'mixinGetter');
// instance of A should not have the mixin properties
A = Registries.Component.get(A);
const a = new A();
assert.verifySteps(['A', 'A1']);
assert.notOk(a.mixinMethod);
assert.notOk(a.mixinGetter);
});
QUnit.test('extending methods', async function(assert) {
assert.expect(16);
class A {
foo() {
assert.step('A foo');
}
}
Registries.Component.add(A);
let B = x =>
class extends x {
bar() {
assert.step('B bar');
}
};
Registries.Component.addByExtending(B, A);
let A1 = x =>
class extends x {
bar() {
assert.step('A1 bar');
// should only be for A.
}
};
Registries.Component.extend(A, A1);
let B1 = x =>
class extends x {
foo() {
super.foo();
assert.step('B1 foo');
}
};
Registries.Component.extend(B, B1);
let C = x =>
class extends x {
foo() {
super.foo();
assert.step('C foo');
}
bar() {
super.bar();
assert.step('C bar');
}
};
Registries.Component.addByExtending(C, B);
Registries.Component.freeze();
A = Registries.Component.get(A);
B = Registries.Component.get(B);
C = Registries.Component.get(C);
const a = new A();
const b = new B();
const c = new C();
a.foo();
assert.verifySteps(['A foo']);
b.foo();
assert.verifySteps(['A foo', 'B1 foo']);
c.foo();
assert.verifySteps(['A foo', 'B1 foo', 'C foo']);
a.bar();
assert.verifySteps(['A1 bar']);
b.bar();
assert.verifySteps(['B bar']);
c.bar();
assert.verifySteps(['B bar', 'C bar']);
});
});

View file

@ -0,0 +1,69 @@
odoo.define('point_of_sale.tests.NumberBuffer', function(require) {
'use strict';
const NumberBuffer = require('point_of_sale.NumberBuffer');
const makeTestEnvironment = require('web.test_env');
const testUtils = require('web.test_utils');
const { mount } = require('@web/../tests/helpers/utils');
const { LegacyComponent } = require("@web/legacy/legacy_component");
const { useState, xml } = owl;
QUnit.module('unit tests for NumberBuffer', {
before() {},
});
QUnit.test('simple fast inputs with capture in between', async function(assert) {
assert.expect(3);
const target = testUtils.prepareTarget();
const env = makeTestEnvironment();
class Root extends LegacyComponent {
setup() {
this.state = useState({ buffer: '' });
NumberBuffer.activate();
NumberBuffer.use({
nonKeyboardInputEvent: 'numpad-click-input',
state: this.state,
});
}
resetBuffer() {
NumberBuffer.capture();
NumberBuffer.reset();
}
onClickOne() {
this.trigger('numpad-click-input', { key: '1' });
}
onClickTwo() {
this.trigger('numpad-click-input', { key: '2' });
}
}
Root.template = xml/* html */ `
<div>
<p><t t-esc="state.buffer" /></p>
<button class="one" t-on-click="onClickOne">1</button>
<button class="two" t-on-click="onClickTwo">2</button>
<button class="reset" t-on-click="resetBuffer">reset</button>
</div>
`;
await mount(Root, target, { env });
const oneButton = target.querySelector('button.one');
const twoButton = target.querySelector('button.two');
const resetButton = target.querySelector('button.reset');
const bufferEl = target.querySelector('p');
testUtils.dom.click(oneButton);
testUtils.dom.click(twoButton);
await testUtils.nextTick();
assert.strictEqual(bufferEl.textContent, '12');
testUtils.dom.click(resetButton);
await testUtils.nextTick();
assert.strictEqual(bufferEl.textContent, '');
testUtils.dom.click(twoButton);
testUtils.dom.click(oneButton);
await testUtils.nextTick();
assert.strictEqual(bufferEl.textContent, '21');
});
});

View file

@ -0,0 +1,165 @@
odoo.define('point_of_sale.tests.PosPopupController', function(require) {
'use strict';
const PosPopupController = require('point_of_sale.PosPopupController');
const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup');
const PosComponent = require('point_of_sale.PosComponent');
const makeTestEnvironment = require('web.test_env');
const testUtils = require('web.test_utils');
const Registries = require('point_of_sale.Registries');
const { mount } = require('@web/../tests/helpers/utils');
const { EventBus, useSubEnv, xml } = owl;
QUnit.module('unit tests for PosPopupController', {
before() {
Registries.Component.freeze();
// Note that we are creating new popups here to decouple this test from the pos app.
class CustomPopup1 extends AbstractAwaitablePopup {}
CustomPopup1.template = xml/* html */`
<div class="popup custom-popup-1">
<footer>
<div class="confirm" t-on-click="confirm">
Yes
</div>
<div class="cancel" t-on-click="cancel">
No
</div>
</footer>
</div>
`;
class CustomPopup2 extends AbstractAwaitablePopup {}
CustomPopup2.template = xml/* html */`
<div class="popup custom-popup-2">
<footer>
<div class="confirm" t-on-click="confirm">
Okay
</div>
</footer>
</div>
`;
PosPopupController.components = { CustomPopup1, CustomPopup2 };
},
});
QUnit.test('allow multiple popups at the same time', async function(assert) {
assert.expect(12);
class Root extends PosComponent {
setup() {
super.setup();
useSubEnv({
isDebug: () => false,
posbus: new EventBus(),
});
}
}
Root.env = makeTestEnvironment();
Root.template = xml/* html */ `
<div>
<PosPopupController />
</div>
`;
const root = await mount(Root, testUtils.prepareTarget());
// Check 1 popup
let popup1Promise = root.showPopup('CustomPopup1', {});
await testUtils.nextTick();
assert.strictEqual(root.el.querySelectorAll('.popup').length, 1);
testUtils.dom.click(root.el.querySelector('.modal-dialog .custom-popup-1 .confirm'));
let result1 = await popup1Promise;
assert.strictEqual(result1.confirmed, true);
await testUtils.nextTick();
assert.strictEqual(root.el.querySelectorAll('.popup').length, 0);
// Check multiple popups
popup1Promise = root.showPopup('CustomPopup1', {});
await testUtils.nextTick();
// Check if the first popup is shown.
assert.strictEqual(root.el.querySelectorAll('.popup').length, 1);
let popup2Promise = root.showPopup('CustomPopup2', {});
await testUtils.nextTick();
// Check for the second popup.
assert.strictEqual(root.el.querySelectorAll('.popup').length, 2);
// popup 1 should be hidden
assert.strictEqual(root.el.querySelectorAll('.modal-dialog.oe_hidden').length, 1);
// click confirm on popup 2
testUtils.dom.click(root.el.querySelector('.modal-dialog .custom-popup-2 .confirm'));
await testUtils.nextTick();
// after confirming on popup 2, only 1 should remain.
assert.strictEqual(root.el.querySelectorAll('.popup').length, 1);
assert.strictEqual(root.el.querySelectorAll('.modal-dialog .custom-popup-2').length, 0);
// popup 1 should not be hidden
const CustomPopup1 = root.el.querySelector('.modal-dialog')
assert.strictEqual(![...CustomPopup1.classList].includes('oe_hidden'), true);
testUtils.dom.click(root.el.querySelector('.modal-dialog .custom-popup-1 .cancel'));
await testUtils.nextTick();
// after cancelling popup 1, no popup should remain.
assert.strictEqual(root.el.querySelectorAll('.popup').length, 0);
result1 = await popup1Promise;
let result2 = await popup2Promise;
assert.strictEqual(result1.confirmed, false); // false because it's cancelled.
assert.strictEqual(result2.confirmed, true); // true because it's confirmed.
});
QUnit.test('pressing cancel/confirm key should only close the top popup', async function(assert) {
assert.expect(6);
class Root extends PosComponent {
setup() {
super.setup();
useSubEnv({
isDebug: () => false,
posbus: new EventBus(),
});
}
}
Root.env = makeTestEnvironment();
Root.template = xml/* html */ `
<div>
<PosPopupController />
</div>
`;
const root = await mount(Root, testUtils.prepareTarget());
let popup1Promise = root.showPopup('CustomPopup1', { confirmKey: 'Enter', cancelKey: 'Escape' });
await testUtils.nextTick();
assert.strictEqual(root.el.querySelectorAll('.popup').length, 1);
let popup2Promise = root.showPopup('CustomPopup2', { confirmKey: 'Enter', cancelKey: 'Escape' });
await testUtils.nextTick();
assert.strictEqual(root.el.querySelectorAll('.popup').length, 2);
// Pressing 'Escape' should cancel the top popup which is the CustomPopup2.
testUtils.dom.triggerEvent(window, 'keyup', { key: 'Escape' });
await testUtils.nextTick();
// Therefore, the popup2Promise has now resolved with `confirmed` value = false.
const result2 = await popup2Promise;
assert.strictEqual(result2.confirmed, false);
assert.strictEqual(root.el.querySelectorAll('.popup').length, 1);
testUtils.dom.triggerEvent(window, 'keyup', { key: 'Enter' });
await testUtils.nextTick();
assert.strictEqual(root.el.querySelectorAll('.popup').length, 0);
const result1 = await popup1Promise;
assert.strictEqual(result1.confirmed, true);
});
});