Merge pull request #11131 from colinux/fix-edit-footer-logic

ETQ usager corrige le fait d'être informé d'un dossier inéligible dans certaines conditions
This commit is contained in:
Colin Darie 2024-12-17 10:47:03 +00:00 committed by GitHub
commit 1f8325b172
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 122 additions and 78 deletions

View file

@ -5,7 +5,8 @@ en:
confirmation: Draft saved
error: Impossible to save the draft
en_construction:
explanation: Your modifications are automatically saved. Submit them when youre done.
explanation: Your modifications are automatically saved.
submit_them: Submit them when youre done.
confirmation: Modifications saved
error: Impossible to save the modifications.
annotations:

View file

@ -5,7 +5,8 @@ fr:
confirmation: Brouillon enregistré
error: Impossible denregistrer le brouillon
en_construction:
explanation: Vos modifications sont automatiquement enregistrées. Déposez-les quand vous aurez terminé.
explanation: Vos modifications sont automatiquement enregistrées.
submit_them: Déposez-les quand vous aurez terminé.
confirmation: Modifications enregistrées.
error: Impossible denregistrer les modifications
annotations:

View file

@ -5,6 +5,8 @@
= t('.annotations.explanation')
- elsif dossier.editing_fork?
= t('.en_construction.explanation')
- if dossier.can_passer_en_construction?
= t('.en_construction.submit_them')
- else
= t('.brouillon.explanation')
- if !annotation?

View file

@ -1,18 +1,28 @@
# frozen_string_literal: true
class Dossiers::InvalidIneligibiliteRulesComponent < ApplicationComponent
delegate :can_passer_en_construction?, to: :@dossier
delegate :can_passer_en_construction?, to: :dossier
def initialize(dossier:)
def initialize(dossier:, wrapped: true)
@dossier = dossier
@revision = dossier.revision
@opened = !dossier.can_passer_en_construction?
@wrapped = wrapped
end
private
attr_reader :dossier
def render?
!can_passer_en_construction?
dossier.revision.ineligibilite_enabled?
end
def error_message
@dossier.revision.ineligibilite_message
dossier.revision.ineligibilite_message
end
def opened? = @opened
def wrapped? = @wrapped
end

View file

@ -1,8 +1,8 @@
%div{ id: dom_id(@dossier, :ineligibilite_rules_broken), data: { controller: 'ineligibilite-rules-match', turbo_force: :server } }
%button.fr-sr-only{ aria: {controls: 'modal-eligibilite-rules-dialog' }, data: {'fr-opened': "false" } }
- modal_content = capture do
%button.fr-sr-only{ aria: { controls: 'modal-eligibilite-rules-dialog' }, data: { 'fr-opened': opened?.to_s } }
show modal
%dialog.fr-modal{ "aria-labelledby" => "fr-modal-title-modal-1", role: "dialog", id: 'modal-eligibilite-rules-dialog', data: { 'ineligibilite-rules-match-target' => 'dialog' } }
%dialog.fr-modal{ "aria-labelledby" => "fr-modal-title-modal-1", role: "dialog", id: 'modal-eligibilite-rules-dialog', data: { 'turbo-permanent' => true } }
.fr-container.fr-container--fluid.fr-container-md
.fr-grid-row.fr-grid-row--center
.fr-col-12.fr-col-md-8.fr-col-lg-6
@ -14,3 +14,9 @@
%span.fr-icon-arrow-right-line.fr-icon--lg>
= t('.modal.title')
%p= error_message
- if wrapped?
#ineligibilite_rules_modal
= modal_content
- else
= modal_content

View file

@ -1,3 +1,3 @@
= render Dsfr::AlertComponent.new(state: :warning) do |c|
= render Dsfr::AlertComponent.new(state: :warning, extra_class_names: "fr-mb-3w") do |c|
- c.with_body do
= t('.pending_republish_html', href: admin_procedure_path(@procedure.id))

View file

@ -11,7 +11,8 @@ module Administrateurs
draft_revision.assign_attributes(procedure_revision_params)
if draft_revision.validate(:ineligibilite_rules_editor) && draft_revision.save
redirect_to edit_admin_procedure_ineligibilite_rules_path(@procedure)
flash[:notice] = "Les conditions dinéligibilité ont été modifiées."
redirect_to [:admin, @procedure]
else
flash[:alert] = draft_revision.errors.full_messages
render :edit

View file

@ -280,9 +280,7 @@ module Users
def update
@dossier = dossier.en_construction? ? dossier.find_editing_fork(dossier.user) : dossier
@dossier = dossier_with_champs(pj_template: false)
@can_passer_en_construction_was, @can_passer_en_construction_is = dossier.track_can_passer_en_construction do
update_dossier_and_compute_errors
end
update_dossier_and_compute_errors
respond_to do |format|
format.turbo_stream do

View file

@ -80,11 +80,13 @@ export class AutosaveController extends ApplicationController {
// Wait next tick so champs having JS can interact
// with form elements before extracting form data.
setTimeout(() => {
this.enqueueAutosaveRequest();
this.enqueueAutosaveWithValidationRequest();
this.showConditionnalSpinner(target);
}, 0);
},
inputable: (target) => this.enqueueOnInput(target, true),
inputable: (target) => {
this.enqueueOnInput(target, true);
},
hidden: (target) => {
// In comboboxes we dispatch a "change" event on hidden inputs to trigger autosave.
// We want to debounce them.
@ -152,7 +154,7 @@ export class AutosaveController extends ApplicationController {
private didRequestRetry() {
if (this.#needsRetry) {
this.enqueueAutosaveRequest();
this.enqueueAutosaveWithValidationRequest();
}
}

View file

@ -1,19 +0,0 @@
import { ApplicationController } from './application_controller';
declare interface modal {
disclose: () => void;
}
declare interface dsfr {
modal: modal;
}
declare const window: Window &
typeof globalThis & { dsfr: (elem: HTMLElement) => dsfr };
export class InvalidIneligibiliteRulesController extends ApplicationController {
static targets = ['dialog'];
declare dialogTarget: HTMLElement;
connect() {
setTimeout(() => window.dsfr(this.dialogTarget).modal.disclose(), 100);
}
}

View file

@ -38,15 +38,15 @@ module DossierCloneConcern
end
def forked_with_changes?
if forked_diff.present?
forked_diff.values.any?(&:present?) || forked_groupe_instructeur_changed?
end
return false if forked_diff.blank?
forked_diff.values.any?(&:present?) || forked_groupe_instructeur_changed?
end
def champ_forked_with_changes?(champ)
if forked_diff.present?
forked_diff.values.any? { |champs| champs.any? { _1.public_id == champ.public_id } }
end
return false if forked_diff.blank?
forked_diff.values.any? { |champs| champs.any? { _1.public_id == champ.public_id } }
end
def make_diff(editing_fork)

View file

@ -1025,18 +1025,6 @@ class Dossier < ApplicationRecord
procedure.accuse_lecture? && termine?
end
def track_can_passer_en_construction
if !revision.ineligibilite_enabled
yield
[true, true] # without eligibilite rules, we never reach dossier.champs.visible?, don't cache anything
else
from = can_passer_en_construction? # with eligibilite rules, self.champ[x].visible is cached by passing thru conditions checks
yield
champs.map(&:reset_visible) # we must reset self.champs[x].visible?, because an update occurred and we should re-evaluate champs[x] visibility
[from, can_passer_en_construction?]
end
end
private
def build_default_champs

View file

@ -1,10 +1,7 @@
= render partial: 'shared/dossiers/update_champs', locals: { to_show: @to_show, to_hide: @to_hide, to_update: @to_update, dossier: @dossier }
- if !params.key?(:validate)
- if @can_passer_en_construction_was && !@can_passer_en_construction_is
= turbo_stream.append('contenu', render(Dossiers::InvalidIneligibiliteRulesComponent.new(dossier: @dossier)))
- else @ineligibilite_rules_is_computable
= turbo_stream.remove(dom_id(@dossier, :ineligibilite_rules_broken))
- if params[:validate].present? && @dossier.revision.ineligibilite_enabled?
= turbo_stream.update :ineligibilite_rules_modal, render(Dossiers::InvalidIneligibiliteRulesComponent.new(dossier: @dossier, wrapped: false))
- if @update_contact_information
= turbo_stream.update "contact_information", partial: 'shared/dossiers/update_contact_information', locals: { dossier: @dossier, procedure: @dossier.procedure }

View file

@ -0,0 +1,44 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Dossiers::AutosaveFooterComponent, type: :component do
subject(:component) { render_inline(described_class.new(dossier:, annotation:)) }
let(:dossier) { create(:dossier) }
let(:annotation) { false }
context 'when showing brouillon state (default state)' do
it 'displays brouillon explanation' do
expect(component).to have_text("Votre brouillon")
end
end
context 'when editing fork and can pass en construction' do
let(:dossier) { create(:dossier, :en_construction).find_or_create_editing_fork(create(:user)) }
it 'displays en construction explanation' do
expect(component).to have_text("Vos modifications")
expect(component).to have_text("Déposez-les")
end
context 'when dossier is not eligible' do
before do
allow(dossier).to receive(:can_passer_en_construction?).and_return(false)
end
it 'displays en construction explanation' do
expect(component).to have_text("Vos modifications")
expect(component).not_to have_text("Déposez-les")
end
end
end
context 'when showing annotations' do
let(:annotation) { true }
it 'displays annotations explanation' do
expect(component).to have_text("Vos annotations")
end
end
end

View file

@ -243,7 +243,8 @@ describe Administrateurs::IneligibiliteRulesController, type: :controller do
draft_revision = procedure.reload.draft_revision
expect(draft_revision.ineligibilite_message).to eq('panpan')
expect(draft_revision.ineligibilite_enabled).to eq(true)
expect(response).to redirect_to(edit_admin_procedure_ineligibilite_rules_path(procedure))
expect(response).to redirect_to(admin_procedure_path(procedure))
expect(flash.notice).not_to be_empty
end
end
end

View file

@ -775,9 +775,11 @@ describe Users::DossiersController, type: :controller do
let(:types_de_champ_public) { [{ type: :text }, { type: :integer_number }] }
let(:text_champ) { dossier.project_champs_public.first }
let(:number_champ) { dossier.project_champs_public.last }
let(:validate) { "true" }
let(:submit_payload) do
{
id: dossier.id,
validate:,
dossier: {
groupe_instructeur_id: dossier.groupe_instructeur_id,
champs_public_attributes: {
@ -805,28 +807,36 @@ describe Users::DossiersController, type: :controller do
end
render_views
context 'when it switches from true to false' do
context 'when it becomes invalid' do
let(:value) { must_be_greater_than + 1 }
it 'raises popup' do
subject
dossier.reload
expect(dossier.can_passer_en_construction?).to be_falsey
expect(assigns(:can_passer_en_construction_was)).to eq(true)
expect(assigns(:can_passer_en_construction_is)).to eq(false)
expect(response.body).to match(ActionView::RecordIdentifier.dom_id(dossier, :ineligibilite_rules_broken))
expect(response.body).to match(/aria-controls='modal-eligibilite-rules-dialog'[^>]*data-fr-opened='true'/)
end
end
context 'when it stays true' do
context 'when it says valid' do
let(:value) { must_be_greater_than - 1 }
it 'does nothing' do
subject
dossier.reload
expect(dossier.can_passer_en_construction?).to be_truthy
expect(assigns(:can_passer_en_construction_was)).to eq(true)
expect(assigns(:can_passer_en_construction_is)).to eq(true)
expect(response.body).not_to have_selector("##{ActionView::RecordIdentifier.dom_id(dossier, :ineligibilite_rules_broken)}")
expect(response.body).to match(/aria-controls='modal-eligibilite-rules-dialog'[^>]*data-fr-opened='false'/)
end
end
context 'when not validating' do
let(:validate) { nil }
let(:value) { must_be_greater_than + 1 }
it 'does not render invalid ineligible modal' do
subject
dossier.reload
expect(dossier.can_passer_en_construction?).to be_falsey
expect(response.body).not_to include("aria-controls='modal-eligibilite-rules-dialog'")
end
end
end

View file

@ -28,21 +28,21 @@ describe 'Dossier Inéligibilité', js: true do
visit brouillon_dossier_path(dossier)
# no error while dossier is empty
expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: false)
expect(page).not_to have_content("Vous ne pouvez pas déposer votre dossier")
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: false)
# does raise error when dossier is filled with condition that does not match
# does nothing when dossier is filled with condition that does not match
within "#champ-1" do
find("label", text: "Non").click
end
expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: false)
expect(page).not_to have_content("Vous ne pouvez pas déposer votre dossier")
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: false)
# raise error when dossier is filled with condition that matches
# open modal when dossier is filled with condition that matches
within "#champ-1" do
find("label", text: "Oui").click
end
expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: true)
expect(page).to have_content("Vous ne pouvez pas déposer votre dossier")
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: true)
# reload page and see error
visit brouillon_dossier_path(dossier)
@ -51,6 +51,7 @@ describe 'Dossier Inéligibilité', js: true do
# modal is closable, and we can change our dossier response to be eligible
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: true)
expect(page).to have_text("Vous ne pouvez pas déposer votre dossier")
within("#modal-eligibilite-rules-dialog") { click_on "Fermer" }
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: false)
@ -78,7 +79,7 @@ describe 'Dossier Inéligibilité', js: true do
visit brouillon_dossier_path(dossier)
# no error while dossier is empty
expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: false)
expect(page).not_to have_content("Vous ne pouvez pas déposer votre dossier")
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: false)
# first condition matches (so ineligible), cannot submit dossier and error message is clear
within "#champ-#{first_tdc.stable_id}" do
@ -148,14 +149,14 @@ describe 'Dossier Inéligibilité', js: true do
visit brouillon_dossier_path(dossier)
# no error while dossier is empty
expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: false)
expect(page).not_to have_content("Vous ne pouvez pas déposer votre dossier")
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: false)
# only one condition is matches, can submit dossier
within "#champ-#{first_tdc.stable_id}" do
find("label", text: "Oui").click
end
expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: false)
expect(page).not_to have_content("Vous ne pouvez pas déposer votre dossier")
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: false)
# Now test dossier modification
click_on "Déposer le dossier"
@ -196,7 +197,7 @@ describe 'Dossier Inéligibilité', js: true do
scenario 'ineligibilite rules without validation on champ ensure to re-process cached champs.visible' do
visit brouillon_dossier_path(dossier)
expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: false)
expect(page).not_to have_content("Vous ne pouvez pas déposer votre dossier")
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: false)
within "#champ-1" do
find("label", text: "Non").click

View file

@ -160,10 +160,11 @@ describe 'shared/dossiers/edit', type: :view do
before do
allow(dossier).to receive(:can_passer_en_construction?).and_return(false)
allow(dossier.revision).to receive(:ineligibilite_enabled?).and_return(true)
end
it 'renders broken transitions rules dialog' do
expect(subject).to have_selector("##{ActionView::RecordIdentifier.dom_id(dossier, :ineligibilite_rules_broken)}")
expect(subject).to have_selector("#ineligibilite_rules_modal [data-fr-opened='true']")
end
end
end