feat(champ-numbers): format value in input to a backend compatible value

This commit is contained in:
Colin Darie 2023-09-26 16:31:35 +02:00
parent 123114be81
commit 75bf30bed2
No known key found for this signature in database
GPG key ID: 8C76CADD40253590
7 changed files with 108 additions and 10 deletions

View file

@ -1 +1 @@
= @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))
= @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 }))

View file

@ -1 +1 @@
= @form.text_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, pattern: "[0-9]*", inputmode: :numeric, 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 }))

View file

@ -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);
}
}

View file

@ -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'), '');
}
}

View file

@ -1,4 +1,5 @@
class Champs::DecimalNumberChamp < Champ
before_validation :format_value
validates :value, numericality: {
allow_nil: true,
allow_blank: true,
@ -17,6 +18,12 @@ class Champs::DecimalNumberChamp < Champ
private
def format_value
return if value.blank?
self.value = value.tr(",", ".")
end
def processed_value
return if invalid?

View file

@ -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

View file

@ -146,7 +146,9 @@ describe 'The user' do
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", type: :integer_number }
{ 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 }
])
}
@ -174,17 +176,24 @@ describe 'The user' do
expect(page).to have_current_path(merci_dossier_path(user_dossier))
end
scenario 'validates invalid number', js: true, retry: 3 do
scenario 'numbers champs formatting', js: true, retry: 3 do
log_in(user, simple_procedure)
fill_individual
# Check an incomplete dossier can be saved as a draft, even when mandatory fields are missing
fill_in('nombre', with: 'environ 300')
wait_for_autosave
fill_in('nombre entier', with: '300 environ')
wait_until {
champ_value_for('nombre entier') == '300'
}
within ".fr-message--error" do
expect(page).to have_content("doit être un nombre entier")
end
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