[IMP] base_iban: convert to owl
1) conversion to owl 2) very small improvement: - make the validation icon appears when starting edition closes odoo/odoo#100435 Task-id: 2857743 Signed-off-by: William André (wan) <wan@odoo.com>
This commit is contained in:
@@ -18,8 +18,8 @@ with a single statement.
|
||||
'demo': ['data/res_partner_bank_demo.xml'],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'base_iban/static/src/js/iban_widget.js',
|
||||
'base_iban/static/src/scss/iban_widget_view.scss',
|
||||
'base_iban/static/src/components/**/*',
|
||||
'base_iban/static/src/js/*.js',
|
||||
],
|
||||
'web.qunit_suite_tests': [
|
||||
'base_iban/static/src/tests/**/*',
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { CharField } from "@web/views/fields/char/char_field";
|
||||
import { useDebounced } from "@web/core/utils/timing";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
const { useState } = owl;
|
||||
export const DELAY = 400;
|
||||
|
||||
export class IbanWidget extends CharField {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.state = useState({ isValidIBAN: null });
|
||||
this.orm = useService("orm");
|
||||
this.validateIbanDebounced = useDebounced(async (ev) => {
|
||||
const iban = ev.target.value;
|
||||
if (!iban) {
|
||||
this.state.isValidIBAN = null;
|
||||
} else if (!/[A-Za-z]{2}.{3,}/.test(iban)) {
|
||||
this.state.isValidIBAN = false;
|
||||
} else {
|
||||
this.state.isValidIBAN = await this.orm.call("res.partner.bank", "check_iban", [[], iban]);
|
||||
}
|
||||
}, DELAY);
|
||||
}
|
||||
}
|
||||
IbanWidget.template = "base_iban.iban";
|
||||
|
||||
registry.category("fields").add("iban", IbanWidget);
|
||||
+2
-3
@@ -4,13 +4,12 @@
|
||||
.o_iban {
|
||||
display: inline-flex;
|
||||
margin-left: -15px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.o_iban_fail {
|
||||
cursor: help;
|
||||
}
|
||||
.o_iban_input_with_validator {
|
||||
padding-right: 20px!important;
|
||||
input.o_input.o_iban_input_with_validator {
|
||||
padding-right: 17px!important;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
<t t-name="base_iban.iban" t-inherit="web.CharField" owl="1">
|
||||
<xpath expr="//input" position="attributes">
|
||||
<attribute name="class" add="o_iban_input_with_validator" separator=" "/>
|
||||
<attribute name="t-on-input">validateIbanDebounced</attribute>
|
||||
<attribute name="t-on-focus">validateIbanDebounced</attribute>
|
||||
<attribute name="t-on-blur">(ev) => { this.state.isValidIBAN = null; }</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//input" position="after">
|
||||
<t t-if="this.state.isValidIBAN === true">
|
||||
<i class="fa fa-check o_iban text-success"/>
|
||||
</t>
|
||||
<t t-elif="this.state.isValidIBAN === false">
|
||||
<i class="fa fa-times o_iban text-danger o_iban_fail" title="Account isn't a valid IBAN"/>
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,5 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { addModelNamesToFetch } from '@bus/../tests/helpers/model_definitions_helpers';
|
||||
|
||||
addModelNamesToFetch(['res.partner.bank']);
|
||||
@@ -1,65 +1,94 @@
|
||||
odoo.define('base_iban.iban_widget_tests', function (require) {
|
||||
"use strict";
|
||||
/** @odoo-module **/
|
||||
import { start, startServer } from '@mail/../tests/helpers/test_utils';
|
||||
import { click, clickEdit, clickSave, editInput, getFixture } from "@web/../tests/helpers/utils";
|
||||
import { DELAY } from "@base_iban/components/iban_widget/iban_widget";
|
||||
|
||||
var FormView = require('web.FormView');
|
||||
var testUtils = require('web.test_utils');
|
||||
|
||||
var createView = testUtils.createView;
|
||||
QUnit.module('Fields', {}, function () {
|
||||
QUnit.module("IbanWidget");
|
||||
const [validIban, invalidIban] = ["BE12651194580992", "invalidIban!"];
|
||||
|
||||
QUnit.module('fields', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
partner: {
|
||||
fields: {
|
||||
acc_number: {string: "acc_number", type: "char"},
|
||||
},
|
||||
records: [{
|
||||
id: 1,
|
||||
acc_number: "",
|
||||
}]
|
||||
const openPreparedView = async (assert, validIbanList, startingAccNumber) => {
|
||||
const target = getFixture();
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv['res.partner'].create([
|
||||
{
|
||||
name: "Awesome partner",
|
||||
bank_ids: [
|
||||
pyEnv['res.partner.bank'].create([{ acc_number: startingAccNumber }]),
|
||||
],
|
||||
},
|
||||
]);
|
||||
const views = {
|
||||
'res.partner,false,form':
|
||||
`<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
</group>
|
||||
<field name="bank_ids">
|
||||
<tree editable="bottom">
|
||||
<field name="acc_number" widget="iban"/>
|
||||
</tree>
|
||||
</field>
|
||||
</sheet>
|
||||
</form>`,
|
||||
};
|
||||
// patch _.debounce to be fast and synchronous
|
||||
this.underscoreDebounce = _.debounce;
|
||||
_.debounce = _.identity;
|
||||
},
|
||||
afterEach: function () {
|
||||
// unpatch _.debounce
|
||||
_.debounce = this.underscoreDebounce;
|
||||
}
|
||||
}, function () {
|
||||
|
||||
QUnit.module('IbanWidget');
|
||||
|
||||
QUnit.test('Iban widgets are correctly rendered', async function (assert) {
|
||||
assert.expect(6);
|
||||
var form = await createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form><sheet><field name="acc_number" widget="iban"/></sheet></form>',
|
||||
const { advanceTime, afterNextRender, openView } = await start({
|
||||
serverData: { views },
|
||||
hasTimeControl: true,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'check_iban') {
|
||||
console.log(args.args[1] === "BE00 0000 0000 0000 0000")
|
||||
return Promise.resolve(args.args[1] === "BE00 0000 0000 0000 0000");
|
||||
if (args.method === "check_iban") {
|
||||
const iban = args.args[1].replace(/\s/g, '');
|
||||
return Promise.resolve(iban === validIban);
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
await openView({
|
||||
res_id: partnerId,
|
||||
res_model: 'res.partner',
|
||||
views: [[false, 'form']],
|
||||
});
|
||||
return { target, advanceTime, afterNextRender };
|
||||
};
|
||||
|
||||
await testUtils.fields.editAndTrigger(form.$('.o_field_widget'), "BE00", 'input');
|
||||
assert.containsOnce(form, '.o_iban_fail', "Should be a False account, it's too short");
|
||||
assert.containsOnce(form, '.fa-times', "Should have a cross pictogram");
|
||||
QUnit.test('Iban Widget full flow', async assert => {
|
||||
const { target, advanceTime, afterNextRender } = await openPreparedView(assert, [validIban], "");
|
||||
|
||||
await testUtils.fields.editAndTrigger(form.$('.o_field_widget'), "BE00 0000 0000 0000 0000", 'input');
|
||||
assert.containsOnce(form, '.text-success', "Should have text-success");
|
||||
assert.containsOnce(form, '.fa-check', "Should have a valid pictogram");
|
||||
assert.containsNone(target, ".o_iban", "Shouldn't display any validation icon while not editing");
|
||||
await clickEdit(target);
|
||||
assert.containsNone(target, ".o_iban",
|
||||
"Shouldn't display any validation icon while not editing a specific line");
|
||||
|
||||
await testUtils.fields.editAndTrigger(form.$('.o_field_widget'), "BE00 xxxx xxxx xxxx xxxx", 'input');
|
||||
assert.containsOnce(form, '.o_iban_fail', "Should be False account");
|
||||
assert.containsOnce(form, '.fa-times', "Should have a cross pictogram");
|
||||
await click(target, "td.o_iban_cell");
|
||||
assert.containsNone(target, ".o_iban", "Shouldn't display any validation icon while iban is empty");
|
||||
|
||||
form.destroy();
|
||||
await editInput(target, ".o_iban_cell .o_input", invalidIban);
|
||||
assert.containsNone(target, ".o_iban", "Shouldn't change its state of display before edition is finished");
|
||||
await afterNextRender(() => advanceTime(DELAY));
|
||||
assert.containsOnce(target, ".o_iban", "Should contain a validation icon 400ms after edition");
|
||||
assert.containsOnce(target, "i.fa.fa-times.o_iban_fail", "The validation icon should be the failed one");
|
||||
assert.containsNone(target, "i.fa.fa-check.o_iban", "The validation icon shouldn't be the successful one");
|
||||
|
||||
await clickSave(target);
|
||||
assert.containsNone(target, ".o_iban", "Shouldn't display any validation while not editing");
|
||||
|
||||
await clickEdit(target);
|
||||
assert.containsNone(target, ".o_iban",
|
||||
"Shouldn't display any validation icon while not editing a specific line");
|
||||
|
||||
await click(target, "td.o_iban_cell");
|
||||
await afterNextRender(() => advanceTime(DELAY));
|
||||
assert.containsOnce(target, "i.fa.fa-times.o_iban_fail", "The validation icon should be present while clicking on an already filled IBAN");
|
||||
|
||||
await editInput(target, ".o_iban_cell .o_input", validIban);
|
||||
assert.containsOnce(target, "i.fa.fa-times.o_iban_fail", "The validation icon shouldn't change during the edition");
|
||||
await afterNextRender(() => advanceTime(DELAY));
|
||||
assert.containsOnce(target, ".o_iban", "Should contain a validation icon 400ms after edition");
|
||||
assert.containsOnce(target, "i.fa.fa-check.o_iban", "The validation icon should be the successful one");
|
||||
assert.containsNone(target, "i.fa.fa-times.o_iban_fail", "The validation icon shouldn't be the failed one");
|
||||
|
||||
await clickSave(target);
|
||||
assert.containsNone(target, ".o_iban", "Shouldn't display any validation while not editing");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user