362 lines
15 KiB
JavaScript
362 lines
15 KiB
JavaScript
/** @odoo-module **/
|
|
|
|
import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services";
|
|
import { registry } from "@web/core/registry";
|
|
import { 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;
|
|
|
|
QUnit.module("Fields", (hooks) => {
|
|
hooks.beforeEach(() => {
|
|
target = getFixture();
|
|
serverData = {
|
|
models: {
|
|
partner: {
|
|
fields: {
|
|
int_field: {
|
|
string: "int_field",
|
|
type: "integer",
|
|
sortable: true,
|
|
searchable: true,
|
|
},
|
|
qux: { string: "Qux", type: "float", digits: [16, 1], searchable: true },
|
|
currency_id: {
|
|
string: "Currency",
|
|
type: "many2one",
|
|
relation: "currency",
|
|
searchable: true,
|
|
},
|
|
float_factor_field: {
|
|
string: "Float Factor",
|
|
type: "float_factor",
|
|
},
|
|
percentage: {
|
|
string: "Percentage",
|
|
type: "percentage",
|
|
},
|
|
monetary: { string: "Monetary", type: "monetary" },
|
|
progressbar: {
|
|
type: "integer",
|
|
},
|
|
progressmax: {
|
|
type: "float",
|
|
},
|
|
},
|
|
records: [
|
|
{
|
|
id: 1,
|
|
int_field: 10,
|
|
qux: 0.44444,
|
|
float_factor_field: 9.99,
|
|
percentage: 0.99,
|
|
monetary: 9.99,
|
|
currency_id: 1,
|
|
progressbar: 69,
|
|
progressmax: 5.41,
|
|
},
|
|
],
|
|
},
|
|
currency: {
|
|
fields: {
|
|
digits: { string: "Digits" },
|
|
symbol: { string: "Currency Sumbol", type: "char", searchable: true },
|
|
position: { string: "Currency Position", type: "char", searchable: true },
|
|
},
|
|
records: [
|
|
{
|
|
id: 1,
|
|
display_name: "$",
|
|
symbol: "$",
|
|
position: "before",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
};
|
|
|
|
setupViewRegistries();
|
|
patchWithCleanup(localization, { decimalPoint: ",", thousandsSep: "." });
|
|
});
|
|
|
|
QUnit.module("Numeric fields");
|
|
|
|
QUnit.test(
|
|
"Numeric fields: fields with keydown on numpad decimal key",
|
|
async function (assert) {
|
|
registry.category("services").remove("localization");
|
|
registry
|
|
.category("services")
|
|
.add("localization", makeFakeLocalizationService({ decimalPoint: "🇧🇪" }));
|
|
await makeView({
|
|
serverData,
|
|
type: "form",
|
|
resModel: "partner",
|
|
arch: `
|
|
<form>
|
|
<field name="float_factor_field" options="{'factor': 0.5}"/>
|
|
<field name="qux"/>
|
|
<field name="int_field"/>
|
|
<field name="monetary"/>
|
|
<field name="currency_id" invisible="1"/>
|
|
<field name="percentage"/>
|
|
<field name="progressbar" widget="progressbar" options="{'editable': true, 'max_value': 'qux', 'edit_max_value': true}"/>
|
|
</form>`,
|
|
resId: 1,
|
|
});
|
|
|
|
// Get all inputs
|
|
const floatFactorField = target.querySelector(".o_field_float_factor input");
|
|
const floatInput = target.querySelector(".o_field_float input");
|
|
const integerInput = target.querySelector(".o_field_integer input");
|
|
const monetaryInput = target.querySelector(".o_field_monetary input");
|
|
const percentageInput = target.querySelector(".o_field_percentage input");
|
|
const progressbarInput = target.querySelector(".o_field_progressbar input");
|
|
|
|
// Dispatch numpad "dot" and numpad "comma" keydown events to all inputs and check
|
|
// Numpad "comma" is specific to some countries (Brazil...)
|
|
floatFactorField.dispatchEvent(
|
|
new KeyboardEvent("keydown", { code: "NumpadDecimal", key: "." })
|
|
);
|
|
floatFactorField.dispatchEvent(
|
|
new KeyboardEvent("keydown", { code: "NumpadDecimal", key: "," })
|
|
);
|
|
await nextTick();
|
|
assert.strictEqual(floatFactorField.value, "5🇧🇪00🇧🇪🇧🇪");
|
|
|
|
floatInput.dispatchEvent(
|
|
new KeyboardEvent("keydown", { code: "NumpadDecimal", key: "." })
|
|
);
|
|
floatInput.dispatchEvent(
|
|
new KeyboardEvent("keydown", { code: "NumpadDecimal", key: "," })
|
|
);
|
|
await nextTick();
|
|
assert.strictEqual(floatInput.value, "0🇧🇪4🇧🇪🇧🇪");
|
|
|
|
integerInput.dispatchEvent(
|
|
new KeyboardEvent("keydown", { code: "NumpadDecimal", key: "." })
|
|
);
|
|
integerInput.dispatchEvent(
|
|
new KeyboardEvent("keydown", { code: "NumpadDecimal", key: "," })
|
|
);
|
|
await nextTick();
|
|
assert.strictEqual(integerInput.value, "10🇧🇪🇧🇪");
|
|
|
|
monetaryInput.dispatchEvent(
|
|
new KeyboardEvent("keydown", { code: "NumpadDecimal", key: "." })
|
|
);
|
|
monetaryInput.dispatchEvent(
|
|
new KeyboardEvent("keydown", { code: "NumpadDecimal", key: "," })
|
|
);
|
|
await nextTick();
|
|
assert.strictEqual(monetaryInput.value, "9🇧🇪99🇧🇪🇧🇪");
|
|
|
|
percentageInput.dispatchEvent(
|
|
new KeyboardEvent("keydown", { code: "NumpadDecimal", key: "." })
|
|
);
|
|
percentageInput.dispatchEvent(
|
|
new KeyboardEvent("keydown", { code: "NumpadDecimal", key: "," })
|
|
);
|
|
await nextTick();
|
|
assert.strictEqual(percentageInput.value, "99🇧🇪🇧🇪");
|
|
|
|
progressbarInput.focus();
|
|
await nextTick();
|
|
|
|
// When the input is focused, we get the length of the input value to be
|
|
// able to set the cursor position at the end of the value.
|
|
const length = progressbarInput.value.length;
|
|
|
|
// Make sure that the cursor position is at the end of the value.
|
|
progressbarInput.setSelectionRange(length, length);
|
|
progressbarInput.dispatchEvent(
|
|
new KeyboardEvent("keydown", { code: "NumpadDecimal", key: "." })
|
|
);
|
|
progressbarInput.dispatchEvent(
|
|
new KeyboardEvent("keydown", { code: "NumpadDecimal", key: "," })
|
|
);
|
|
await nextTick();
|
|
assert.strictEqual(progressbarInput.value, "0🇧🇪44🇧🇪🇧🇪");
|
|
}
|
|
);
|
|
|
|
QUnit.test(
|
|
"Numeric fields: NumpadDecimal key is different from the decimalPoint",
|
|
async function (assert) {
|
|
await makeView({
|
|
serverData,
|
|
type: "form",
|
|
resModel: "partner",
|
|
arch: /*xml*/ `
|
|
<form>
|
|
<field name="float_factor_field" options="{'factor': 0.5}"/>
|
|
<field name="qux"/>
|
|
<field name="int_field"/>
|
|
<field name="monetary"/>
|
|
<field name="currency_id" invisible="1"/>
|
|
<field name="percentage"/>
|
|
<field name="progressbar" widget="progressbar" options="{'editable': true, 'max_value': 'qux', 'edit_max_value': true}"/>
|
|
</form>`,
|
|
resId: 1,
|
|
});
|
|
|
|
// Get all inputs
|
|
const floatFactorField = target.querySelector(".o_field_float_factor input");
|
|
const floatInput = target.querySelector(".o_field_float input");
|
|
const integerInput = target.querySelector(".o_field_integer input");
|
|
const monetaryInput = target.querySelector(".o_field_monetary input");
|
|
const percentageInput = target.querySelector(".o_field_percentage input");
|
|
const progressbarInput = target.querySelector(".o_field_progressbar input");
|
|
|
|
/**
|
|
* Common assertion steps are extracted in this procedure.
|
|
*
|
|
* @param {object} params
|
|
* @param {HTMLInputElement} params.el
|
|
* @param {[number, number]} params.selectionRange
|
|
* @param {string} params.expectedValue
|
|
* @param {string} params.msg
|
|
*/
|
|
async function testInputElementOnNumpadDecimal(params) {
|
|
const { el, selectionRange, expectedValue, msg } = params;
|
|
|
|
el.focus();
|
|
await nextTick();
|
|
|
|
el.setSelectionRange(...selectionRange);
|
|
const numpadDecimalEvent = new KeyboardEvent("keydown", {
|
|
code: "NumpadDecimal",
|
|
key: ".",
|
|
});
|
|
numpadDecimalEvent.preventDefault = () => assert.step("preventDefault");
|
|
el.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");
|
|
};
|
|
el.dispatchEvent(extraEvent);
|
|
await nextTick();
|
|
|
|
// Selection range should be at 1 + the specified selection start.
|
|
assert.strictEqual(el.selectionStart, selectionRange[0] + 1);
|
|
assert.strictEqual(el.selectionEnd, selectionRange[0] + 1);
|
|
await nextTick();
|
|
assert.verifySteps(
|
|
["preventDefault"],
|
|
"NumpadDecimal event should be default prevented"
|
|
);
|
|
assert.strictEqual(el.value, expectedValue, msg);
|
|
}
|
|
|
|
await testInputElementOnNumpadDecimal({
|
|
el: floatFactorField,
|
|
selectionRange: [1, 3],
|
|
expectedValue: "5,0",
|
|
msg: "Float factor field from 5,00 to 5,0",
|
|
});
|
|
|
|
await testInputElementOnNumpadDecimal({
|
|
el: floatInput,
|
|
selectionRange: [0, 2],
|
|
expectedValue: ",4",
|
|
msg: "Float field from 0,4 to ,4",
|
|
});
|
|
|
|
await testInputElementOnNumpadDecimal({
|
|
el: integerInput,
|
|
selectionRange: [1, 2],
|
|
expectedValue: "1,",
|
|
msg: "Integer field from 10 to 1,",
|
|
});
|
|
|
|
await testInputElementOnNumpadDecimal({
|
|
el: monetaryInput,
|
|
selectionRange: [0, 3],
|
|
expectedValue: ",9",
|
|
msg: "Monetary field from 9,99 to ,9",
|
|
});
|
|
|
|
await testInputElementOnNumpadDecimal({
|
|
el: percentageInput,
|
|
selectionRange: [1, 1],
|
|
expectedValue: "9,9",
|
|
msg: "Percentage field from 99 to 9,9",
|
|
});
|
|
|
|
await testInputElementOnNumpadDecimal({
|
|
el: progressbarInput,
|
|
selectionRange: [1, 3],
|
|
expectedValue: "0,4",
|
|
msg: "Progressbar field 2 from 0,44 to 0,4",
|
|
});
|
|
}
|
|
);
|
|
|
|
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"));
|
|
}
|
|
);
|
|
});
|