diff --git a/app/controllers/admin/types_de_champ_controller.rb b/app/controllers/admin/types_de_champ_controller.rb
index 4a13b80e9..b50d4c2c0 100644
--- a/app/controllers/admin/types_de_champ_controller.rb
+++ b/app/controllers/admin/types_de_champ_controller.rb
@@ -16,9 +16,12 @@ class Admin::TypesDeChampController < AdminController
end
def update
- @procedure.update(TypesDeChampService.create_update_procedure_params params)
+ if @procedure.update(TypesDeChampService.create_update_procedure_params params)
+ flash.now.notice = 'Modifications sauvegardées'
+ else
+ flash.now.alert = @procedure.errors.full_messages.join(', ')
+ end
create_facade
- flash.now.notice = 'Modifications sauvegardées'
render 'show', format: :js
end
diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb
index 79aee06b4..33cfd914a 100644
--- a/app/models/type_de_champ.rb
+++ b/app/models/type_de_champ.rb
@@ -59,6 +59,17 @@ class TypeDeChamp < ApplicationRecord
before_validation :check_mandatory
before_save :remove_piece_justificative_template, if: -> { type_champ_changed? }
+ def valid?(context = nil)
+ super
+ if dynamic_type.present?
+ dynamic_type.valid?
+ errors.merge!(dynamic_type.errors)
+ end
+ errors.empty?
+ end
+
+ alias_method :validate, :valid?
+
def set_dynamic_type
@dynamic_type = type_champ.present? ? self.class.type_champ_to_class_name(type_champ).constantize.new(self) : nil
end
diff --git a/app/models/types_de_champ/linked_drop_down_list_type_de_champ.rb b/app/models/types_de_champ/linked_drop_down_list_type_de_champ.rb
index c1aa269aa..a90b7a0f0 100644
--- a/app/models/types_de_champ/linked_drop_down_list_type_de_champ.rb
+++ b/app/models/types_de_champ/linked_drop_down_list_type_de_champ.rb
@@ -3,6 +3,8 @@ class TypesDeChamp::LinkedDropDownListTypeDeChamp < TypesDeChamp::TypeDeChampBas
delegate :drop_down_list, to: :@type_de_champ
+ validate :check_presence_of_primary_options
+
def primary_options
primary_options = unpack_options.map(&:first)
if primary_options.present?
@@ -21,13 +23,19 @@ class TypesDeChamp::LinkedDropDownListTypeDeChamp < TypesDeChamp::TypeDeChampBas
private
+ def check_presence_of_primary_options
+ if !PRIMARY_PATTERN.match?(drop_down_list.options.second)
+ errors.add(libelle, "doit commencer par une entrée de menu primaire de la forme --texte--
")
+ end
+ end
+
def unpack_options
_, *options = drop_down_list.options
chunked = options.slice_before(PRIMARY_PATTERN)
chunked.map do |chunk|
primary, *secondary = chunk
secondary.unshift('')
- [PRIMARY_PATTERN.match(primary)[1], secondary]
+ [PRIMARY_PATTERN.match(primary)&.[](1), secondary]
end
end
end
diff --git a/app/models/types_de_champ/type_de_champ_base.rb b/app/models/types_de_champ/type_de_champ_base.rb
index a04f9d862..4f77596c3 100644
--- a/app/models/types_de_champ/type_de_champ_base.rb
+++ b/app/models/types_de_champ/type_de_champ_base.rb
@@ -1,4 +1,8 @@
class TypesDeChamp::TypeDeChampBase
+ include ActiveModel::Validations
+
+ delegate :libelle, to: :@type_de_champ
+
def initialize(type_de_champ)
@type_de_champ = type_de_champ
end
diff --git a/spec/factories/type_de_champ.rb b/spec/factories/type_de_champ.rb
index c208f5aac..363b79476 100644
--- a/spec/factories/type_de_champ.rb
+++ b/spec/factories/type_de_champ.rb
@@ -54,7 +54,7 @@ FactoryBot.define do
end
factory :type_de_champ_linked_drop_down_list do
type_champ { TypeDeChamp.type_champs.fetch(:linked_drop_down_list) }
- drop_down_list { create(:drop_down_list) }
+ drop_down_list { create(:drop_down_list, value: "--primary--\nsecondary\n") }
end
factory :type_de_champ_pays do
type_champ { TypeDeChamp.type_champs.fetch(:pays) }
diff --git a/spec/models/type_de_champ_shared_example.rb b/spec/models/type_de_champ_shared_example.rb
index f23cf2156..092527bbc 100644
--- a/spec/models/type_de_champ_shared_example.rb
+++ b/spec/models/type_de_champ_shared_example.rb
@@ -79,5 +79,26 @@ shared_examples 'type_de_champ_spec' do
end
end
end
+
+ context 'delegate validation to dynamic type' do
+ subject { build(:type_de_champ_text) }
+ let(:dynamic_type) do
+ Class.new(TypesDeChamp::TypeDeChampBase) do
+ validate :never_valid
+
+ def never_valid
+ errors.add(:troll, 'always invalid')
+ end
+ end.new(subject)
+ end
+
+ before { subject.instance_variable_set(:@dynamic_type, dynamic_type) }
+
+ it { is_expected.to be_invalid }
+ it do
+ subject.validate
+ expect(subject.errors.full_messages.to_sentence).to eq('Troll always invalid')
+ end
+ end
end
end
diff --git a/spec/models/types_de_champ/linked_drop_down_list_type_de_champ_spec.rb b/spec/models/types_de_champ/linked_drop_down_list_type_de_champ_spec.rb
index f9bc0af6a..b7ee29bdf 100644
--- a/spec/models/types_de_champ/linked_drop_down_list_type_de_champ_spec.rb
+++ b/spec/models/types_de_champ/linked_drop_down_list_type_de_champ_spec.rb
@@ -1,12 +1,78 @@
require 'spec_helper'
describe TypesDeChamp::LinkedDropDownListTypeDeChamp do
+ let(:drop_down_list) { build(:drop_down_list, value: menu_options) }
+ let(:type_de_champ) { build(:type_de_champ_linked_drop_down_list, drop_down_list: drop_down_list) }
+
+ subject { type_de_champ.dynamic_type }
+
+ describe 'validation' do
+ context 'It must start with one primary option' do
+ context 'valid menu' do
+ let(:menu_options) do
+ <<~END_OPTIONS
+ --Primary 1--
+ secondary 1.1
+ secondary 1.2
+ --Primary 2--
+ secondary 2.1
+ secondary 2.2
+ secondary 2.3
+ END_OPTIONS
+ end
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'degenerate but valid menu' do
+ let(:menu_options) do
+ <<~END_OPTIONS
+ --Primary 1--
+ END_OPTIONS
+ end
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'invalid menus' do
+ shared_examples 'missing primary option' do
+ it { is_expected.to be_invalid }
+ it do
+ subject.validate
+ expect(subject.errors.full_messages).to eq ["#{subject.libelle} doit commencer par une entrée de menu primaire de la forme --texte--
"]
+ end
+ end
+
+ context 'no primary option' do
+ let(:menu_options) do
+ <<~END_OPTIONS
+ secondary 1.1
+ secondary 1.2
+ END_OPTIONS
+ end
+
+ it_should_behave_like 'missing primary option'
+ end
+
+ context 'starting with secondary options' do
+ let(:menu_options) do
+ <<~END_OPTIONS
+ secondary 1.1
+ secondary 1.2
+ --Primary 2--
+ secondary 2.1
+ secondary 2.2
+ secondary 2.3
+ END_OPTIONS
+ end
+
+ it_should_behave_like 'missing primary option'
+ end
+ end
+ end
+ end
+
describe '#unpack_options' do
- let(:drop_down_list) { build(:drop_down_list, value: menu_options) }
- let(:type_de_champ) { build(:type_de_champ_linked_drop_down_list, drop_down_list: drop_down_list) }
-
- subject { type_de_champ.dynamic_type }
-
context 'with no options' do
let(:menu_options) { '' }
it { expect(subject.secondary_options).to eq({}) }