[FIX] web: useNumpadDecimal: synchronize the inputs listeners
This makes sure that the handler is properly assigned/removed to/from the added/removed input elements. closes odoo/odoo#104114 X-original-commit: d88b5e96a4d4a80dd14a271538a4669a6efd2d48 Signed-off-by: Bruno Boi (boi) <boi@odoo.com> Signed-off-by: Joseph Caburnay (jcb) <jcb@odoo.com>
This commit is contained in:
@@ -17,7 +17,6 @@ const { useRef, useEffect } = owl;
|
||||
*/
|
||||
export function useNumpadDecimal() {
|
||||
const decimalPoint = localization.decimalPoint;
|
||||
const listeners = [];
|
||||
const ref = useRef("numpadDecimal");
|
||||
const handler = (ev) => {
|
||||
if (
|
||||
@@ -35,19 +34,15 @@ export function useNumpadDecimal() {
|
||||
"end"
|
||||
);
|
||||
};
|
||||
useEffect(
|
||||
(el) => {
|
||||
if (el) {
|
||||
const inputs = el.nodeName === "INPUT" ? [el] : el.querySelectorAll("input");
|
||||
inputs.forEach((input) => {
|
||||
listeners.push(input);
|
||||
input.addEventListener("keydown", handler);
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
listeners.forEach((input) => input.removeEventListener("keydown", handler));
|
||||
};
|
||||
},
|
||||
() => [ref.el]
|
||||
);
|
||||
useEffect(() => {
|
||||
let inputs = [];
|
||||
const el = ref.el;
|
||||
if (el) {
|
||||
inputs = el.nodeName === "INPUT" ? [el] : el.querySelectorAll("input");
|
||||
inputs.forEach((input) => input.addEventListener("keydown", handler));
|
||||
}
|
||||
return () => {
|
||||
inputs.forEach((input) => input.removeEventListener("keydown", handler));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@ import { registry } from "@web/core/registry";
|
||||
import { click, getFixture, nextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { localization } from "@web/core/l10n/localization";
|
||||
import { useNumpadDecimal } from "@web/views/fields/numpad_decimal_hook";
|
||||
import { makeTestEnv } from "../../helpers/mock_env";
|
||||
|
||||
const { Component, mount, useState, xml } = owl;
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
@@ -78,6 +82,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
};
|
||||
|
||||
setupViewRegistries();
|
||||
patchWithCleanup(localization, { decimalPoint: ",", thousandsSep: "." });
|
||||
});
|
||||
|
||||
QUnit.module("Numeric fields");
|
||||
@@ -193,8 +198,6 @@ QUnit.module("Fields", (hooks) => {
|
||||
QUnit.test(
|
||||
"Numeric fields: NumpadDecimal key is different from the decimalPoint",
|
||||
async function (assert) {
|
||||
patchWithCleanup(localization, { decimalPoint: ",", thousandsSep: "." });
|
||||
|
||||
await makeView({
|
||||
serverData,
|
||||
type: "form",
|
||||
@@ -223,7 +226,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
* Common assertion steps are extracted in this procedure.
|
||||
*
|
||||
* @param {object} params
|
||||
* @param {InputElement} params.el
|
||||
* @param {HTMLInputElement} params.el
|
||||
* @param {[number, number]} params.selectionRange
|
||||
* @param {string} params.expectedValue
|
||||
* @param {string} params.msg
|
||||
@@ -313,4 +316,62 @@ QUnit.module("Fields", (hooks) => {
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"useNumpadDecimal should synchronize handlers on input elements",
|
||||
async function (assert) {
|
||||
/**
|
||||
* Takes an array of input elements and asserts that each has the correct event listener.
|
||||
* @param {HTMLInputElement[]} inputEls
|
||||
*/
|
||||
async function testInputElements(inputEls) {
|
||||
for (const inputEl of inputEls) {
|
||||
inputEl.focus();
|
||||
const numpadDecimalEvent = new KeyboardEvent("keydown", {
|
||||
code: "NumpadDecimal",
|
||||
key: ".",
|
||||
});
|
||||
numpadDecimalEvent.preventDefault = () => assert.step("preventDefault");
|
||||
inputEl.dispatchEvent(numpadDecimalEvent);
|
||||
await nextTick();
|
||||
|
||||
// dispatch an extra keydown event and assert that it's not default prevented
|
||||
const extraEvent = new KeyboardEvent("keydown", { code: "Digit1", key: "1" });
|
||||
extraEvent.preventDefault = () => {
|
||||
throw new Error("should not be default prevented");
|
||||
};
|
||||
inputEl.dispatchEvent(extraEvent);
|
||||
await nextTick();
|
||||
|
||||
assert.verifySteps(["preventDefault"]);
|
||||
}
|
||||
}
|
||||
|
||||
class MyComponent extends Component {
|
||||
setup() {
|
||||
useNumpadDecimal();
|
||||
this.state = useState({ showOtherInput: false });
|
||||
}
|
||||
}
|
||||
MyComponent.template = xml`
|
||||
<main t-ref="numpadDecimal">
|
||||
<input type="text" placeholder="input 1" />
|
||||
<input t-if="state.showOtherInput" type="text" placeholder="input 2" />
|
||||
</main>
|
||||
`;
|
||||
const comp = await mount(MyComponent, target, { env: await makeTestEnv() });
|
||||
|
||||
// Initially, only one input should be rendered.
|
||||
assert.containsOnce(target, "main > input");
|
||||
await testInputElements(target.querySelectorAll("main > input"));
|
||||
|
||||
// We show the second input by manually updating the state.
|
||||
comp.state.showOtherInput = true;
|
||||
await nextTick();
|
||||
|
||||
// The second input should also be able to handle numpad decimal.
|
||||
assert.containsN(target, "main > input", 2);
|
||||
await testInputElements(target.querySelectorAll("main > input"));
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user