Merge pull request #7522 from betagouv/add_condition_component
feature: ajout du composant d'édition des conditons
This commit is contained in:
commit
745d0809a2
14 changed files with 665 additions and 2 deletions
86
app/assets/stylesheets/conditions_component.scss
Normal file
86
app/assets/stylesheets/conditions_component.scss
Normal file
|
@ -0,0 +1,86 @@
|
|||
@import "colors";
|
||||
@import "constants";
|
||||
|
||||
.conditionnel {
|
||||
|
||||
.condition-error {
|
||||
background: $background-red;
|
||||
margin: ($default-spacer) (-$default-spacer);
|
||||
|
||||
ul {
|
||||
padding: $default-spacer;
|
||||
}
|
||||
}
|
||||
|
||||
.condition-table {
|
||||
table-layout: fixed;
|
||||
|
||||
.far-left {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.target {
|
||||
width: 350px;
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.operator {
|
||||
width: 250px;
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.value {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.delete-column {
|
||||
width: 50px;
|
||||
|
||||
button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
padding: $default-spacer;
|
||||
|
||||
}
|
||||
|
||||
td {
|
||||
padding: $default-spacer;
|
||||
|
||||
input,
|
||||
select {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
display: inline-block;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
input.alert,
|
||||
select.alert {
|
||||
border-color: $dark-red;
|
||||
}
|
||||
}
|
||||
|
||||
.add-row {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
line-height: 25px;
|
||||
margin: 0 $default-padding $default-padding;
|
||||
}
|
||||
}
|
|
@ -93,7 +93,7 @@
|
|||
|
||||
.flex {
|
||||
&.section {
|
||||
padding: 10px 10px 0 10px;
|
||||
padding: $default-spacer $default-spacer 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
|
|
218
app/components/types_de_champ_editor/conditions_component.rb
Normal file
218
app/components/types_de_champ_editor/conditions_component.rb
Normal file
|
@ -0,0 +1,218 @@
|
|||
class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
|
||||
include Logic
|
||||
|
||||
def initialize(tdc:, upper_tdcs:, procedure_id:)
|
||||
@tdc, @condition, @upper_tdcs = tdc, tdc.condition, upper_tdcs
|
||||
@procedure_id = procedure_id
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def rows
|
||||
condition_per_row.map { |c| Logic.split_condition(c) }
|
||||
end
|
||||
|
||||
def condition_per_row
|
||||
if [And, Or].include?(@condition.class)
|
||||
@condition.operands
|
||||
else
|
||||
[@condition].compact
|
||||
end
|
||||
end
|
||||
|
||||
def logic_conditionnel_button
|
||||
if @condition.nil?
|
||||
submit_tag(t('.enable_conditionnel'), formaction: add_row_admin_procedure_condition_path(@procedure_id, @tdc.id))
|
||||
else
|
||||
submit_tag(
|
||||
t('.disable_conditionnel'),
|
||||
formmethod: 'delete',
|
||||
formnovalidate: true,
|
||||
data: { confirm: t('.disable_conditionnel_alert') }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def far_left_tag(row_number)
|
||||
if row_number == 0
|
||||
t('.display_if')
|
||||
elsif row_number == 1
|
||||
select_tag(
|
||||
"#{input_prefix}[top_operator_name]",
|
||||
options_for_select(options_for_far_left_tag, @condition.class.name)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def options_for_far_left_tag
|
||||
[And, Or]
|
||||
.map(&:name)
|
||||
.map { |name| [t(name, scope: 'logic.operators'), name] }
|
||||
end
|
||||
|
||||
def left_operand_tag(targeted_champ, row_index)
|
||||
# current_target can be invalid if
|
||||
# - its type has changed : number -> carto
|
||||
# - it has been removed
|
||||
# - it has been put lower in the form
|
||||
current_target_valid = targets_for_select.map(&:second).include?(targeted_champ.to_json)
|
||||
|
||||
selected_target = current_target_valid ? targeted_champ.to_json : empty.to_json
|
||||
|
||||
select_tag(
|
||||
input_name_for('targeted_champ'),
|
||||
options_for_select(targets_for_select, selected_target),
|
||||
onchange: "this.form.action = this.form.action + '/change_targeted_champ?row_index=#{row_index}'",
|
||||
id: input_id_for('targeted_champ', row_index),
|
||||
class: { alert: !current_target_valid }
|
||||
)
|
||||
end
|
||||
|
||||
def targets_for_select
|
||||
empty_target_for_select + available_targets_for_select
|
||||
end
|
||||
|
||||
def empty_target_for_select
|
||||
[[t('.select'), empty.to_json]]
|
||||
end
|
||||
|
||||
def available_targets_for_select
|
||||
@upper_tdcs
|
||||
.filter { |tdc| ChampValue::MANAGED_TYPE_DE_CHAMP.values.include?(tdc.type_champ) }
|
||||
.map { |tdc| [tdc.libelle, champ_value(tdc.stable_id).to_json] }
|
||||
end
|
||||
|
||||
def operator_tag(operator_name, targeted_champ, row_index)
|
||||
operators_for_select = compatibles_operators_for_select(targeted_champ)
|
||||
|
||||
current_operator_invalid = !operators_for_select.map(&:second).include?(operator_name)
|
||||
|
||||
if current_operator_invalid
|
||||
operators_for_select = [[t('.select'), EmptyOperator.name]] + operators_for_select
|
||||
end
|
||||
|
||||
select_tag(
|
||||
input_name_for('operator_name'),
|
||||
options_for_select(operators_for_select, selected: operator_name),
|
||||
id: input_id_for('operator_name', row_index),
|
||||
class: { alert: current_operator_invalid }
|
||||
)
|
||||
end
|
||||
|
||||
def compatibles_operators_for_select(left)
|
||||
case left.type
|
||||
when ChampValue::CHAMP_VALUE_TYPE.fetch(:boolean)
|
||||
[
|
||||
[t('is', scope: 'logic'), Eq.name]
|
||||
]
|
||||
when ChampValue::CHAMP_VALUE_TYPE.fetch(:empty)
|
||||
[
|
||||
[t('is', scope: 'logic'), EmptyOperator.name]
|
||||
]
|
||||
when ChampValue::CHAMP_VALUE_TYPE.fetch(:enum)
|
||||
[
|
||||
[t('is', scope: 'logic'), Eq.name]
|
||||
]
|
||||
when ChampValue::CHAMP_VALUE_TYPE.fetch(:number)
|
||||
[Eq, LessThan, GreaterThan, LessThanEq, GreaterThanEq]
|
||||
.map(&:name)
|
||||
.map { |name| [t(name, scope: 'logic.operators'), name] }
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def right_operand_tag(left, right, row_index)
|
||||
right_invalid = !current_right_valid?(left, right)
|
||||
|
||||
case left.type
|
||||
when :boolean
|
||||
booleans_for_select = [[t('utils.yes'), constant(true).to_json], [t('utils.no'), constant(false).to_json]]
|
||||
|
||||
if right_invalid
|
||||
booleans_for_select = empty_target_for_select + booleans_for_select
|
||||
end
|
||||
|
||||
select_tag(
|
||||
input_name_for('value'),
|
||||
options_for_select(booleans_for_select, right.to_json),
|
||||
id: input_id_for('value', row_index),
|
||||
class: { alert: right_invalid }
|
||||
)
|
||||
when :empty
|
||||
select_tag(
|
||||
input_name_for('value'),
|
||||
options_for_select(empty_target_for_select),
|
||||
id: input_id_for('value', row_index)
|
||||
)
|
||||
when :enum
|
||||
enums_for_select = left.options
|
||||
|
||||
if right_invalid
|
||||
enums_for_select = empty_target_for_select + enums_for_select
|
||||
end
|
||||
|
||||
select_tag(
|
||||
input_name_for('value'),
|
||||
options_for_select(enums_for_select, right.value),
|
||||
id: input_id_for('value', row_index),
|
||||
class: { alert: right_invalid }
|
||||
)
|
||||
when :number
|
||||
number_field_tag(
|
||||
input_name_for('value'),
|
||||
right.value,
|
||||
required: true,
|
||||
id: input_id_for('value', row_index),
|
||||
class: { alert: right_invalid }
|
||||
)
|
||||
else
|
||||
number_field_tag(input_name_for('value'), '', id: input_id_for('value', row_index))
|
||||
end
|
||||
end
|
||||
|
||||
def current_right_valid?(left, right)
|
||||
case [left.type, right.type]
|
||||
in [:boolean, :boolean] | [:number, :number] | [:empty, :empty]
|
||||
true
|
||||
in [:enum, :string]
|
||||
left.options.include?(right.value)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def add_condition_tag
|
||||
tag.button(
|
||||
tag.span('', class: 'icon add') + tag.span(t('.add_condition')),
|
||||
formaction: add_row_admin_procedure_condition_path(@procedure_id, @tdc.id),
|
||||
formnovalidate: true,
|
||||
class: 'add-row'
|
||||
)
|
||||
end
|
||||
|
||||
def delete_condition_tag(row_index)
|
||||
tag.button(
|
||||
tag.span('', class: 'icon delete') + tag.span(t('.remove_a_row'), class: 'sr-only'),
|
||||
formaction: delete_row_admin_procedure_condition_path(@procedure_id, @tdc.id, row_index: row_index),
|
||||
formmethod: 'delete',
|
||||
formnovalidate: true
|
||||
)
|
||||
end
|
||||
|
||||
def render?
|
||||
@condition.present? || available_targets_for_select.any?
|
||||
end
|
||||
|
||||
def input_name_for(name)
|
||||
"#{input_prefix}[rows][][#{name}]"
|
||||
end
|
||||
|
||||
def input_id_for(name, row_index)
|
||||
"#{@tdc.id}-#{name}-#{row_index}"
|
||||
end
|
||||
|
||||
def input_prefix
|
||||
'type_de_champ[condition_form]'
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
fr:
|
||||
display_if: Afficher si
|
||||
enable_conditionnel: cliquer pour activer
|
||||
disable_conditionnel: cliquer pour désactiver
|
||||
disable_conditionnel_alert: "La logique conditionnelle appliquée à ce champ sera désactivé.\nVoulez-vous continuer ?"
|
||||
select: Sélectionner
|
||||
add_condition: Ajouter une condition
|
||||
remove_a_row: Supprimer la ligne
|
|
@ -0,0 +1,28 @@
|
|||
.flex.justify-start.section{ id: dom_id(@tdc, :conditions) }
|
||||
= form_with url: admin_procedure_condition_path(@procedure_id, @tdc), method: :patch, class: 'form width-100' do |f|
|
||||
.conditionnel.mt-2.width-100
|
||||
.flex
|
||||
%p.mr-2 Logique conditionnelle
|
||||
= logic_conditionnel_button
|
||||
|
||||
= render TypesDeChampEditor::ConditionsErrorsComponent.new(conditions: condition_per_row, upper_tdcs: @upper_tdcs)
|
||||
|
||||
- if @condition.present?
|
||||
%table.condition-table.mt-2.width-100
|
||||
%thead
|
||||
%tr
|
||||
%th.far-left
|
||||
%th.target Champ Cible
|
||||
%th.operator Opérateur
|
||||
%th.value Valeur
|
||||
%th.delete-column
|
||||
%tbody
|
||||
- rows.each.with_index do |(targeted_champ, operator_name, value), row_index|
|
||||
%tr
|
||||
%td.far-left= far_left_tag(row_index)
|
||||
%td.target= left_operand_tag(targeted_champ, row_index)
|
||||
%td.operator= operator_tag(operator_name, targeted_champ, row_index)
|
||||
%td.value= right_operand_tag(targeted_champ, value, row_index)
|
||||
%td.delete-column= delete_condition_tag(row_index)
|
||||
|
||||
.flex.justify-end.mt-2= add_condition_tag
|
|
@ -0,0 +1,40 @@
|
|||
class TypesDeChampEditor::ConditionsErrorsComponent < ApplicationComponent
|
||||
def initialize(conditions:, upper_tdcs:)
|
||||
@conditions, @upper_tdcs = conditions, upper_tdcs
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def errors
|
||||
@conditions
|
||||
.filter { |condition| condition.errors(@upper_tdcs.map(&:stable_id)).present? }
|
||||
.map { |condition| row_error(Logic.split_condition(condition)) }
|
||||
.uniq
|
||||
.map { |message| tag.li(message) }
|
||||
.then { |lis| tag.ul(lis.reduce(&:+)) }
|
||||
end
|
||||
|
||||
def row_error((left, operator_name, right))
|
||||
targeted_champ = @upper_tdcs.find { |tdc| tdc.stable_id == left.stable_id }
|
||||
|
||||
if targeted_champ.nil?
|
||||
t('not_available', scope: '.errors')
|
||||
elsif left.type == :unmanaged
|
||||
t('unmanaged', scope: '.errors',
|
||||
libelle: targeted_champ.libelle,
|
||||
type_champ: t(targeted_champ.type_champ, scope: '.type'))
|
||||
else
|
||||
t('incompatible', scope: '.errors',
|
||||
libelle: targeted_champ.libelle,
|
||||
type_champ: t(targeted_champ.type_champ, scope: '.type'),
|
||||
operator: t(operator_name, scope: 'logic.operators').downcase,
|
||||
right: right.to_s.downcase)
|
||||
end
|
||||
end
|
||||
|
||||
def render?
|
||||
@conditions
|
||||
.filter { |condition| condition.errors(@upper_tdcs.map(&:stable_id)).present? }
|
||||
.present?
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
fr:
|
||||
type:
|
||||
number: un nombre
|
||||
string: un texte
|
||||
boolean: soit oui, soit non
|
||||
yes_no: soit oui, soit non
|
||||
address: une adresse
|
||||
errors:
|
||||
not_available: "Un champ cible n'est plus disponible. Il est soit supprimé, soit déplacé en dessous de ce champ."
|
||||
unmanaged: "Le champ « %{libelle} » est %{type_champ} et ne peut pas être utilisé comme champ cible."
|
||||
incompatible: "Le champ « %{libelle} » est %{type_champ}. Il ne peut pas être %{operator} %{right}."
|
|
@ -0,0 +1,2 @@
|
|||
.condition-error
|
||||
= errors
|
|
@ -68,6 +68,10 @@ module Logic
|
|||
end
|
||||
end
|
||||
|
||||
def self.split_condition(condition)
|
||||
[condition.left, condition.class.name, condition.right]
|
||||
end
|
||||
|
||||
def ds_eq(left, right) = Logic::Eq.new(left, right)
|
||||
|
||||
def greater_than(left, right) = Logic::GreaterThan.new(left, right)
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
= favicon_link_tag(image_url("#{FAVICON_32PX_SRC}"), type: "image/png", sizes: "32x32")
|
||||
= favicon_link_tag(image_url("#{FAVICON_96PX_SRC}"), type: "image/png", sizes: "96x96")
|
||||
|
||||
= javascript_packs_with_chunks_tag 'application', defer: true
|
||||
= vite_javascript_tag 'application'
|
||||
|
||||
= preload_link_tag(asset_url("Muli-Regular.woff2"))
|
||||
= preload_link_tag(asset_url("Muli-Bold.woff2"))
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
fr:
|
||||
logic:
|
||||
empty: un membre vide
|
||||
is: Est
|
||||
operators:
|
||||
'Logic::LessThan': Inférieur à
|
||||
'Logic::LessThanEq': Inférieur ou égal à
|
||||
'Logic::Eq': Égal à
|
||||
'Logic::GreaterThan': Supérieur à
|
||||
'Logic::GreaterThanEq': Supérieur ou égal à
|
||||
'Logic::And': Et
|
||||
'Logic::Or': Ou
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
class TypesDeChampEditor::ConditionsComponentPreview < ViewComponent::Preview
|
||||
include Logic
|
||||
|
||||
def with_empty_condition
|
||||
tdc = TypeDeChamp.create(type_champ: :text, condition: empty_operator(empty, empty), libelle: 't')
|
||||
upper_tdcs = []
|
||||
|
||||
render TypesDeChampEditor::ConditionsComponent.new(
|
||||
tdc: tdc, upper_tdcs: upper_tdcs, procedure_id: '1'
|
||||
)
|
||||
end
|
||||
|
||||
def with_conditions
|
||||
surface = TypeDeChamp.create(type_champ: :integer_number, libelle: 'surface')
|
||||
appartement = TypeDeChamp.create(type_champ: :yes_no, libelle: 'appartement')
|
||||
type_appartement = TypeDeChamp.create(type_champ: :drop_down_list, libelle: 'type', drop_down_list_value: "T1\r\nT2\r\nT3")
|
||||
upper_tdcs = [surface, appartement, type_appartement]
|
||||
|
||||
condition = ds_and([
|
||||
greater_than_eq(champ_value(surface.stable_id), constant(50)),
|
||||
ds_eq(champ_value(appartement.stable_id), constant(true)),
|
||||
ds_eq(champ_value(type_appartement.stable_id), constant('T2'))
|
||||
])
|
||||
tdc = TypeDeChamp.create(type_champ: :integer_number, condition: condition, libelle: 'nb de piece')
|
||||
|
||||
render TypesDeChampEditor::ConditionsComponent.new(
|
||||
tdc: tdc, upper_tdcs: upper_tdcs, procedure_id: '1'
|
||||
)
|
||||
end
|
||||
|
||||
def with_errors
|
||||
surface = TypeDeChamp.create(type_champ: :integer_number, libelle: 'surface')
|
||||
address = TypeDeChamp.create(type_champ: :address, libelle: 'adresse')
|
||||
yes_non = TypeDeChamp.create(type_champ: :yes_no, libelle: 'oui/non')
|
||||
|
||||
upper_tdcs = [address, yes_non]
|
||||
|
||||
condition = ds_and([
|
||||
ds_eq(champ_value(address.stable_id), empty),
|
||||
greater_than_eq(champ_value(surface.stable_id), constant(50)),
|
||||
ds_eq(champ_value(yes_non.stable_id), constant(5))
|
||||
])
|
||||
|
||||
tdc = TypeDeChamp.create(type_champ: :integer_number, condition: condition, libelle: 'nb de piece')
|
||||
|
||||
render TypesDeChampEditor::ConditionsComponent.new(
|
||||
tdc: tdc, upper_tdcs: upper_tdcs, procedure_id: '1'
|
||||
)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,162 @@
|
|||
describe TypesDeChampEditor::ConditionsComponent, type: :component do
|
||||
include Logic
|
||||
|
||||
describe 'render' do
|
||||
let(:tdc) { create(:type_de_champ, condition: condition) }
|
||||
let(:condition) { nil }
|
||||
let(:upper_tdcs) { [] }
|
||||
|
||||
before { render_inline(described_class.new(tdc: tdc, upper_tdcs: upper_tdcs, procedure_id: 123)) }
|
||||
|
||||
context 'when there are no upper tdc' do
|
||||
it { expect(page).not_to have_text('Logique conditionnelle') }
|
||||
end
|
||||
|
||||
context 'when there are upper tdcs but not managed' do
|
||||
let(:upper_tdcs) { [build(:type_de_champ_address)] }
|
||||
|
||||
it { expect(page).not_to have_text('Logique conditionnelle') }
|
||||
end
|
||||
|
||||
context 'when there are upper tdc but no condition to display' do
|
||||
let(:upper_tdcs) { [build(:type_de_champ_integer_number)] }
|
||||
|
||||
it do
|
||||
expect(page).to have_text('Logique conditionnelle')
|
||||
expect(page).to have_button('cliquer pour activer')
|
||||
expect(page).not_to have_selector('table')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are upper tdc and a condition' do
|
||||
let(:upper_tdc) { create(:type_de_champ_number) }
|
||||
let(:upper_tdcs) { [upper_tdc] }
|
||||
|
||||
context 'and one condition' do
|
||||
let(:condition) { ds_eq(champ_value(upper_tdc.stable_id), constant(1)) }
|
||||
|
||||
it do
|
||||
expect(page).to have_button('cliquer pour désactiver')
|
||||
expect(page).to have_selector('table')
|
||||
expect(page).to have_selector('tbody > tr', count: 1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'focus one row' do
|
||||
context 'empty' do
|
||||
let(:condition) { empty_operator(empty, empty) }
|
||||
|
||||
it do
|
||||
expect(page).to have_select('type_de_champ[condition_form][rows][][operator_name]', options: ['Est'])
|
||||
expect(page).to have_select('type_de_champ[condition_form][rows][][value]', options: ['Sélectionner'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'number' do
|
||||
let(:condition) { empty_operator(constant(1), constant(0)) }
|
||||
|
||||
it do
|
||||
expect(page).to have_select('type_de_champ[condition_form][rows][][operator_name]', with_options: ['Égal à'])
|
||||
expect(page).to have_selector('input[name="type_de_champ[condition_form][rows][][value]"][value=0]')
|
||||
end
|
||||
end
|
||||
|
||||
context 'boolean' do
|
||||
let(:condition) { empty_operator(constant(true), constant(true)) }
|
||||
|
||||
it do
|
||||
expect(page).to have_select('type_de_champ[condition_form][rows][][operator_name]', with_options: ['Est'])
|
||||
expect(page).to have_select('type_de_champ[condition_form][rows][][value]', options: ['Oui', 'Non'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'enum' do
|
||||
let(:drop_down) { create(:type_de_champ_drop_down_list) }
|
||||
let(:upper_tdcs) { [drop_down] }
|
||||
let(:condition) { empty_operator(champ_value(drop_down.stable_id), constant(true)) }
|
||||
|
||||
it do
|
||||
expect(page).to have_select('type_de_champ[condition_form][rows][][operator_name]', with_options: ['Est'])
|
||||
expect(page).to have_select('type_de_champ[condition_form][rows][][value]', options: ['Sélectionner', 'val1', 'val2', 'val3'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'and 2 conditions' do
|
||||
let(:condition) { ds_and([empty_operator(empty, empty), empty_operator(empty, empty)]) }
|
||||
|
||||
it do
|
||||
expect(page).to have_selector('tbody > tr', count: 2)
|
||||
expect(page).to have_select("type_de_champ_condition_form_top_operator_name", selected: "Et", options: ['Et', 'Ou'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are 3 conditions' do
|
||||
let(:upper_tdc) { create(:type_de_champ_number) }
|
||||
let(:upper_tdcs) { [upper_tdc] }
|
||||
|
||||
let(:condition) do
|
||||
ds_or([
|
||||
ds_eq(champ_value(upper_tdc.stable_id), constant(1)),
|
||||
ds_eq(champ_value(upper_tdc.stable_id), empty),
|
||||
greater_than(champ_value(upper_tdc.stable_id), constant(3))
|
||||
])
|
||||
end
|
||||
|
||||
it do
|
||||
expect(page).to have_selector('tbody > tr', count: 3)
|
||||
expect(page).to have_select("type_de_champ_condition_form_top_operator_name", selected: "Ou", options: ['Et', 'Ou'])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.rows' do
|
||||
let(:tdc) { build(:type_de_champ, condition: condition) }
|
||||
let(:condition) { nil }
|
||||
|
||||
subject { described_class.new(tdc: tdc, upper_tdcs: [], procedure_id: 123).send(:rows) }
|
||||
|
||||
context 'when there is one condition' do
|
||||
let(:condition) { ds_eq(empty, constant(1)) }
|
||||
|
||||
it { is_expected.to eq([[empty, Logic::Eq.name, constant(1)]]) }
|
||||
end
|
||||
|
||||
context 'when there are 2 conditions' do
|
||||
let(:condition) { ds_and([ds_eq(empty, constant(1)), ds_eq(empty, empty)]) }
|
||||
|
||||
let(:expected) do
|
||||
[
|
||||
[empty, Logic::Eq.name, constant(1)],
|
||||
[empty, Logic::Eq.name, empty]
|
||||
]
|
||||
end
|
||||
|
||||
it { is_expected.to eq(expected) }
|
||||
end
|
||||
|
||||
context 'when there are 3 conditions' do
|
||||
let(:upper_tdc) { create(:type_de_champ_number) }
|
||||
let(:upper_tdcs) { [upper_tdc] }
|
||||
|
||||
let(:condition) do
|
||||
ds_or([
|
||||
ds_eq(champ_value(upper_tdc.stable_id), constant(1)),
|
||||
ds_eq(champ_value(upper_tdc.stable_id), empty),
|
||||
greater_than(champ_value(upper_tdc.stable_id), constant(3))
|
||||
])
|
||||
end
|
||||
|
||||
let(:expected) do
|
||||
[
|
||||
[champ_value(upper_tdc.stable_id), Logic::Eq.name, constant(1)],
|
||||
[champ_value(upper_tdc.stable_id), Logic::Eq.name, empty],
|
||||
[champ_value(upper_tdc.stable_id), Logic::GreaterThan.name, constant(3)]
|
||||
]
|
||||
end
|
||||
|
||||
it { is_expected.to eq(expected) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
describe TypesDeChampEditor::ConditionsErrorsComponent, type: :component do
|
||||
include Logic
|
||||
|
||||
describe 'render' do
|
||||
let(:conditions) { [] }
|
||||
let(:upper_tdcs) { [] }
|
||||
|
||||
before { render_inline(described_class.new(conditions: conditions, upper_tdcs: upper_tdcs)) }
|
||||
|
||||
context 'when there are no condition' do
|
||||
it { expect(page).to have_no_css('.condition-error') }
|
||||
end
|
||||
|
||||
context 'when the targeted_champ is not available' do
|
||||
let(:tdc) { create(:type_de_champ_integer_number) }
|
||||
let(:conditions) { [ds_eq(champ_value(tdc.stable_id), constant(1))] }
|
||||
|
||||
it do
|
||||
expect(page).to have_css('.condition-error')
|
||||
expect(page).to have_content("Un champ cible n'est plus disponible")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the targeted_champ is unmanaged' do
|
||||
let(:tdc) { create(:type_de_champ_address) }
|
||||
let(:upper_tdcs) { [tdc] }
|
||||
let(:conditions) { [ds_eq(champ_value(tdc.stable_id), constant(1))] }
|
||||
|
||||
it do
|
||||
expect(page).to have_css('.condition-error')
|
||||
expect(page).to have_content("ne peut pas être utilisé")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the types mismatch' do
|
||||
let(:tdc) { create(:type_de_champ_integer_number) }
|
||||
let(:upper_tdcs) { [tdc] }
|
||||
let(:conditions) { [ds_eq(champ_value(tdc.stable_id), constant('a'))] }
|
||||
|
||||
it { expect(page).to have_content("Il ne peut pas être") }
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue