Merge pull request #9516 from colinux/fix-invalid-facultative-evaporation
ETQ usager je ne veux pas pouvoir saisir un nombre invalide sans feedback
This commit is contained in:
commit
2cec44109d
14 changed files with 143 additions and 6 deletions
|
@ -296,6 +296,8 @@
|
|||
input[type=email],
|
||||
input[type=password],
|
||||
input[type=number],
|
||||
input[inputmode=numeric],
|
||||
input[inputmode=decimal],
|
||||
input[type=tel] {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
@ -315,6 +317,8 @@
|
|||
&[type='date'],
|
||||
&[type='tel'],
|
||||
&[type='number'],
|
||||
&[inputmode='numeric'],
|
||||
&[inputmode='decimal'],
|
||||
&[type='datetime-local'] {
|
||||
width: 33.33%;
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
= @form.number_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, step: :any, required: @champ.required?))
|
||||
= @form.text_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, required: @champ.required?, pattern: "-?[0-9]+([\.,][0-9]{1,3})?", inputmode: :decimal, data: { controller: 'format decimal-number-input', format: :decimal }))
|
||||
|
|
|
@ -1 +1 @@
|
|||
= @form.number_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, placeholder: 5, required: @champ.required?))
|
||||
= @form.text_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, pattern: "[0-9]*", inputmode: :numeric, required: @champ.required?, data: { controller: 'format', format: :integer }))
|
||||
|
|
|
@ -1 +1 @@
|
|||
= @form.number_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, placeholder: @champ.libelle, required: @champ.required?))
|
||||
= @form.text_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, placeholder: @champ.libelle, required: @champ.required?, pattern: "[0-9]*", inputmode: :decimal))
|
||||
|
|
|
@ -205,6 +205,8 @@ export class AutosaveController extends ApplicationController {
|
|||
formData.append(input.name, input.value);
|
||||
}
|
||||
} else {
|
||||
// NOTE: some type inputs (like number) have an empty input.value
|
||||
// when the filled value is invalid (not a number) so we avoid them
|
||||
formData.append(input.name, input.value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import { ApplicationController } from './application_controller';
|
||||
|
||||
export class DecimalNumberInputController extends ApplicationController {
|
||||
connect() {
|
||||
const value = this.inputElement.value;
|
||||
|
||||
if (value) {
|
||||
this.formatValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
formatValue(value: string) {
|
||||
const number = parseFloat(value);
|
||||
|
||||
if (isNaN(number)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.inputElement.value = number.toLocaleString();
|
||||
this.emitInputEvent(); // trigger format controller
|
||||
}
|
||||
|
||||
private get inputElement(): HTMLInputElement {
|
||||
return this.element as HTMLInputElement;
|
||||
}
|
||||
|
||||
private emitInputEvent() {
|
||||
const event = new InputEvent('input', {
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
|
||||
this.inputElement.dispatchEvent(event);
|
||||
}
|
||||
}
|
|
@ -21,6 +21,13 @@ export class FormatController extends ApplicationController {
|
|||
const target = event.target as HTMLInputElement;
|
||||
target.value = this.formatInteger(target.value);
|
||||
});
|
||||
break;
|
||||
case 'decimal':
|
||||
this.on('input', (event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
target.value = this.formatDecimal(target.value);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,4 +46,14 @@ export class FormatController extends ApplicationController {
|
|||
private formatInteger(value: string) {
|
||||
return value.replace(/[^\d]/g, '');
|
||||
}
|
||||
|
||||
private formatDecimal(value: string) {
|
||||
// Le séparateur de décimales est toujours après le séparateur de milliers (un point ou une virgule).
|
||||
// S'il n'y a qu'un seul séparateur, on considère que c'est celui des décimales.
|
||||
// S'il n'y en a pas, ça n'a pas d'effet.
|
||||
const decimalSeparator =
|
||||
value.lastIndexOf(',') > value.lastIndexOf('.') ? ',' : '.';
|
||||
|
||||
return value.replace(new RegExp(`[^\\d${decimalSeparator}]`, 'g'), '');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
class Champs::DecimalNumberChamp < Champ
|
||||
before_validation :format_value
|
||||
validates :value, numericality: {
|
||||
allow_nil: true,
|
||||
allow_blank: true,
|
||||
|
@ -17,7 +18,15 @@ class Champs::DecimalNumberChamp < Champ
|
|||
|
||||
private
|
||||
|
||||
def format_value
|
||||
return if value.blank?
|
||||
|
||||
self.value = value.tr(",", ".")
|
||||
end
|
||||
|
||||
def processed_value
|
||||
return if invalid?
|
||||
|
||||
value&.to_f
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,6 +20,8 @@ class Champs::IntegerNumberChamp < Champ
|
|||
private
|
||||
|
||||
def processed_value
|
||||
return if invalid?
|
||||
|
||||
value&.to_i
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,4 +3,4 @@ en:
|
|||
attributes:
|
||||
champs/decimal_number_champ:
|
||||
hints:
|
||||
value: "You can enter up to 3 decimal places after the decimal point. Exemple: 3.14"
|
||||
value: "You can enter up to 3 decimal places after the decimal point. Exemple: 3.141"
|
||||
|
|
|
@ -3,4 +3,4 @@ fr:
|
|||
attributes:
|
||||
champs/decimal_number_champ:
|
||||
hints:
|
||||
value: "Vous pouvez saisir jusqu’à 3 décimales après la virgule. Exemple: 3,14"
|
||||
value: "Vous pouvez saisir jusqu’à 3 décimales après la virgule. Exemple: 3,141"
|
||||
|
|
|
@ -694,6 +694,36 @@ describe Users::DossiersController, type: :controller do
|
|||
it { expect(first_champ.reload.value).to eq('beautiful value') }
|
||||
it { expect(response).to have_http_status(:ok) }
|
||||
end
|
||||
|
||||
context 'decimal number champ separator' do
|
||||
let (:procedure) { create(:procedure, :published, types_de_champ_public: [{ type: :decimal_number }]) }
|
||||
let (:submit_payload) do
|
||||
{
|
||||
id: dossier.id,
|
||||
dossier: {
|
||||
champs_public_attributes: { first_champ.id => { id: first_champ.id, value: value } }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
context 'when spearator is dot' do
|
||||
let(:value) { '3.14' }
|
||||
|
||||
it "saves the value" do
|
||||
subject
|
||||
expect(first_champ.reload.value).to eq('3.14')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when spearator is comma' do
|
||||
let(:value) { '3,14' }
|
||||
|
||||
it "saves the value" do
|
||||
subject
|
||||
expect(first_champ.reload.value).to eq('3.14')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update en_construction' do
|
||||
|
|
|
@ -39,6 +39,12 @@ describe Logic::ChampValue do
|
|||
|
||||
it { is_expected.to be nil }
|
||||
end
|
||||
|
||||
context 'with invalid value' do
|
||||
before { champ.value = 'environ 300' }
|
||||
|
||||
it { is_expected.to be nil }
|
||||
end
|
||||
end
|
||||
|
||||
context 'decimal tdc' do
|
||||
|
@ -46,6 +52,12 @@ describe Logic::ChampValue do
|
|||
|
||||
it { expect(champ_value(champ.stable_id).type([champ.type_de_champ])).to eq(:number) }
|
||||
it { is_expected.to eq(42.01) }
|
||||
|
||||
context 'with invalid value' do
|
||||
before { champ.value = 'racine de 2' }
|
||||
|
||||
it { is_expected.to be nil }
|
||||
end
|
||||
end
|
||||
|
||||
context 'dropdown tdc' do
|
||||
|
|
|
@ -144,7 +144,13 @@ describe 'The user' do
|
|||
end.to change { Champ.count }
|
||||
end
|
||||
|
||||
let(:simple_procedure) { create(:procedure, :published, :for_individual, types_de_champ_public: [{ mandatory: true, libelle: 'texte obligatoire' }, { mandatory: false, libelle: 'texte optionnel' }]) }
|
||||
let(:simple_procedure) {
|
||||
create(:procedure, :published, :for_individual, types_de_champ_public: [
|
||||
{ mandatory: true, libelle: 'texte obligatoire' }, { mandatory: false, libelle: 'texte optionnel' },
|
||||
{ mandatory: false, libelle: "nombre entier", type: :integer_number },
|
||||
{ mandatory: false, libelle: "nombre décimal", type: :decimal_number }
|
||||
])
|
||||
}
|
||||
|
||||
scenario 'save an incomplete dossier as draft but cannot not submit it', js: true, retry: 3 do
|
||||
log_in(user, simple_procedure)
|
||||
|
@ -170,6 +176,26 @@ describe 'The user' do
|
|||
expect(page).to have_current_path(merci_dossier_path(user_dossier))
|
||||
end
|
||||
|
||||
scenario 'numbers champs formatting', js: true, retry: 3 do
|
||||
log_in(user, simple_procedure)
|
||||
fill_individual
|
||||
|
||||
fill_in('nombre entier', with: '300 environ')
|
||||
wait_until {
|
||||
champ_value_for('nombre entier') == '300'
|
||||
}
|
||||
|
||||
fill_in('nombre décimal', with: '123 456,78')
|
||||
wait_until {
|
||||
champ_value_for('nombre décimal') == '123456.78'
|
||||
}
|
||||
|
||||
fill_in('nombre décimal', with: '1,234.56')
|
||||
wait_until {
|
||||
champ_value_for('nombre décimal') == '1234.56'
|
||||
}
|
||||
end
|
||||
|
||||
scenario 'extends dossier experation date more than one time, ', js: true, retry: 3 do
|
||||
simple_procedure.update(procedure_expires_when_termine_enabled: true)
|
||||
user_old_dossier = create(:dossier,
|
||||
|
|
Loading…
Add table
Reference in a new issue