From 3751379f1e9a4c215fb6eb898b4ccc67659b9ace Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Tue, 10 May 2022 21:51:50 +0700 Subject: initial commit 2 --- .../static/tests/unit/helpers/test_env.js | 46 ++ .../static/tests/unit/helpers/test_main.js | 23 + .../static/tests/unit/test_ChromeWidgets.js | 89 +++ .../static/tests/unit/test_ComponentRegistry.js | 414 ++++++++++++++ .../static/tests/unit/test_NumberBuffer.js | 65 +++ .../static/tests/unit/test_PaymentScreen.js | 309 +++++++++++ .../static/tests/unit/test_ProductScreen.js | 603 +++++++++++++++++++++ .../point_of_sale/static/tests/unit/test_popups.js | 180 ++++++ 8 files changed, 1729 insertions(+) create mode 100644 addons/point_of_sale/static/tests/unit/helpers/test_env.js create mode 100644 addons/point_of_sale/static/tests/unit/helpers/test_main.js create mode 100644 addons/point_of_sale/static/tests/unit/test_ChromeWidgets.js create mode 100644 addons/point_of_sale/static/tests/unit/test_ComponentRegistry.js create mode 100644 addons/point_of_sale/static/tests/unit/test_NumberBuffer.js create mode 100644 addons/point_of_sale/static/tests/unit/test_PaymentScreen.js create mode 100644 addons/point_of_sale/static/tests/unit/test_ProductScreen.js create mode 100644 addons/point_of_sale/static/tests/unit/test_popups.js (limited to 'addons/point_of_sale/static/tests/unit') diff --git a/addons/point_of_sale/static/tests/unit/helpers/test_env.js b/addons/point_of_sale/static/tests/unit/helpers/test_env.js new file mode 100644 index 00000000..c4b0b3ec --- /dev/null +++ b/addons/point_of_sale/static/tests/unit/helpers/test_env.js @@ -0,0 +1,46 @@ +odoo.define('point_of_sale.test_env', async function (require) { + 'use strict'; + + /** + * Many components in PoS are dependent on the PosModel 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('web.env'); + const models = require('point_of_sale.models'); + const Registries = require('point_of_sale.Registries'); + + Registries.Component.add(owl.misc.Portal); + + await env.session.is_bound; + const pos = new models.PosModel({ + rpc: env.services.rpc, + session: env.session, + do_action: async () => {}, + setLoadingMessage: () => {}, + setLoadingProgress: () => {}, + showLoadingSkip: () => {}, + }); + await pos.ready; + + /** + * @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 }); + let posEnv = makeTestEnvironment(env, providedRPC); + // Replace rpc in the PosModel instance after loading + // data from the server so that every succeeding rpc calls + // made by pos are mocked by the providedRPC. + pos.rpc = posEnv.rpc; + pos.do_action = providedDoAction; + return posEnv; + } + + return makePosTestEnv; +}); diff --git a/addons/point_of_sale/static/tests/unit/helpers/test_main.js b/addons/point_of_sale/static/tests/unit/helpers/test_main.js new file mode 100644 index 00000000..f42e01cb --- /dev/null +++ b/addons/point_of_sale/static/tests/unit/helpers/test_main.js @@ -0,0 +1,23 @@ +odoo.define('web.web_client', function (require) { + // this module is required by the test + const { bus } = require('web.core'); + const WebClient = require('web.AbstractWebClient'); + + // listen to unhandled rejected promises, and when the rejection is not due + // to a crash, prevent the browser from displaying an 'unhandledrejection' + // error in the console, which would make tests crash on each Promise.reject() + // something similar is done by the CrashManagerService, but by default, it + // isn't deployed in tests + bus.on('crash_manager_unhandledrejection', this, function (ev) { + if (!ev.reason || !(ev.reason instanceof Error)) { + ev.stopPropagation(); + ev.stopImmediatePropagation(); + ev.preventDefault(); + } + }); + + owl.config.mode = "dev"; + + const webClient = new WebClient(); + return webClient; +}); diff --git a/addons/point_of_sale/static/tests/unit/test_ChromeWidgets.js b/addons/point_of_sale/static/tests/unit/test_ChromeWidgets.js new file mode 100644 index 00000000..a0df97fd --- /dev/null +++ b/addons/point_of_sale/static/tests/unit/test_ChromeWidgets.js @@ -0,0 +1,89 @@ +odoo.define('point_of_sale.tests.ChromeWidgets', function (require) { + 'use strict'; + + const PosComponent = require('point_of_sale.PosComponent'); + const PopupControllerMixin = require('point_of_sale.PopupControllerMixin'); + const testUtils = require('web.test_utils'); + const makePosTestEnv = require('point_of_sale.test_env'); + const { xml } = owl.tags; + + QUnit.module('unit tests for Chrome Widgets', {}); + + QUnit.test('CashierName', async function (assert) { + assert.expect(1); + + class Parent extends PosComponent {} + Parent.env = makePosTestEnv(); + Parent.template = xml/* html */ ` +
+ `; + Parent.env.pos.employee.name = 'Test Employee'; + + const parent = new Parent(); + await parent.mount(testUtils.prepareTarget()); + + assert.strictEqual(parent.el.querySelector('span.username').innerText, 'Test Employee'); + + parent.unmount(); + parent.destroy(); + }); + + QUnit.test('HeaderButton', async function (assert) { + assert.expect(1); + + class Parent extends PosComponent {} + Parent.env = makePosTestEnv(); + Parent.template = xml/* html */ ` +
+ `; + + const parent = new Parent(); + await parent.mount(testUtils.prepareTarget()); + + const headerButton = parent.el.querySelector('.header-button'); + await testUtils.dom.click(headerButton); + await testUtils.nextTick(); + assert.ok(headerButton.classList.contains('confirm')); + + parent.unmount(); + parent.destroy(); + }); + + QUnit.test('SyncNotification', async function (assert) { + assert.expect(5); + + class Parent extends PosComponent {} + Parent.env = makePosTestEnv(); + Parent.template = xml/* html */ ` +
+ +
+ `; + + const pos = Parent.env.pos; + pos.set('synch', { status: 'connected', pending: false }); + + const parent = new Parent(); + await parent.mount(testUtils.prepareTarget()); + assert.ok(parent.el.querySelector('i.fa').parentElement.classList.contains('js_connected')); + + pos.set('synch', { status: 'connecting', pending: false }); + await testUtils.nextTick(); + assert.ok(parent.el.querySelector('i.fa').parentElement.classList.contains('js_connecting')); + + pos.set('synch', { status: 'disconnected', pending: false }); + await testUtils.nextTick(); + assert.ok(parent.el.querySelector('i.fa').parentElement.classList.contains('js_disconnected')); + + pos.set('synch', { status: 'error', pending: false }); + await testUtils.nextTick(); + assert.ok(parent.el.querySelector('i.fa').parentElement.classList.contains('js_error')); + + pos.set('synch', { status: 'error', pending: 10 }); + await testUtils.nextTick(); + assert.ok(parent.el.querySelector('.js_msg').innerText.includes('10')); + + parent.unmount(); + parent.destroy(); + }); +}); diff --git a/addons/point_of_sale/static/tests/unit/test_ComponentRegistry.js b/addons/point_of_sale/static/tests/unit/test_ComponentRegistry.js new file mode 100644 index 00000000..4b2217cb --- /dev/null +++ b/addons/point_of_sale/static/tests/unit/test_ComponentRegistry.js @@ -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']); + }); +}); diff --git a/addons/point_of_sale/static/tests/unit/test_NumberBuffer.js b/addons/point_of_sale/static/tests/unit/test_NumberBuffer.js new file mode 100644 index 00000000..1e9da1e6 --- /dev/null +++ b/addons/point_of_sale/static/tests/unit/test_NumberBuffer.js @@ -0,0 +1,65 @@ +odoo.define('point_of_sale.tests.NumberBuffer', function(require) { + 'use strict'; + + const { Component, useState } = owl; + const { xml } = owl.tags; + const NumberBuffer = require('point_of_sale.NumberBuffer'); + const makeTestEnvironment = require('web.test_env'); + const testUtils = require('web.test_utils'); + + QUnit.module('unit tests for NumberBuffer', { + before() {}, + }); + + QUnit.test('simple fast inputs with capture in between', async function(assert) { + assert.expect(3); + + class Root extends Component { + constructor() { + super(); + this.state = useState({ buffer: '' }); + NumberBuffer.activate(); + NumberBuffer.use({ + nonKeyboardInputEvent: 'numpad-click-input', + state: this.state, + }); + } + resetBuffer() { + NumberBuffer.capture(); + NumberBuffer.reset(); + } + } + Root.env = makeTestEnvironment(); + Root.template = xml/* html */ ` +
+

+ + + +
+ `; + + const root = new Root(); + await root.mount(testUtils.prepareTarget()); + + const oneButton = root.el.querySelector('button.one'); + const twoButton = root.el.querySelector('button.two'); + const resetButton = root.el.querySelector('button.reset'); + const bufferEl = root.el.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'); + + root.unmount(); + root.destroy(); + }); +}); diff --git a/addons/point_of_sale/static/tests/unit/test_PaymentScreen.js b/addons/point_of_sale/static/tests/unit/test_PaymentScreen.js new file mode 100644 index 00000000..48d3b55d --- /dev/null +++ b/addons/point_of_sale/static/tests/unit/test_PaymentScreen.js @@ -0,0 +1,309 @@ +odoo.define('point_of_sale.tests.PaymentScreen', function (require) { + 'use strict'; + + const PosComponent = require('point_of_sale.PosComponent'); + const { useListener } = require('web.custom_hooks'); + const testUtils = require('web.test_utils'); + const makePosTestEnv = require('point_of_sale.test_env'); + const { xml } = owl.tags; + const { useState } = owl; + + QUnit.module('unit tests for PaymentScreen components', {}); + + QUnit.test('PaymentMethodButton', async function (assert) { + assert.expect(2); + + class Parent extends PosComponent { + constructor() { + super(...arguments); + useListener('new-payment-line', this._newPaymentLine); + } + _newPaymentLine() { + assert.step('new-payment-line'); + } + } + Parent.env = makePosTestEnv(); + Parent.template = xml/* html */ ` +
+ +
+ `; + + const parent = new Parent(); + await parent.mount(testUtils.prepareTarget()); + + const button = parent.el.querySelector('.paymentmethod'); + await testUtils.dom.click(button); + assert.verifySteps(['new-payment-line']); + + parent.unmount(); + parent.destroy(); + }); + + QUnit.test('PSNumpadInputButton', async function (assert) { + assert.expect(15); + + class Parent extends PosComponent { + constructor({ value, text, changeClassTo }) { + super(); + this.state = useState({ value, text, changeClassTo }); + useListener('input-from-numpad', this._inputFromNumpad); + } + _inputFromNumpad({ detail: { key } }) { + assert.step(`${key}-input`); + } + setState(obj) { + Object.assign(this.state, obj); + } + } + Parent.env = makePosTestEnv(); + Parent.template = xml/* html */ ` +
+ +
+ `; + + let parent = new Parent({ value: '1' }); + await parent.mount(testUtils.prepareTarget()); + + let button = parent.el.querySelector('button'); + assert.ok(button.textContent.includes('1')); + assert.ok(button.classList.contains('number-char')); + await testUtils.dom.click(button); + await testUtils.nextTick(); + assert.verifySteps(['1-input']); + + parent.setState({ value: '2', text: 'Two' }); + await testUtils.nextTick(); + assert.ok(button.textContent.includes('Two')); + await testUtils.dom.click(button); + await testUtils.nextTick(); + assert.verifySteps(['2-input']); + + parent.setState({ value: '+12', text: null, changeClassTo: 'not-number-char' }); + await testUtils.nextTick(); + assert.ok(button.textContent.includes('+12')); + assert.ok(button.classList.contains('not-number-char')); + // class number-char should have been replaced + assert.notOk(button.classList.contains('number-char')); + await testUtils.dom.click(button); + await testUtils.nextTick(); + assert.verifySteps(['+12-input']); + + parent.unmount(); + parent.destroy(); + + // using the slot should ignore value and text props of the component + Parent.template = xml/* html */ ` +
+ + UseSlot + +
+ `; + parent = new Parent({ value: 'slotted', text: 'Text' }); + await parent.mount(testUtils.prepareTarget()); + + button = parent.el.querySelector('button'); + assert.ok(button.textContent.includes('UseSlot')); + await testUtils.dom.click(button); + await testUtils.nextTick(); + assert.verifySteps(['slotted-input']); + + parent.unmount(); + parent.destroy(); + }); + + QUnit.test('PaymentScreenPaymentLines', async function (assert) { + assert.expect(12); + + class Parent extends PosComponent { + constructor() { + super(); + useListener('delete-payment-line', this._onDeletePaymentLine); + useListener('select-payment-line', this._onSelectPaymentLine); + } + get paymentLines() { + return this.order.get_paymentlines(); + } + get order() { + return this.env.pos.get_order(); + } + mounted() { + this.order.paymentlines.on('change', this.render, this); + } + willUnmount() { + this.order.paymentlines.off('change', null, this); + } + _onDeletePaymentLine() { + assert.step('delete-click'); + } + _onSelectPaymentLine() { + assert.step('select-click'); + } + } + Parent.env = makePosTestEnv(); + Parent.template = xml/* html */ ` +
+ +
+ `; + + let parent = new Parent(); + await parent.mount(testUtils.prepareTarget()); + + const order = parent.env.pos.get_order(); + const cashPM = { id: 0, name: 'Cash', is_cash_count: true, use_payment_terminal: false }; + const bankPM = { id: 0, name: 'Bank', is_cash_count: false, use_payment_terminal: false }; + + let paymentline1 = order.add_paymentline(cashPM); + await testUtils.nextTick(); + + let statusContainer = parent.el.querySelector('.payment-status-container'); + let linesEl = parent.el.querySelector('.paymentlines'); + assert.ok(linesEl, 'payment lines are shown'); + let newLine = linesEl.querySelector('.selected'); + assert.ok(newLine, 'the new line is automatically selected'); + + let paymentline2 = order.add_paymentline(bankPM); + await testUtils.nextTick(); + assert.notOk( + linesEl.querySelector('.selected') === newLine, + 'the previously added paymentline should not be selected anymore' + ); + assert.ok( + linesEl.querySelectorAll('.paymentline:not(.heading)').length === 2, + 'there should be two paymentlines' + ); + + let paymentline3 = order.add_paymentline(cashPM); + await testUtils.nextTick(); + assert.ok( + linesEl.querySelectorAll('.paymentline:not(.heading)').length === 3, + 'there should be three paymentlines' + ); + assert.ok( + linesEl.querySelectorAll('.paymentline.selected').length === 1, + 'there should only be one selected paymentline' + ); + + await testUtils.dom.click(linesEl.querySelector('.paymentline.selected .delete-button')); + await testUtils.nextTick(); + assert.verifySteps(['delete-click', 'select-click']); + + // click the 2nd payment line + await testUtils.dom.click(linesEl.querySelectorAll('.paymentline:not(.heading)')[1]); + await testUtils.nextTick(); + assert.verifySteps(['select-click']); + + // remove paymentline3 (the selected) + order.remove_paymentline(paymentline3); + await testUtils.nextTick(); + assert.notOk( + linesEl.querySelector('.paymentline.selected'), + 'no more selected payment line' + ); + + order.remove_paymentline(paymentline1); + order.remove_paymentline(paymentline2); + + parent.unmount(); + parent.destroy(); + }); + + QUnit.test('PaymentScreenElectronicPayment', async function (assert) { + assert.expect(17); + + class SimulatedPaymentLine extends Backbone.Model { + constructor() { + super(); + this.payment_status = 'pending'; + this.can_be_reversed = false; + } + canBeAdjusted() { + return false; + } + setPaymentStatus(status) { + this.payment_status = status; + this.trigger('change'); + } + toggleCanBeReversed() { + this.can_be_reversed = !this.can_be_reversed; + this.trigger('change'); + } + } + + class Parent extends PosComponent { + constructor() { + super(); + this.line = new SimulatedPaymentLine(); + useListener('send-payment-request', () => assert.step('send-payment-request')); + useListener('send-force-done', () => assert.step('send-force-done')); + useListener('send-payment-cancel', () => assert.step('send-payment-cancel')); + useListener('send-payment-reverse', () => assert.step('send-payment-reverse')); + } + } + Parent.env = makePosTestEnv(); + Parent.template = xml/* html */ ` +
+ +
+ `; + + let parent = new Parent(); + await parent.mount(testUtils.prepareTarget()); + + assert.ok(parent.el.querySelector('.paymentline .send_payment_request')); + await testUtils.dom.click(parent.el.querySelector('.paymentline .send_payment_request')); + await testUtils.nextTick(); + assert.verifySteps(['send-payment-request']); + + parent.line.setPaymentStatus('retry'); + await testUtils.nextTick(); + await testUtils.dom.click(parent.el.querySelector('.paymentline .send_payment_request')); + await testUtils.nextTick(); + assert.verifySteps(['send-payment-request']); + + parent.line.setPaymentStatus('force_done'); + await testUtils.nextTick(); + await testUtils.dom.click(parent.el.querySelector('.paymentline .send_force_done')); + await testUtils.nextTick(); + assert.verifySteps(['send-force-done']); + + parent.line.setPaymentStatus('waitingCard'); + await testUtils.nextTick(); + await testUtils.dom.click(parent.el.querySelector('.paymentline .send_payment_cancel')); + await testUtils.nextTick(); + assert.verifySteps(['send-payment-cancel']); + + parent.line.setPaymentStatus('waiting'); + await testUtils.nextTick(); + assert.ok(parent.el.querySelector('.paymentline i.fa-spinner')); + + parent.line.setPaymentStatus('waitingCancel'); + await testUtils.nextTick(); + assert.ok(parent.el.querySelector('.paymentline i.fa-spinner')); + + parent.line.setPaymentStatus('reversing'); + await testUtils.nextTick(); + assert.ok(parent.el.querySelector('.paymentline i.fa-spinner')); + + parent.line.setPaymentStatus('done'); + await testUtils.nextTick(); + assert.notOk(parent.el.querySelector('.paymentline .send_payment_reversal')); + + parent.line.toggleCanBeReversed(); + await testUtils.nextTick(); + assert.ok(parent.el.querySelector('.paymentline .send_payment_reversal')); + await testUtils.dom.click(parent.el.querySelector('.paymentline .send_payment_reversal')); + await testUtils.nextTick(); + assert.verifySteps(['send-payment-reverse']); + + parent.line.setPaymentStatus('reversed'); + await testUtils.nextTick(); + assert.ok(parent.el.querySelector('.paymentline')); + + parent.unmount(); + parent.destroy(); + }); +}); diff --git a/addons/point_of_sale/static/tests/unit/test_ProductScreen.js b/addons/point_of_sale/static/tests/unit/test_ProductScreen.js new file mode 100644 index 00000000..bdd9b732 --- /dev/null +++ b/addons/point_of_sale/static/tests/unit/test_ProductScreen.js @@ -0,0 +1,603 @@ +odoo.define('point_of_sale.tests.ProductScreen', function (require) { + 'use strict'; + + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + const { useListener } = require('web.custom_hooks'); + const testUtils = require('web.test_utils'); + const makePosTestEnv = require('point_of_sale.test_env'); + const { xml } = owl.tags; + const { useState } = owl; + + QUnit.module('unit tests for ProductScreen components', {}); + + QUnit.test('ActionpadWidget', async function (assert) { + assert.expect(7); + + class Parent extends PosComponent { + constructor() { + super(); + useListener('click-customer', () => assert.step('click-customer')); + useListener('click-pay', () => assert.step('click-pay')); + this.state = useState({ client: null }); + } + } + Parent.env = makePosTestEnv(); + Parent.template = xml/* html */ ` +
+ +
+ `; + + const parent = new Parent(); + await parent.mount(testUtils.prepareTarget()); + + const setCustomerButton = parent.el.querySelector('button.set-customer'); + const payButton = parent.el.querySelector('button.pay'); + + await testUtils.nextTick(); + assert.ok(setCustomerButton.innerText.includes('Customer')); + + // change to customer with short name + parent.state.client = { name: 'Test' }; + await testUtils.nextTick(); + assert.ok(setCustomerButton.innerText.includes('Test')); + + // change to customer with long name + parent.state.client = { name: 'Change Customer' }; + await testUtils.nextTick(); + assert.ok(setCustomerButton.classList.contains('decentered')); + + parent.state.client = null; + + // click set-customer button + await testUtils.dom.click(setCustomerButton); + await testUtils.nextTick(); + assert.verifySteps(['click-customer']); + + // click pay button + await testUtils.dom.click(payButton); + await testUtils.nextTick(); + assert.verifySteps(['click-pay']); + + parent.unmount(); + parent.destroy(); + }); + + QUnit.test('NumpadWidget', async function (assert) { + assert.expect(25); + + class Parent extends PosComponent { + constructor() { + super(...arguments); + useListener('set-numpad-mode', this.setNumpadMode); + useListener('numpad-click-input', this.numpadClickInput); + this.state = useState({ mode: 'quantity' }); + } + setNumpadMode({ detail: { mode } }) { + this.state.mode = mode; + assert.step(mode); + } + numpadClickInput({ detail: { key } }) { + assert.step(key); + } + } + Parent.env = makePosTestEnv(); + Parent.template = xml/* html */ ` +
+ `; + + const pos = Parent.env.pos; + // set this old values back after testing + const old_config = pos.config; + const old_cashier = pos.get('cashier'); + + // set dummy values in pos.config and pos.get('cashier') + pos.config = { + restrict_price_control: false, + manual_discount: true + }; + pos.set('cashier', { role: 'manager' }); + + const parent = new Parent(); + await parent.mount(testUtils.prepareTarget()); + + const modeButtons = parent.el.querySelectorAll('.mode-button'); + let qtyButton, discButton, priceButton; + for (let button of modeButtons) { + if (button.textContent.includes('Qty')) { + qtyButton = button; + } + if (button.textContent.includes('Disc')) { + discButton = button; + } + if (button.textContent.includes('Price')) { + priceButton = button; + } + } + + // initially, qty button is active + assert.ok(qtyButton.classList.contains('selected-mode')); + assert.ok(!discButton.classList.contains('selected-mode')); + assert.ok(!priceButton.classList.contains('selected-mode')); + + await testUtils.dom.click(discButton); + await testUtils.nextTick(); + assert.ok(!qtyButton.classList.contains('selected-mode')); + assert.ok(discButton.classList.contains('selected-mode')); + assert.ok(!priceButton.classList.contains('selected-mode')); + assert.verifySteps(['discount']); + + await testUtils.dom.click(priceButton); + await testUtils.nextTick(); + assert.ok(!qtyButton.classList.contains('selected-mode')); + assert.ok(!discButton.classList.contains('selected-mode')); + assert.ok(priceButton.classList.contains('selected-mode')); + assert.verifySteps(['price']); + + const numpadOne = [...parent.el.querySelectorAll('.number-char').values()].find((el) => + el.textContent.includes('1') + ); + const numpadMinus = parent.el.querySelector('.numpad-minus'); + const numpadBackspace = parent.el.querySelector('.numpad-backspace'); + + await testUtils.dom.click(numpadOne); + await testUtils.nextTick(); + assert.verifySteps(['1']); + + await testUtils.dom.click(numpadMinus); + await testUtils.nextTick(); + assert.verifySteps(['-']); + + await testUtils.dom.click(numpadBackspace); + await testUtils.nextTick(); + assert.verifySteps(['Backspace']); + + await testUtils.dom.click(priceButton); + await testUtils.nextTick(); + assert.verifySteps(['price']); + + // change to price control restriction and the cashier is not manager + pos.config.restrict_price_control = true; + pos.set('cashier', { role: 'not manager' }); + await testUtils.nextTick(); + + assert.ok(priceButton.classList.contains('disabled-mode')); + assert.ok(qtyButton.classList.contains('selected-mode')); + // after the cashier is changed, since it is not a manager, + // the 'set-numpad-mode' is triggered, setting the mode to + // 'quantity'. + assert.verifySteps(['quantity']); + + // reset old config and cashier values to pos + pos.config = old_config; + pos.set('cashier', old_cashier); + + parent.unmount(); + parent.destroy(); + }); + + QUnit.test('ProductsWidgetControlPanel', async function (assert) { + assert.expect(32); + + // This test incorporates the following components: + // CategoryBreadcrumb + // CategoryButton + // CategorySimpleButton + // HomeCategoryBreadcrumb + + // Create dummy category data + // + // Root + // | Test1 + // | | Test2 + // | ` Test3 + // | | Test5 + // | ` Test6 + // ` Test4 + + const rootCategory = { id: 0, name: 'Root', parent: null }; + const testCategory1 = { id: 1, name: 'Test1', parent: 0 }; + const testCategory2 = { id: 2, name: 'Test2', parent: 1 }; + const testCategory3 = { id: 3, name: 'Test3', parent: 1 }; + const testCategory4 = { id: 4, name: 'Test4', parent: 0 }; + const testCategory5 = { id: 5, name: 'Test5', parent: 3 }; + const testCategory6 = { id: 6, name: 'Test6', parent: 3 }; + const categories = { + 0: rootCategory, + 1: testCategory1, + 2: testCategory2, + 3: testCategory3, + 4: testCategory4, + 5: testCategory5, + 6: testCategory6, + }; + + class Parent extends PosComponent { + constructor() { + super(...arguments); + this.state = useState({ selectedCategoryId: 0 }); + useListener('switch-category', this.switchCategory); + useListener('update-search', this.updateSearch); + useListener('clear-search', this.clearSearch); + } + get breadcrumbs() { + if (this.state.selectedCategoryId === 0) return []; + let current = categories[this.state.selectedCategoryId]; + const res = [current]; + while (current.parent != 0) { + const toAdd = categories[current.parent]; + res.push(toAdd); + current = toAdd; + } + return res.reverse(); + } + get subcategories() { + return Object.values(categories).filter( + ({ parent }) => parent == this.state.selectedCategoryId + ); + } + switchCategory({ detail: id }) { + this.state.selectedCategoryId = id; + assert.step(`${id}`); + } + updateSearch(event) { + assert.step(event.detail); + } + clearSearch() { + assert.step('cleared'); + } + } + Parent.env = makePosTestEnv(); + Parent.template = xml/* html */ ` +
+
+ +
+
+ `; + + const pos = Parent.env.pos; + const old_config = pos.config; + // set dummy config + pos.config = { iface_display_categ_images: false }; + + const parent = new Parent(); + await parent.mount(testUtils.prepareTarget()); + + // The following tests the breadcrumbs and subcategory buttons + + // check if HomeCategoryBreadcrumb is rendered + assert.ok( + parent.el.querySelector('.breadcrumb-home'), + 'Home category should always be there' + ); + let subcategorySpans = [...parent.el.querySelectorAll('.category-simple-button')]; + assert.ok(subcategorySpans.length === 2, 'There should be 2 subcategories for Root.'); + assert.ok(subcategorySpans.find((span) => span.textContent.includes('Test1'))); + assert.ok(subcategorySpans.find((span) => span.textContent.includes('Test4'))); + + // click Test1 + let test1Span = subcategorySpans.find((span) => span.textContent.includes('Test1')); + await testUtils.dom.click(test1Span); + await testUtils.nextTick(); + assert.verifySteps(['1']); + assert.ok( + [...parent.el.querySelectorAll('.breadcrumb-button')][1].textContent.includes('Test1') + ); + subcategorySpans = [...parent.el.querySelectorAll('.category-simple-button')]; + assert.ok(subcategorySpans.length === 2, 'There should be 2 subcategories for Root.'); + assert.ok(subcategorySpans.find((span) => span.textContent.includes('Test2'))); + assert.ok(subcategorySpans.find((span) => span.textContent.includes('Test3'))); + + // click Test2 + let test2Span = subcategorySpans.find((span) => span.textContent.includes('Test2')); + await testUtils.dom.click(test2Span); + await testUtils.nextTick(); + assert.verifySteps(['2']); + subcategorySpans = [...parent.el.querySelectorAll('.category-simple-button')]; + assert.ok(subcategorySpans.length === 0, 'Test2 should not have subcategories'); + + // go back to Test1 + let breadcrumb1 = [...parent.el.querySelectorAll('.breadcrumb-button')].find((el) => + el.textContent.includes('Test1') + ); + await testUtils.dom.click(breadcrumb1); + await testUtils.nextTick(); + assert.verifySteps(['1']); + + // click Test3 + subcategorySpans = [...parent.el.querySelectorAll('.category-simple-button')]; + let test3Span = subcategorySpans.find((span) => span.textContent.includes('Test3')); + await testUtils.dom.click(test3Span); + await testUtils.nextTick(); + assert.verifySteps(['3']); + subcategorySpans = [...parent.el.querySelectorAll('.category-simple-button')]; + assert.ok(subcategorySpans.length === 2); + + // click Test6 + let test6Span = subcategorySpans.find((span) => span.textContent.includes('Test6')); + await testUtils.dom.click(test6Span); + await testUtils.nextTick(); + assert.verifySteps(['6']); + let breadcrumbButtons = [...parent.el.querySelectorAll('.breadcrumb-button')]; + assert.ok(breadcrumbButtons.length === 4); + + // Now check subcategory buttons with images + pos.config.iface_display_categ_images = true; + + let breadcrumbHome = parent.el.querySelector('.breadcrumb-home'); + await testUtils.dom.click(breadcrumbHome); + await testUtils.nextTick(); + assert.verifySteps(['0']); + assert.ok( + !parent.el.querySelector('.category-list').classList.contains('simple'), + 'Category list should not have simple class' + ); + let categoryButtons = [...parent.el.querySelectorAll('.category-button')]; + assert.ok(categoryButtons.length === 2, 'There should be 2 subcategories for Root'); + + // The following tests the search bar + + const wait = (ms) => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }; + + const inputEl = parent.el.querySelector('.search-box input'); + await testUtils.dom.triggerEvent(inputEl, 'keyup', { key: 'A' }); + // Triggering keyup event doesn't type the key to the input + // so we manually assign the value of the input. + inputEl.value = 'A'; + await wait(30); + await testUtils.dom.triggerEvent(inputEl, 'keyup', { key: 'B' }); + inputEl.value = 'AB'; + await wait(30); + await testUtils.dom.triggerEvent(inputEl, 'keyup', { key: 'C' }); + inputEl.value = 'ABC'; + await wait(110); + // Only after waiting for more than 100ms that update-search is triggered + // because the method is debounced. + assert.verifySteps(['ABC']); + await testUtils.dom.triggerEvent(inputEl, 'keyup', { key: 'D' }); + inputEl.value = 'ABCD'; + await wait(110); + assert.verifySteps(['ABCD']); + + // clear the search bar + await testUtils.dom.click(parent.el.querySelector('.search-box .clear-icon')); + await testUtils.nextTick(); + assert.verifySteps(['cleared']); + assert.ok(inputEl.value === '', 'value of the input element should be empty'); + + pos.config = old_config; + + parent.unmount(); + parent.destroy(); + }); + + QUnit.test('ProductList, ProductItem', async function (assert) { + assert.expect(10); + + // patch imageUrl and price of ProductItem component + const MockProductItemExt = (X) => + class extends X { + get imageUrl() { + return 'data:,'; + } + get price() { + return this.props.product.price; + } + }; + + const extension = Registries.Component.extend('ProductItem', MockProductItemExt); + extension.compile(); + + const dummyProducts = [ + { id: 0, display_name: 'Burger', price: '$10' }, + { id: 1, display_name: 'Water', price: '$2' }, + { id: 2, display_name: 'Chair', price: '$25' }, + ]; + + class Parent extends PosComponent { + constructor() { + super(...arguments); + this.state = useState({ searchWord: '', products: dummyProducts }); + useListener('click-product', this._clickProduct); + } + _clickProduct({ detail: product }) { + assert.step(product.display_name); + } + } + Parent.env = makePosTestEnv(); + Parent.template = xml/* html */ ` +
+ +
+ `; + + const parent = new Parent(); + await parent.mount(testUtils.prepareTarget()); + + // Check if there are 3 products listed + assert.strictEqual( + parent.el.querySelectorAll('article.product').length, + 3, + 'There should be 3 products listed' + ); + + // Check contents of product item and click + const product1el = parent.el.querySelector( + 'article.product[aria-labelledby="article_product_1"]' + ); + assert.ok(product1el.querySelector('.product-img img[alt="Water"]')); + assert.ok(product1el.querySelector('.product-img .price-tag').textContent.includes('$2')); + await testUtils.dom.click(product1el); + await testUtils.nextTick(); + assert.verifySteps(['Water']); + + // Remove one product, check if only two is listed + parent.state.products.splice(0, 1); + await testUtils.nextTick(); + assert.strictEqual( + parent.el.querySelectorAll('article.product').length, + 2, + 'There should be 2 products listed after removing the first item' + ); + + // Remove all products, check if empty message is There are no products in this category + parent.state.products.splice(0, parent.state.products.length); + await testUtils.nextTick(); + assert.strictEqual( + parent.el.querySelectorAll('article.product').length, + 0, + 'There should be 0 products listed after removing everything' + ); + assert.ok( + parent.el + .querySelector('.product-list-empty p') + .textContent.includes('There are no products in this category.') + ); + + // change the searchWord to 'something', check if empty message is No results found + parent.state.searchWord = 'something'; + await testUtils.nextTick(); + assert.ok( + parent.el + .querySelector('.product-list-empty p') + .textContent.includes('No results found for') + ); + assert.ok( + parent.el.querySelector('.product-list-empty p b').textContent.includes('something') + ); + + extension.remove(); + + parent.unmount(); + parent.destroy(); + }); + + QUnit.test('Orderline', async function (assert) { + assert.expect(10); + + class Parent extends PosComponent { + constructor(product) { + super(); + useListener('select-line', this._selectLine); + useListener('edit-pack-lot-lines', this._editPackLotLines); + this.order.add_product(product); + } + get order() { + return this.env.pos.get_order(); + } + get line() { + return this.env.pos.get_order().get_orderlines()[0]; + } + _selectLine() { + assert.step('select-line'); + } + _editPackLotLines() { + assert.step('edit-pack-lot-lines'); + } + willUnmount() { + this.order.remove_orderline(this.line); + } + } + Parent.env = makePosTestEnv(); + Parent.template = xml/* html */ ` +
+ +
+ `; + + const [chair1, chair2] = Parent.env.pos.db.search_product_in_category(0, 'Office Chair'); + // patch chair2 to have tracking + chair2.tracking = 'serial'; + + // 1. Test orderline without lot icon + + let parent = new Parent(chair1); + await parent.mount(testUtils.prepareTarget()); + + let line = parent.el.querySelector('li.orderline'); + assert.ok(line); + assert.notOk(line.querySelector('.line-lot-icon'), 'there should be no lot icon'); + await testUtils.dom.click(line); + assert.verifySteps(['select-line']); + + parent.unmount(); + parent.destroy(); + + // 2. Test orderline with lot icon + + parent = new Parent(chair2); + await parent.mount(testUtils.prepareTarget()); + + line = parent.el.querySelector('li.orderline'); + const lotIcon = line.querySelector('.line-lot-icon'); + assert.ok(line); + assert.ok(lotIcon, 'there should be lot icon'); + await testUtils.dom.click(line); + assert.verifySteps(['select-line']); + await testUtils.dom.click(lotIcon); + assert.verifySteps(['edit-pack-lot-lines']); + + parent.unmount(); + parent.destroy(); + }); + + QUnit.test('OrderWidget', async function (assert) { + assert.expect(8); + + // OrderWidget is dependent on its parent's rerendering + class Parent extends PosComponent { + mounted() { + this.env.pos.on('change:selectedOrder', this.render, this); + } + willUnmount() { + this.env.pos.off('change:selectedOrder', null, this); + } + } + Parent.env = makePosTestEnv(); + Parent.template = xml/* html */ ` +
+ +
+ `; + + const [chair1, chair2] = Parent.env.pos.db.search_product_in_category(0, 'Office Chair'); + + let parent = new Parent(); + await parent.mount(testUtils.prepareTarget()); + + // current order is empty + assert.notOk(parent.el.querySelector('.summary')); + assert.ok(parent.el.querySelector('.order-empty')); + + // add line to the current order + const order1 = parent.env.pos.get_order(); + order1.add_product(chair1); + await testUtils.nextTick(); + assert.ok(parent.el.querySelector('.summary')); + assert.notOk(parent.el.querySelector('.order-empty')); + + // selected new order, new order is empty + const order2 = parent.env.pos.add_new_order(); + await testUtils.nextTick(); + assert.notOk(parent.el.querySelector('.summary')); + assert.ok(parent.el.querySelector('.order-empty')); + + // add line to the current order + order2.add_product(chair2); + await testUtils.nextTick(); + assert.ok(parent.el.querySelector('.summary')); + assert.notOk(parent.el.querySelector('.order-empty')); + + parent.env.pos.delete_current_order(); + parent.env.pos.delete_current_order(); + + parent.unmount(); + parent.destroy(); + }); +}); diff --git a/addons/point_of_sale/static/tests/unit/test_popups.js b/addons/point_of_sale/static/tests/unit/test_popups.js new file mode 100644 index 00000000..205d1b24 --- /dev/null +++ b/addons/point_of_sale/static/tests/unit/test_popups.js @@ -0,0 +1,180 @@ +odoo.define('point_of_sale.test_popups', function(require) { + 'use strict'; + + const Registries = require('point_of_sale.Registries'); + const testUtils = require('web.test_utils'); + const PosComponent = require('point_of_sale.PosComponent'); + const PopupControllerMixin = require('point_of_sale.PopupControllerMixin'); + const makePosTestEnv = require('point_of_sale.test_env'); + const { xml } = owl.tags; + + QUnit.module('unit tests for Popups', { + before() { + class Root extends PopupControllerMixin(PosComponent) { + static template = xml` +
+ +
+ `; + } + Root.env = makePosTestEnv(); + this.Root = Root; + Registries.Component.freeze(); + }, + }); + + QUnit.test('ConfirmPopup', async function(assert) { + assert.expect(6); + + const root = new this.Root(); + await root.mount(testUtils.prepareTarget()); + + let promResponse, userResponse; + + // Step: show popup and confirm + promResponse = root.showPopup('ConfirmPopup', {}); + await testUtils.nextTick(); + testUtils.dom.click(root.el.querySelector('.confirm')); + await testUtils.nextTick(); + userResponse = await promResponse; + assert.strictEqual(userResponse.confirmed, true); + + // Step: show popup then cancel + promResponse = root.showPopup('ConfirmPopup', {}); + await testUtils.nextTick(); + testUtils.dom.click(root.el.querySelector('.cancel')); + await testUtils.nextTick(); + userResponse = await promResponse; + assert.strictEqual(userResponse.confirmed, false); + + // Step: check texts + promResponse = root.showPopup('ConfirmPopup', { + title: 'Are you sure?', + body: 'Are you having fun?', + confirmText: 'Hell Yeah!', + cancelText: 'Are you kidding me?', + }); + await testUtils.nextTick(); + assert.strictEqual(root.el.querySelector('.title').innerText.trim(), 'Are you sure?'); + assert.strictEqual(root.el.querySelector('.body').innerText.trim(), 'Are you having fun?'); + assert.strictEqual(root.el.querySelector('.confirm').innerText.trim(), 'Hell Yeah!'); + assert.strictEqual( + root.el.querySelector('.cancel').innerText.trim(), + 'Are you kidding me?' + ); + + root.unmount(); + root.destroy(); + }); + + QUnit.test('NumberPopup', async function(assert) { + assert.expect(8); + + const root = new this.Root(); + await root.mount(testUtils.prepareTarget()); + + let promResponse, userResponse; + + // Step: show NumberPopup and confirm with empty buffer + promResponse = root.showPopup('NumberPopup', {}); + await testUtils.nextTick(); + testUtils.dom.triggerEvent(root.el.querySelector('.confirm'), 'mousedown'); + await testUtils.nextTick(); + userResponse = await promResponse; + assert.strictEqual(userResponse.confirmed, true); + assert.strictEqual(userResponse.payload, ""); + + // Step: show NumberPopup and cancel + promResponse = root.showPopup('NumberPopup', {}); + await testUtils.nextTick(); + testUtils.dom.triggerEvent(root.el.querySelector('.cancel'), 'mousedown'); + await testUtils.nextTick(); + userResponse = await promResponse; + assert.strictEqual(userResponse.confirmed, false); + + // Step: show NumberPopup and confirm with filled buffer, new title, new text + promResponse = root.showPopup('NumberPopup', { + title: 'Are you sure?', + confirmText: 'Hell Yeah!', + cancelText: 'Are you kidding me?', + }); + await testUtils.nextTick(); + let nodes = Array.from(root.el.querySelectorAll('button')); + testUtils.dom.triggerEvent(nodes.find(elem => elem.innerHTML === "7"), 'mousedown'); + await testUtils.nextTick(); + testUtils.dom.triggerEvent(nodes.find(elem => elem.innerHTML === "+10"), 'mousedown'); + await testUtils.nextTick(); + assert.strictEqual(root.el.querySelector('.title').innerText.trim(), 'Are you sure?'); + assert.strictEqual(root.el.querySelector('.confirm').innerText.trim(), 'Hell Yeah!'); + assert.strictEqual(root.el.querySelector('.cancel').innerText.trim(), 'Are you kidding me?'); + testUtils.dom.triggerEvent(root.el.querySelector('.confirm'), 'mousedown'); + await testUtils.nextTick(); + userResponse = await promResponse; + assert.strictEqual(userResponse.confirmed, true); + assert.strictEqual(userResponse.payload, "17"); + + root.unmount(); + root.destroy(); + }); + + QUnit.test('EditListPopup', async function(assert) { + assert.expect(7); + + const root = new this.Root(); + await root.mount(testUtils.prepareTarget()); + + let promResponse, userResponse; + + // Step: show popup and confirm + promResponse = root.showPopup('EditListPopup', {}); + await testUtils.nextTick(); + testUtils.dom.click(root.el.querySelector('.confirm')); + await testUtils.nextTick(); + userResponse = await promResponse; + assert.strictEqual(userResponse.confirmed, true); + assert.strictEqual(JSON.stringify(userResponse.payload.newArray), JSON.stringify([])); + + // Step: show popup and cancel + promResponse = root.showPopup('EditListPopup', {}); + await testUtils.nextTick(); + testUtils.dom.click(root.el.querySelector('.cancel')); + await testUtils.nextTick(); + userResponse = await promResponse; + assert.strictEqual(userResponse.confirmed, false); + + // Step: show popup and confirm with a default array + let defaultArray = ["Banana", "Cherry"]; + promResponse = root.showPopup('EditListPopup', { + title: "Fruits", + isSingleItem: false, + array: defaultArray, + }); + await testUtils.nextTick(); + testUtils.dom.click(root.el.querySelector('.confirm')); + await testUtils.nextTick(); + userResponse = await promResponse; + + assert.strictEqual(userResponse.confirmed, true); + let i = 0; + defaultArray = defaultArray.map((item) => Object.assign({}, { _id: i++ }, { 'text': item})); + assert.strictEqual(JSON.stringify(userResponse.payload.newArray), JSON.stringify(defaultArray)); + + // Step: show popup and confirm with a new array + promResponse = root.showPopup('EditListPopup', { + title: "Fruits", + isSingleItem: false, + array: ["Banana", "Cherry"], + }); + await testUtils.nextTick(); + testUtils.dom.click(root.el.querySelector('.fa-trash-o')); + await testUtils.nextTick(); + testUtils.dom.click(root.el.querySelector('.confirm')); + await testUtils.nextTick(); + userResponse = await promResponse; + assert.strictEqual(userResponse.confirmed, true); + assert.strictEqual(JSON.stringify(userResponse.payload.newArray), JSON.stringify([{ _id: 1, text: "Cherry"}])); + + root.unmount(); + root.destroy(); + }); +}); -- cgit v1.2.3