Merge pull request #7810 from betagouv/fix_conditional_tdc

Fix(conditional and revision): restreint les calculs conditionels a la révision courante
This commit is contained in:
LeSim 2022-09-28 10:24:37 +02:00 committed by GitHub
commit 3612c722a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 160 additions and 115 deletions

View file

@ -105,7 +105,7 @@ class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
end
def compatibles_operators_for_select(left)
case left.type
case left.type(@upper_tdcs)
when ChampValue::CHAMP_VALUE_TYPE.fetch(:boolean)
[
[t('is', scope: 'logic'), Eq.name]
@ -135,7 +135,7 @@ class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
def right_operand_tag(left, right, row_index)
right_invalid = !current_right_valid?(left, right)
case left.type
case left.type(@upper_tdcs)
when :boolean
booleans_for_select = [[t('utils.yes'), constant(true).to_json], [t('utils.no'), constant(false).to_json]]
@ -156,7 +156,7 @@ class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
id: input_id_for('value', row_index)
)
when :enum, :enums
enums_for_select = left.options
enums_for_select = left.options(@upper_tdcs)
if right_invalid
enums_for_select = empty_target_for_select + enums_for_select
@ -182,7 +182,7 @@ class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
end
def current_right_valid?(left, right)
Logic.compatible_type?(left, right)
Logic.compatible_type?(left, right, @upper_tdcs)
end
def add_condition_tag

View file

@ -6,10 +6,24 @@ class TypesDeChampEditor::ConditionsErrorsComponent < ApplicationComponent
private
def errors
@conditions
.flat_map { |condition| condition.errors(@upper_tdcs.map(&:stable_id)) }
.map { |error| humanize(error) }
errors = @conditions
.flat_map { |condition| condition.errors(@upper_tdcs) }
.uniq
# if a tdc is not available (has been removed for example)
# it causes a lot of errors (incompatible type for example)
# only the root cause is displayed
messages = if errors.include?({ type: :not_available })
[t('not_available', scope: '.errors')]
else
errors.map { |error| humanize(error) }
end
to_html_list(messages)
end
def to_html_list(messages)
messages
.map { |message| tag.li(message) }
.then { |lis| tag.ul(lis.reduce(&:+)) }
end
@ -48,7 +62,7 @@ class TypesDeChampEditor::ConditionsErrorsComponent < ApplicationComponent
def render?
@conditions
.filter { |condition| condition.errors(@upper_tdcs.map(&:stable_id)).present? }
.filter { |condition| condition.errors(@upper_tdcs).present? }
.present?
end
end

View file

@ -49,7 +49,7 @@ module Administrateurs
end
def condition_form
ConditionForm.new(condition_params)
ConditionForm.new(condition_params.merge({ upper_tdcs: @upper_tdcs }))
end
def retrieve_coordinate_and_uppers

View file

@ -2,7 +2,7 @@ class ConditionForm
include ActiveModel::Model
include Logic
attr_accessor :top_operator_name, :rows
attr_accessor :top_operator_name, :rows, :upper_tdcs
def to_condition
case sub_conditions.count
@ -22,7 +22,7 @@ class ConditionForm
end
def change_champ(i)
sub_conditions[i] = Logic.ensure_compatibility_from_left(sub_conditions[i])
sub_conditions[i] = Logic.ensure_compatibility_from_left(sub_conditions[i], upper_tdcs)
self
end
@ -39,7 +39,7 @@ class ConditionForm
def row_to_condition(row)
left = Logic.from_json(row[:targeted_champ])
right = parse_value(left.type, row[:value])
right = parse_value(left.type(upper_tdcs), row[:value])
Logic.class_from_name(row[:operator_name]).new(left, right)
end

View file

@ -12,12 +12,12 @@ module Logic
.find { |c| c.name == name }
end
def self.ensure_compatibility_from_left(condition)
def self.ensure_compatibility_from_left(condition, type_de_champs)
left = condition.left
right = condition.right
operator_class = condition.class
case [left.type, condition]
case [left.type(type_de_champs), condition]
in [:boolean, _]
operator_class = Eq
in [:empty, _]
@ -31,14 +31,14 @@ module Logic
in [:number, _]
end
if !compatible_type?(left, right)
right = case left.type
if !compatible_type?(left, right, type_de_champs)
right = case left.type(type_de_champs)
when :boolean
Constant.new(true)
when :empty
Empty.new
when :enum, :enums
Constant.new(left.options.first.second)
Constant.new(left.options(type_de_champs).first.second)
when :number
Constant.new(0)
end
@ -47,8 +47,8 @@ module Logic
operator_class.new(left, right)
end
def self.compatible_type?(left, right)
case [left.type, right.type]
def self.compatible_type?(left, right, type_de_champs)
case [left.type(type_de_champs), right.type(type_de_champs)]
in [a, ^a] # syntax for same type
true
in [:enum, :string] | [:enums, :string]

View file

@ -7,5 +7,5 @@ class Logic::And < Logic::NAryOperator
@operands.map { |operand| operand.compute(champs) }.all?
end
def to_s = "(#{@operands.map(&:to_s).join(' && ')})"
def to_s(type_de_champs) = "(#{@operands.map { |o| o.to_s(type_de_champs) }.join(' && ')})"
end

View file

@ -17,17 +17,17 @@ class Logic::BinaryOperator < Logic::Term
self.new(Logic.from_h(h['left']), Logic.from_h(h['right']))
end
def errors(stable_ids = [])
def errors(type_de_champs = [])
errors = []
if @left.type != :number || @right.type != :number
if @left.type(type_de_champs) != :number || @right.type(type_de_champs) != :number
errors << { type: :required_number, operator_name: self.class.name }
end
errors + @left.errors(stable_ids) + @right.errors(stable_ids)
errors + @left.errors(type_de_champs) + @right.errors(type_de_champs)
end
def type = :boolean
def type(type_de_champs = []) = :boolean
def compute(champs = [])
l = @left.compute(champs)
@ -36,7 +36,7 @@ class Logic::BinaryOperator < Logic::Term
l&.send(operation, r) || false
end
def to_s = "(#{@left} #{operation} #{@right})"
def to_s(type_de_champs) = "(#{@left.to_s(type_de_champs)} #{operation} #{@right.to_s(type_de_champs)})"
def ==(other)
self.class == other.class &&

View file

@ -29,7 +29,7 @@ class Logic::ChampValue < Logic::Term
return nil if !targeted_champ.visible?
return nil if targeted_champ.blank?
case type_de_champ.type_champ
case type_de_champ(champs.map(&:type_de_champ)).type_champ
when MANAGED_TYPE_DE_CHAMP.fetch(:yes_no),
MANAGED_TYPE_DE_CHAMP.fetch(:checkbox)
targeted_champ.true?
@ -42,10 +42,10 @@ class Logic::ChampValue < Logic::Term
end
end
def to_s = type_de_champ&.libelle # TODO: gerer le cas ou un tdc est supprimé
def to_s(type_de_champs) = type_de_champ(type_de_champs)&.libelle # TODO: gerer le cas ou un tdc est supprimé
def type
case type_de_champ&.type_champ # TODO: gerer le cas ou un tdc est supprimé
def type(type_de_champs)
case type_de_champ(type_de_champs)&.type_champ # TODO: gerer le cas ou un tdc est supprimé
when MANAGED_TYPE_DE_CHAMP.fetch(:yes_no),
MANAGED_TYPE_DE_CHAMP.fetch(:checkbox)
CHAMP_VALUE_TYPE.fetch(:boolean)
@ -60,8 +60,8 @@ class Logic::ChampValue < Logic::Term
end
end
def errors(stable_ids)
if !stable_ids.include?(stable_id)
def errors(type_de_champs)
if !type_de_champs.map(&:stable_id).include?(stable_id)
[{ type: :not_available }]
else
[]
@ -83,9 +83,10 @@ class Logic::ChampValue < Logic::Term
self.class == other.class && @stable_id == other.stable_id
end
def options
opts = type_de_champ.drop_down_list_enabled_non_empty_options.map { |option| [option, option] }
if type_de_champ.drop_down_other?
def options(type_de_champs)
tdc = type_de_champ(type_de_champs)
opts = tdc.drop_down_list_enabled_non_empty_options.map { |option| [option, option] }
if tdc.drop_down_other?
opts + [["Autre", Champs::DropDownListChamp::OTHER]]
else
opts
@ -94,8 +95,8 @@ class Logic::ChampValue < Logic::Term
private
def type_de_champ
TypeDeChamp.find_by(stable_id: stable_id)
def type_de_champ(type_de_champs)
type_de_champs.find { |c| c.stable_id == stable_id }
end
def champ(champs)

View file

@ -7,7 +7,7 @@ class Logic::Constant < Logic::Term
def compute(_champs = nil) = @value
def to_s
def to_s(_type_de_champs = [])
case @value
when TrueClass
I18n.t('utils.yes')
@ -18,7 +18,7 @@ class Logic::Constant < Logic::Term
end
end
def type
def type(_type_de_champs = [])
case @value
when TrueClass, FalseClass
:boolean
@ -29,7 +29,7 @@ class Logic::Constant < Logic::Term
end
end
def errors(_stable_ids = nil) = []
def errors(_type_de_champs = nil) = []
def to_h
{

View file

@ -1,9 +1,9 @@
class Logic::Empty < Logic::Term
def to_s = I18n.t('logic.empty')
def to_s(_type_de_champs = []) = I18n.t('logic.empty')
def type = :empty
def type(_type_de_champs = []) = :empty
def errors(_stable_ids = nil) = ['empty']
def errors(_type_de_champs = []) = ['empty']
def to_h
{

View file

@ -1,9 +1,9 @@
class Logic::EmptyOperator < Logic::BinaryOperator
def to_s = "empty operator"
def to_s(_type_de_champs = []) = "empty operator"
def type = :empty
def type(_type_de_champs = []) = :empty
def errors(_stable_ids = nil) = []
def errors(_type_de_champs = []) = []
def compute(_champs = [])
true

View file

@ -1,20 +1,20 @@
class Logic::Eq < Logic::BinaryOperator
def operation = :==
def errors(stable_ids = [])
def errors(type_de_champs = [])
errors = [@left, @right]
.filter { |term| term.type == :unmanaged }
.filter { |term| term.type(type_de_champs) == :unmanaged }
.map { |term| { type: :unmanaged, stable_id: term.stable_id } }
if !Logic.compatible_type?(@left, @right)
if !Logic.compatible_type?(@left, @right, type_de_champs)
errors << {
type: :incompatible,
stable_id: @left.try(:stable_id),
right: @right,
operator_name: self.class.name
}
elsif @left.type == :enum &&
!left.options.map(&:second).include?(right.value)
elsif @left.type(type_de_champs) == :enum &&
!left.options(type_de_champs).map(&:second).include?(right.value)
errors << {
type: :not_included,
stable_id: @left.stable_id,
@ -22,7 +22,7 @@ class Logic::Eq < Logic::BinaryOperator
}
end
errors + @left.errors(stable_ids) + @right.errors(stable_ids)
errors + @left.errors(type_de_champs) + @right.errors(type_de_champs)
end
def ==(other)

View file

@ -1,12 +1,12 @@
class Logic::IncludeOperator < Logic::BinaryOperator
def operation = :include?
def errors(stable_ids = [])
def errors(type_de_champs = [])
result = []
if left_not_a_list?
if left_not_a_list?(type_de_champs)
result << { type: :required_list }
elsif right_value_not_in_list?
elsif right_value_not_in_list?(type_de_champs)
result << {
type: :not_included,
stable_id: @left.stable_id,
@ -14,16 +14,16 @@ class Logic::IncludeOperator < Logic::BinaryOperator
}
end
result + @left.errors(stable_ids) + @right.errors(stable_ids)
result + @left.errors(type_de_champs) + @right.errors(type_de_champs)
end
private
def left_not_a_list?
@left.type != :enums
def left_not_a_list?(type_de_champs)
@left.type(type_de_champs) != :enums
end
def right_value_not_in_list?
!@left.options.map(&:second).include?(@right.value)
def right_value_not_in_list?(type_de_champs)
!@left.options(type_de_champs).map(&:second).include?(@right.value)
end
end

View file

@ -16,22 +16,22 @@ class Logic::NAryOperator < Logic::Term
self.new(h['operands'].map { |operand_h| Logic.from_h(operand_h) })
end
def errors(stable_ids = [])
def errors(type_de_champs = [])
errors = []
if @operands.empty?
errors += ["opérateur '#{operator_name}' vide"]
end
not_booleans = @operands.filter { |operand| operand.type != :boolean }
not_booleans = @operands.filter { |operand| operand.type(type_de_champs) != :boolean }
if not_booleans.present?
errors += ["'#{operator_name}' ne contient pas que des booléens : #{not_booleans.map(&:to_s).join(', ')}"]
errors += ["'#{operator_name}' ne contient pas que des booléens : #{not_booleans.map { |o| o.to_s(type_de_champs) }.join(', ')}"]
end
errors + @operands.flat_map { |operand| operand.errors(stable_ids) }
errors + @operands.flat_map { |operand| operand.errors(type_de_champs) }
end
def type = :boolean
def type(_type_de_champs = []) = :boolean
def ==(other)
self.class == other.class &&

View file

@ -7,5 +7,5 @@ class Logic::Or < Logic::NAryOperator
@operands.map { |operand| operand.compute(champs) }.any?
end
def to_s = "(#{@operands.map(&:to_s).join(' || ')})"
def to_s(type_de_champs = []) = "(#{@operands.map { |o| o.to_s(type_de_champs) }.join(' || ')})"
end

View file

@ -317,7 +317,7 @@ class ProcedureRevision < ApplicationRecord
changed = kept
.map { |sid| [sid, from_h[sid], to_h[sid]] }
.flat_map do |sid, from, to|
compare_type_de_champ(from.type_de_champ, to.type_de_champ)
compare_type_de_champ(from.type_de_champ, to.type_de_champ, from_coordinates, to_coordinates)
.each { |h| h[:_position] = to_sids.index(sid) }
end
@ -327,7 +327,7 @@ class ProcedureRevision < ApplicationRecord
end
end
def compare_type_de_champ(from_type_de_champ, to_type_de_champ)
def compare_type_de_champ(from_type_de_champ, to_type_de_champ, from_coordinates, to_coordinates)
changes = []
if from_type_de_champ.type_champ != to_type_de_champ.type_champ
changes << {
@ -385,8 +385,8 @@ class ProcedureRevision < ApplicationRecord
attribute: :condition,
label: from_type_de_champ.libelle,
private: from_type_de_champ.private?,
from: from_type_de_champ.condition&.to_s,
to: to_type_de_champ.condition&.to_s,
from: from_type_de_champ.condition&.to_s(from_coordinates.map(&:type_de_champ)),
to: to_type_de_champ.condition&.to_s(to_coordinates.map(&:type_de_champ)),
stable_id: from_type_de_champ.stable_id
}
end
@ -479,12 +479,12 @@ class ProcedureRevision < ApplicationRecord
end
def conditions_are_valid?
stable_ids = types_de_champ_public.map(&:stable_id)
public_tdcs = types_de_champ_public.to_a
types_de_champ_public
public_tdcs
.map.with_index
.filter_map { |tdc, i| tdc.condition.present? ? [tdc, i] : nil }
.map { |tdc, i| [tdc, tdc.condition.errors(stable_ids.take(i))] }
.map { |tdc, i| [tdc, tdc.condition.errors(public_tdcs.take(i))] }
.filter { |_tdc, errors| errors.present? }
.each { |tdc, message| errors.add(:condition, message, type_de_champ: tdc) }
end

View file

@ -1,16 +1,20 @@
describe Administrateurs::ConditionsController, type: :controller do
include Logic
let(:procedure) { create(:procedure, :with_type_de_champ, types_de_champ_count: 2) }
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :integer_number }] * 3) }
let(:first_coordinate) { procedure.draft_revision.revision_types_de_champ.first }
let(:second_coordinate) { procedure.draft_revision.revision_types_de_champ.first }
let(:third_coordinate) { procedure.draft_revision.revision_types_de_champ.first }
let(:first_tdc) { procedure.draft_revision.types_de_champ.first }
let(:second_tdc) { procedure.draft_revision.types_de_champ.second }
let(:third_tdc) { procedure.draft_revision.types_de_champ.third }
before { sign_in(procedure.administrateurs.first.user) }
let(:default_params) do
{
procedure_id: procedure.id,
stable_id: second_tdc.stable_id
stable_id: third_tdc.stable_id
}
end
@ -23,7 +27,7 @@ describe Administrateurs::ConditionsController, type: :controller do
{
rows: [
{
targeted_champ: champ_value(1).to_json,
targeted_champ: champ_value(first_tdc.stable_id).to_json,
operator_name: Logic::Eq.name,
value: '2'
}
@ -32,9 +36,9 @@ describe Administrateurs::ConditionsController, type: :controller do
end
it do
expect(second_tdc.reload.condition).to eq(ds_eq(champ_value(1), constant('2')))
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(second_tdc))
expect(assigns(:upper_tdcs)).to eq([first_coordinate.type_de_champ])
expect(third_tdc.reload.condition).to eq(ds_eq(champ_value(first_tdc.stable_id), constant(2)))
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(third_tdc))
expect(assigns(:upper_tdcs)).to eq([first_tdc, second_tdc])
end
end
@ -42,9 +46,9 @@ describe Administrateurs::ConditionsController, type: :controller do
before { post :add_row, params: default_params, format: :turbo_stream }
it do
expect(second_tdc.reload.condition).to eq(empty_operator(empty, empty))
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(second_tdc))
expect(assigns(:upper_tdcs)).to eq([first_coordinate.type_de_champ])
expect(third_tdc.reload.condition).to eq(empty_operator(empty, empty))
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(third_tdc))
expect(assigns(:upper_tdcs)).to eq([first_tdc, second_tdc])
end
end
@ -66,9 +70,9 @@ describe Administrateurs::ConditionsController, type: :controller do
end
it do
expect(second_tdc.reload.condition).to eq(nil)
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(second_tdc))
expect(assigns(:upper_tdcs)).to eq([first_coordinate.type_de_champ])
expect(third_tdc.reload.condition).to eq(nil)
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(third_tdc))
expect(assigns(:upper_tdcs)).to eq([first_tdc, second_tdc])
end
end
@ -79,15 +83,13 @@ describe Administrateurs::ConditionsController, type: :controller do
end
it do
expect(second_tdc.reload.condition).to eq(nil)
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(second_tdc))
expect(assigns(:upper_tdcs)).to eq([first_coordinate.type_de_champ])
expect(third_tdc.reload.condition).to eq(nil)
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(third_tdc))
expect(assigns(:upper_tdcs)).to eq([first_tdc, second_tdc])
end
end
describe '#change_targeted_champ' do
let!(:number_tdc) { create(:type_de_champ_integer_number) }
before do
second_tdc.update(condition: empty_operator(empty, empty))
patch :change_targeted_champ, params: params, format: :turbo_stream
@ -99,7 +101,7 @@ describe Administrateurs::ConditionsController, type: :controller do
{
rows: [
{
targeted_champ: champ_value(number_tdc.stable_id).to_json,
targeted_champ: champ_value(second_tdc.stable_id).to_json,
operator_name: Logic::EmptyOperator.name,
value: empty.to_json
}
@ -108,9 +110,9 @@ describe Administrateurs::ConditionsController, type: :controller do
end
it do
expect(second_tdc.reload.condition).to eq(ds_eq(champ_value(number_tdc.stable_id), constant(0)))
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(second_tdc))
expect(assigns(:upper_tdcs)).to eq([first_coordinate.type_de_champ])
expect(third_tdc.reload.condition).to eq(ds_eq(champ_value(second_tdc.stable_id), constant(0)))
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(third_tdc))
expect(assigns(:upper_tdcs)).to eq([first_tdc, second_tdc])
end
end
end

View file

@ -8,7 +8,7 @@ describe Logic::And do
describe '#to_s' do
it do
expect(and_from([true, false, true]).to_s).to eq "(Oui && Non && Oui)"
expect(and_from([true, false, true]).to_s([])).to eq "(Oui && Non && Oui)"
end
end

View file

@ -7,7 +7,7 @@ describe Logic::BinaryOperator do
end
describe '#to_s' do
it { expect(two_greater_than_one.to_s).to eq('(2 > 1)') }
it { expect(two_greater_than_one.to_s([])).to eq('(2 > 1)') }
end
describe '#==' do

View file

@ -7,7 +7,7 @@ describe Logic::ChampValue do
let(:value) { 'true' }
let(:champ) { create(:champ_yes_no, value: value) }
it { expect(champ_value(champ.stable_id).type).to eq(:boolean) }
it { expect(champ_value(champ.stable_id).type([champ.type_de_champ])).to eq(:boolean) }
context 'with true value' do
it { is_expected.to be(true) }
@ -31,7 +31,7 @@ describe Logic::ChampValue do
context 'integer tdc' do
let(:champ) { create(:champ_integer_number, value: '42') }
it { expect(champ_value(champ.stable_id).type).to eq(:number) }
it { expect(champ_value(champ.stable_id).type([champ.type_de_champ])).to eq(:number) }
it { is_expected.to eq(42) }
context 'with a blank value' do
@ -44,22 +44,22 @@ describe Logic::ChampValue do
context 'decimal tdc' do
let(:champ) { create(:champ_decimal_number, value: '42.01') }
it { expect(champ_value(champ.stable_id).type).to eq(:number) }
it { expect(champ_value(champ.stable_id).type([champ.type_de_champ])).to eq(:number) }
it { is_expected.to eq(42.01) }
end
context 'dropdown tdc' do
let(:champ) { create(:champ_drop_down_list, value: 'val1') }
it { expect(champ_value(champ.stable_id).type).to eq(:enum) }
it { expect(champ_value(champ.stable_id).type([champ.type_de_champ])).to eq(:enum) }
it { is_expected.to eq('val1') }
it { expect(champ_value(champ.stable_id).options).to match_array([["val1", "val1"], ["val2", "val2"], ["val3", "val3"]]) }
it { expect(champ_value(champ.stable_id).options([champ.type_de_champ])).to match_array([["val1", "val1"], ["val2", "val2"], ["val3", "val3"]]) }
context 'with other enabled' do
let(:champ) { create(:champ_drop_down_list, value: 'val1', other: true) }
it { is_expected.to eq('val1') }
it { expect(champ_value(champ.stable_id).options).to match_array([["val1", "val1"], ["val2", "val2"], ["val3", "val3"], ["Autre", "__other__"]]) }
it { expect(champ_value(champ.stable_id).options([champ.type_de_champ])).to match_array([["val1", "val1"], ["val2", "val2"], ["val3", "val3"], ["Autre", "__other__"]]) }
end
context 'with other filled' do
@ -72,14 +72,39 @@ describe Logic::ChampValue do
context 'checkbox tdc' do
let(:champ) { create(:champ_checkbox, value: 'on') }
it { expect(champ_value(champ.stable_id).type).to eq(:boolean) }
it { expect(champ_value(champ.stable_id).type([champ.type_de_champ])).to eq(:boolean) }
it { is_expected.to eq(true) }
end
describe 'errors' do
let(:champ) { create(:champ) }
it { expect(champ_value(champ.stable_id).errors([champ.stable_id])).to be_empty }
it { expect(champ_value(champ.stable_id).errors(['other stable ids'])).to eq([{ type: :not_available }]) }
it { expect(champ_value(champ.stable_id).errors([champ.type_de_champ])).to be_empty }
it { expect(champ_value(champ.stable_id).errors([])).to eq([{ type: :not_available }]) }
end
context 'with multiple revision' do
let(:options) { ['revision_1'] }
let(:procedure) do
create(:procedure, :published, :for_individual, types_de_champ_public: [{ type: :drop_down_list, libelle: 'dropdown', options: options }])
end
let(:drop_down_r1) { procedure.published_revision.types_de_champ_public.first }
let(:stable_id) { drop_down_r1.stable_id }
it { expect(champ_value(stable_id).options([drop_down_r1])).to match_array([["revision_1", "revision_1"]]) }
context 'with a new revision' do
let(:drop_down_r2) { procedure.draft_revision.types_de_champ_public.first }
before do
tdc = procedure.draft_revision.find_and_ensure_exclusive_use(stable_id)
tdc.drop_down_options = ['revision_2']
tdc.save!
end
it do
expect(champ_value(stable_id).options([drop_down_r2])).to match_array([["revision_2", "revision_2"]])
end
end
end
end

View file

@ -9,7 +9,7 @@ describe Logic::IncludeOperator do
end
describe '#errors' do
it { expect(ds_include(champ_value(champ.stable_id), constant('val1')).errors([champ.stable_id])).to be_empty }
it { expect(ds_include(champ_value(champ.stable_id), constant('val1')).errors([champ.type_de_champ])).to be_empty }
it do
expected = {
right: constant('something else'),
@ -17,10 +17,10 @@ describe Logic::IncludeOperator do
type: :not_included
}
expect(ds_include(champ_value(champ.stable_id), constant('something else')).errors([champ.stable_id])).to eq([expected])
expect(ds_include(champ_value(champ.stable_id), constant('something else')).errors([champ.type_de_champ])).to eq([expected])
end
it { expect(ds_include(constant(1), constant('val1')).errors).to eq([{ type: :required_list }]) }
it { expect(ds_include(constant(1), constant('val1')).errors([])).to eq([{ type: :required_list }]) }
end
describe '#==' do

View file

@ -16,7 +16,8 @@ describe Logic do
end
describe '.ensure_compatibility_from_left' do
subject { Logic.ensure_compatibility_from_left(condition) }
let(:type_de_champs) { [] }
subject { Logic.ensure_compatibility_from_left(condition, type_de_champs) }
context 'when it s fine' do
let(:condition) { greater_than(constant(1), constant(1)) }
@ -44,31 +45,33 @@ describe Logic do
context 'when dropdown empty operator true' do
let(:drop_down) { create(:type_de_champ_drop_down_list) }
let(:type_de_champs) { [drop_down] }
let(:first_option) { drop_down.drop_down_list_enabled_non_empty_options.first }
let(:condition) { empty_operator(champ_value(drop_down), constant(true)) }
let(:condition) { empty_operator(champ_value(drop_down.stable_id), constant(true)) }
it { is_expected.to eq(ds_eq(champ_value(drop_down), constant(first_option))) }
it { is_expected.to eq(ds_eq(champ_value(drop_down.stable_id), constant(first_option))) }
end
context 'when multiple dropdown empty operator true' do
let(:multiple_drop_down) { create(:type_de_champ_multiple_drop_down_list) }
let(:type_de_champs) { [multiple_drop_down] }
let(:first_option) { multiple_drop_down.drop_down_list_enabled_non_empty_options.first }
let(:condition) { empty_operator(champ_value(multiple_drop_down), constant(true)) }
let(:condition) { empty_operator(champ_value(multiple_drop_down.stable_id), constant(true)) }
it { is_expected.to eq(ds_include(champ_value(multiple_drop_down), constant(first_option))) }
it { is_expected.to eq(ds_include(champ_value(multiple_drop_down.stable_id), constant(first_option))) }
end
end
describe '.compatible_type?' do
it { expect(Logic.compatible_type?(constant(true), constant(true))).to be true }
it { expect(Logic.compatible_type?(constant(1), constant(true))).to be false }
it { expect(Logic.compatible_type?(constant(true), constant(true), [])).to be true }
it { expect(Logic.compatible_type?(constant(1), constant(true), [])).to be false }
context 'with a dropdown' do
let(:drop_down) { create(:type_de_champ_drop_down_list) }
let(:first_option) { drop_down.drop_down_list_enabled_non_empty_options.first }
it do
expect(Logic.compatible_type?(champ_value(drop_down.stable_id), constant('a'))).to be true
expect(Logic.compatible_type?(champ_value(drop_down.stable_id), constant('a'), [drop_down])).to be true
end
end
end