diff --git a/.circleci/config.yml b/.circleci/config.yml index f0538ae06..887b8e06b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,10 +12,12 @@ defaults: &defaults bundle_restore_cache: &bundle_restore_cache restore_cache: + name: Restore Bundler Package Cache key: bundle-install-v8-{{ arch }}-{{ checksum "Gemfile.lock" }} bundle_save_cache: &bundle_save_cache save_cache: + name: Save Bundler Package Cache key: bundle-install-v8-{{ arch }}-{{ checksum "Gemfile.lock" }} paths: - ~/vendor/bundle @@ -27,13 +29,15 @@ bundle_install: &bundle_install yarn_restore_cache: &yarn_restore_cache restore_cache: - key: yarn-install-v8-{{ arch }}-{{ checksum "yarn.lock" }} + name: Restore Yarn Package Cache + key: yarn-install-v1-{{ arch }}-{{ checksum "yarn.lock" }} yarn_save_cache: &yarn_save_cache save_cache: - key: yarn-install-v8-{{ arch }}-{{ checksum "yarn.lock" }} + name: Save Yarn Package Cache + key: yarn-install-v1-{{ arch }}-{{ checksum "yarn.lock" }} paths: - - ~/node_modules + - ~/.cache/yarn yarn_install: &yarn_install run: diff --git a/README.md b/README.md index 1d3aa8160..bb1c41a6d 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,7 @@ Une fois `overmind` lancé, et un breakpoint `byebug` inséré dans le code, il ## Régénérer les binstubs bundle binstub railties --force + bundle binstub unicorn --force bin/rake rails:update:bin ## Tâches Super Admin diff --git a/app/assets/stylesheets/new_design/buttons.scss b/app/assets/stylesheets/new_design/buttons.scss index 230529883..130dc98d8 100644 --- a/app/assets/stylesheets/new_design/buttons.scss +++ b/app/assets/stylesheets/new_design/buttons.scss @@ -10,11 +10,11 @@ line-height: 20px; background-color: #FFFFFF; color: $black; - cursor: pointer; text-align: center; -webkit-appearance: none; - &:hover { + &:hover:not(:disabled) { + cursor: pointer; background: $light-grey; text-decoration: none; } @@ -24,12 +24,17 @@ outline: none; } + &:disabled { + opacity: 0.5; + filter: saturate(50%); + } + &.primary { color: #FFFFFF; border-color: $blue; background-color: $blue; - &:hover { + &:hover:not(:disabled) { background: $light-blue; } } @@ -39,7 +44,7 @@ border-color: $blue; background-color: #FFFFFF; - &:hover { + &:hover:not(:disabled) { color: #FFFFFF; background: $light-blue; } @@ -62,7 +67,7 @@ border-color: $green; background-color: $green; - &:hover { + &:hover:not(:disabled) { color: $green; background-color: #FFFFFF; } @@ -73,7 +78,7 @@ border-color: $black; background-color: $black; - &:hover { + &:hover:not(:disabled) { color: $black; background-color: #FFFFFF; } @@ -84,7 +89,7 @@ border-color: $dark-red; background-color: $dark-red; - &:hover { + &:hover:not(:disabled) { color: $dark-red; background-color: #FFFFFF; } diff --git a/app/assets/stylesheets/new_design/dossier-edit.scss b/app/assets/stylesheets/new_design/dossier-edit.scss index cfea3941d..b37369943 100644 --- a/app/assets/stylesheets/new_design/dossier-edit.scss +++ b/app/assets/stylesheets/new_design/dossier-edit.scss @@ -1,24 +1,27 @@ @import "colors"; @import "constants"; -.dossier-edit { - .dossier-header { - background-color: $light-grey; - margin-bottom: $default-padding; +.dossier-header { + .container { + padding-bottom: $default-padding; + } - .container { - padding: $default-padding; - } + h1 { + font-size: 22px; - h1 { - font-size: 22px; - - .icon.folder { - vertical-align: -3px; - } + .icon.folder { + vertical-align: -3px; } } + .dossier-form-actions { + margin-top: $default-padding; + margin-bottom: $default-padding; + text-align: right; + } +} + +.dossier-edit { .prologue { margin: (1.5 * $default-padding) 0; display: flex; diff --git a/app/assets/stylesheets/new_design/forms.scss b/app/assets/stylesheets/new_design/forms.scss index ad61d5288..4399f5891 100644 --- a/app/assets/stylesheets/new_design/forms.scss +++ b/app/assets/stylesheets/new_design/forms.scss @@ -316,6 +316,15 @@ } } + .send-notice { + @include notice-text-style; + margin-bottom: $default-padding; + } + + .send-wrapper + .send-notice { + margin-top: - $default-padding; + } + .inline-champ { margin-left: $default-spacer; margin-right: $default-spacer; diff --git a/app/assets/stylesheets/new_design/invites_form.scss b/app/assets/stylesheets/new_design/invites_form.scss new file mode 100644 index 000000000..81580da30 --- /dev/null +++ b/app/assets/stylesheets/new_design/invites_form.scss @@ -0,0 +1,35 @@ +@import "constants"; + +#invites-form { + padding: $default-padding; + text-align: left; + + form { + display: flex; + margin-top: $default-padding; + } + + h4 { + font-weight: bold; + margin-bottom: $default-spacer; + } + + p { + margin-bottom: $default-spacer; + } + + ul { + list-style-position: inside; + list-style-type: disc; + margin-bottom: $default-padding; + } + + input[type=email] { + width: auto; + margin-bottom: 0; + } + + .button { + margin-left: $default-spacer; + } +} diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index 25277784b..d3cd732de 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -2,10 +2,11 @@ class InvitesController < ApplicationController before_action :ensure_user_signed_in def create - email = params[:email].downcase + email = params[:invite_email].downcase + dossier = current_user.dossiers.find(params[:dossier_id]) invite = InviteUser.create( - dossier: current_user.dossiers.find(params[:dossier_id]), + dossier: dossier, user: User.find_by(email: email), email: email, email_sender: current_user.email @@ -18,12 +19,15 @@ class InvitesController < ApplicationController InviteMailer.invite_guest(invite).deliver_later end - flash.notice = "Invitation envoyée (#{invite.email})" + flash.notice = "Une invitation a été envoyée à #{invite.email}." else flash.alert = invite.errors.full_messages end - redirect_to url_for(controller: 'users/recapitulatif', action: :show, dossier_id: params['dossier_id']) + respond_to do |format| + format.html { redirect_back(fallback_location: helpers.url_for_dossier(dossier)) } + format.js { @dossier = dossier } + end end private diff --git a/app/controllers/users/dossiers/invites_controller.rb b/app/controllers/users/dossiers/invites_controller.rb index 903a15f75..343edf98c 100644 --- a/app/controllers/users/dossiers/invites_controller.rb +++ b/app/controllers/users/dossiers/invites_controller.rb @@ -9,7 +9,11 @@ class Users::Dossiers::InvitesController < UsersController def show @facade = InviteDossierFacades.new params[:id].to_i, current_user.email - render 'users/recapitulatif/show' + if @facade.dossier.brouillon? + redirect_to modifier_dossier_path(@facade.dossier) + else + render 'users/recapitulatif/show' + end rescue ActiveRecord::RecordNotFound flash.alert = t('errors.messages.dossier_not_found') redirect_to url_for dossiers_path diff --git a/app/facades/dossier_facades.rb b/app/facades/dossier_facades.rb index 9c291942f..635f8da82 100644 --- a/app/facades/dossier_facades.rb +++ b/app/facades/dossier_facades.rb @@ -54,6 +54,6 @@ class DossierFacades end def followers - Gestionnaire.joins(:follows).where("follows.dossier_id=#{@dossier.id}") + @dossier.followers_gestionnaires end end diff --git a/app/views/invites/_form.html.haml b/app/views/invites/_form.html.haml new file mode 100644 index 000000000..ec220af02 --- /dev/null +++ b/app/views/invites/_form.html.haml @@ -0,0 +1,17 @@ +#invites-form + - if dossier.invites.present? + %h4 Personnes invitées à participer à ce dossier + %ul + - dossier.invites.each do |invite| + %li= invite.email + %p Ces personnes peuvent modifier ce dossier. + - if dossier.brouillon? + %p Une fois le dossier complet, vous devez le soumettre vous-même. + + - else + %p Vous pouvez inviter quelqu’un à remplir ce dossier avec vous. + %p Cette personne aura le droit de modifier votre dossier. + + = form_tag invites_dossier_path(dossier_id: dossier.id), remote: true, method: :post, class: 'form' do + = email_field_tag :invite_email, '', class: 'small', placeholder: 'adresse email', required: true + = submit_tag 'Envoyer une invitation', class: 'button accepted' diff --git a/app/views/invites/create.js.erb b/app/views/invites/create.js.erb new file mode 100644 index 000000000..19c233149 --- /dev/null +++ b/app/views/invites/create.js.erb @@ -0,0 +1,6 @@ +var formView = "<%= escape_javascript(render partial: 'invites/form', locals: { dossier: @dossier }) %>"; +document.querySelector("#invites-form").outerHTML = formView; + +var flashMessagesView = "<%= escape_javascript(render partial: 'layouts/flash_messages') %>"; +document.querySelector("#flash_messages").outerHTML = flashMessagesView; +<% flash.clear %> diff --git a/app/views/layouts/_flash_messages.html.haml b/app/views/layouts/_flash_messages.html.haml index 9c00630b5..5f4b14ef3 100644 --- a/app/views/layouts/_flash_messages.html.haml +++ b/app/views/layouts/_flash_messages.html.haml @@ -1,11 +1,12 @@ -- if flash.any? - #flash_message.center - - flash.each do |key, value| - - if value.class == Array - .alert{ class: flash_class(key) } - - value.each do |message| - = sanitize(message) - %br - - else - .alert{ class: flash_class(key) } - = sanitize(value) +#flash_messages + - if flash.any? + #flash_message.center + - flash.each do |key, value| + - if value.class == Array + .alert{ class: flash_class(key) } + - value.each do |message| + = sanitize(message) + %br + - else + .alert{ class: flash_class(key) } + = sanitize(value) diff --git a/app/views/layouts/navbars/_navbar_users_recapitulatifcontroller_show.html.haml b/app/views/layouts/navbars/_navbar_users_recapitulatifcontroller_show.html.haml index cfb5b1fb9..52bbb5926 100644 --- a/app/views/layouts/navbars/_navbar_users_recapitulatifcontroller_show.html.haml +++ b/app/views/layouts/navbars/_navbar_users_recapitulatifcontroller_show.html.haml @@ -20,5 +20,5 @@ %li = form_tag invites_dossier_path(dossier_id: @facade.dossier.id), method: :post, class: 'form-inline', id: 'send-invitation' do - = text_field_tag :email, '', class: 'form-control', placeholder: 'Envoyer une invitation', id: 'invitation-email' + = text_field_tag :invite_email, '', class: 'form-control', placeholder: 'Envoyer une invitation', id: 'invite_email' = submit_tag 'Ajouter', class: 'btn btn-success', data: { confirm: "Envoyer l'invitation ?" } diff --git a/app/views/new_user/dossiers/modifier.html.haml b/app/views/new_user/dossiers/modifier.html.haml index f7fd7a58f..0a20b7d77 100644 --- a/app/views/new_user/dossiers/modifier.html.haml +++ b/app/views/new_user/dossiers/modifier.html.haml @@ -2,10 +2,8 @@ = render partial: "new_user/dossiers/footer", locals: { dossier: @dossier } .dossier-edit - .dossier-header + .dossier-header.sub-header .container - %h1 - %span.icon.folder - = @dossier.procedure.libelle + = render partial: "shared/dossiers/header", locals: { dossier: @dossier, apercu: false } = render partial: "shared/dossiers/edit", locals: { dossier: @dossier, apercu: false } diff --git a/app/views/root/patron.html.haml b/app/views/root/patron.html.haml index be5252fce..69d0400af 100644 --- a/app/views/root/patron.html.haml +++ b/app/views/root/patron.html.haml @@ -58,6 +58,15 @@ = link_to "#", class: "button icon-only" do %span.icon.follow + %p + %button.button{ disabled: true } .button.disabled + + %button.button.primary{ disabled: true } .button.primary.disabled + + %button.button.secondary{ disabled: true } .button.secondary.disabled + + %button.button.danger{ disabled: true } .button.danger.disabled + %p = link_to ".button.accepted", "#", class: "button accepted" diff --git a/app/views/shared/dossiers/_edit.html.haml b/app/views/shared/dossiers/_edit.html.haml index eaf3e6c18..c43012547 100644 --- a/app/views/shared/dossiers/_edit.html.haml +++ b/app/views/shared/dossiers/_edit.html.haml @@ -74,9 +74,10 @@ class: 'button send secondary', data: { action: 'draft', disable_with: 'Envoi...' } - - if current_user.owns?(dossier) && dossier.can_transition_to_en_construction? + - if dossier.can_transition_to_en_construction? = f.button 'Soumettre le dossier', class: 'button send primary', + disabled: !current_user.owns?(dossier), data: { action: 'submit', disable_with: 'Envoi...' } - else @@ -84,4 +85,8 @@ class: 'button send primary', data: { action: 'submit', disable_with: 'Envoi...' } + - if dossier.brouillon? && !current_user.owns?(dossier) + .send-notice.invite-cannot-submit + En tant qu’invité, vous pouvez remplir ce formulaire – mais le titulaire du dossier doit le soumettre lui-même. + = render partial: "shared/dossiers/submit_is_over", locals: { dossier: dossier } diff --git a/app/views/shared/dossiers/_header.html.haml b/app/views/shared/dossiers/_header.html.haml new file mode 100644 index 000000000..d73c8c377 --- /dev/null +++ b/app/views/shared/dossiers/_header.html.haml @@ -0,0 +1,16 @@ +%h1 + %span.icon.folder + = dossier.procedure.libelle + +.dossier-form-actions + - if current_user.owns?(dossier) + %span.button.dropdown.invite-user-action + %span.icon.person + - if dossier.invites.count > 0 + Voir les personnes invitées + %span.badge= dossier.invites.count + - else + Inviter une personne à modifier ce dossier + + .dropdown-content.fade-in-down + = render partial: "invites/form", locals: { dossier: dossier } diff --git a/bin/bundle b/bin/bundle index f19acf5b5..524dfd3f2 100755 --- a/bin/bundle +++ b/bin/bundle @@ -1,3 +1,105 @@ #!/usr/bin/env ruby -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) -load Gem.bin_path('bundler', 'bundle') +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 || ">= 0.a" + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../../Gemfile", __FILE__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_version + @bundler_version ||= begin + env_var_version || cli_arg_version || + lockfile_version || "#{Gem::Requirement.default}.a" + end + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + # must dup string for RG < 1.8 compatibility + activate_bundler(bundler_version.dup) + end + + def activate_bundler(bundler_version) + if Gem::Version.correct?(bundler_version) && Gem::Version.new(bundler_version).release < Gem::Version.new("2.0") + bundler_version = "< 2" + end + gem_error = activation_error_handling do + gem "bundler", bundler_version + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_version).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_version}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_version}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/bin/unicorn b/bin/unicorn new file mode 100755 index 000000000..d3f6570b5 --- /dev/null +++ b/bin/unicorn @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'unicorn' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path("../bundle", __FILE__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("unicorn", "unicorn") diff --git a/bin/unicorn_rails b/bin/unicorn_rails new file mode 100755 index 000000000..2a7368546 --- /dev/null +++ b/bin/unicorn_rails @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'unicorn_rails' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path("../bundle", __FILE__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("unicorn", "unicorn_rails") diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb index d65754633..63295fd8d 100644 --- a/spec/controllers/invites_controller_spec.rb +++ b/spec/controllers/invites_controller_spec.rb @@ -11,7 +11,7 @@ describe InvitesController, type: :controller do sign_in signed_in_profile end - subject { post :create, params: { dossier_id: dossier.id, email: email } } + subject { post :create, params: { dossier_id: dossier.id, invite_email: email } } context "when gestionnaire is signed_in" do let(:signed_in_profile) { create(:gestionnaire) } @@ -69,11 +69,16 @@ describe InvitesController, type: :controller do context 'when user has access to dossier' do before do + request.env["HTTP_REFERER"] = "/dossiers/#{dossier.id}/modifier" dossier.update(user: signed_in_profile) end it { expect { subject }.to change(InviteUser, :count).by(1) } + it "redirects to the previous URL" do + expect(subject).to redirect_to("/dossiers/#{dossier.id}/modifier") + end + context 'when email is assign to an user' do let! (:user_invite) { create(:user, email: email) } diff --git a/spec/controllers/new_user/dossiers_controller_spec.rb b/spec/controllers/new_user/dossiers_controller_spec.rb index 22c7ff106..3d891b1f9 100644 --- a/spec/controllers/new_user/dossiers_controller_spec.rb +++ b/spec/controllers/new_user/dossiers_controller_spec.rb @@ -96,6 +96,47 @@ describe NewUser::DossiersController, type: :controller do end end + describe "#forbid_invite_submission!" do + let(:user) { create(:user) } + let(:asked_dossier) { create(:dossier) } + let(:ensure_authorized) { :forbid_invite_submission! } + let(:submit_action) { 'submit' } + + before do + @controller.params = @controller.params.merge(dossier_id: asked_dossier.id, submit_action: submit_action) + allow(@controller).to receive(:current_user).and_return(user) + allow(@controller).to receive(:redirect_to) + end + + context 'when a user save their own draft' do + let(:asked_dossier) { create(:dossier, user: user) } + let(:submit_action) { 'draft' } + + it_behaves_like 'does not redirect nor flash' + end + + context 'when a user submit their own dossier' do + let(:asked_dossier) { create(:dossier, user: user) } + let(:submit_action) { 'submit' } + + it_behaves_like 'does not redirect nor flash' + end + + context 'when an invite save the draft for a dossier where they where invited' do + before { create(:invite, dossier: asked_dossier, user: user, type: 'InviteUser') } + let(:submit_action) { 'draft' } + + it_behaves_like 'does not redirect nor flash' + end + + context 'when an invite submit a dossier where they where invited' do + before { create(:invite, dossier: asked_dossier, user: user, type: 'InviteUser') } + let(:submit_action) { 'submit' } + + it_behaves_like 'redirects and flashes' + end + end + describe 'attestation' do before { sign_in(user) } diff --git a/spec/controllers/users/dossiers/invites_controller_spec.rb b/spec/controllers/users/dossiers/invites_controller_spec.rb index 90e61899c..f925c3c7f 100644 --- a/spec/controllers/users/dossiers/invites_controller_spec.rb +++ b/spec/controllers/users/dossiers/invites_controller_spec.rb @@ -1,78 +1,78 @@ describe Users::Dossiers::InvitesController, type: :controller do describe '#authenticate_user!' do let(:user) { create :user } - let(:invite) { create :invite } + let(:dossier) { create(:dossier, :en_construction) } + let(:invite) { create(:invite, dossier: dossier) } + + subject { get :show, params: { id: invite.id, email: email } } context 'when email is not set' do - context 'when user is not connected' do - before do - get :show, params: { id: invite.id } - end + let(:email) { nil } + context 'and user is not connected' do it { is_expected.to redirect_to new_user_session_path } end - context 'when user is connected' do - let!(:invite) { create :invite, user: user } - - before do - sign_in invite.user - - get :show, params: { id: invite.id } - end - - it { expect(response.status).to eq 200 } + context 'and user is connected' do + let(:invite) { create :invite, dossier: dossier, user: user } + before { sign_in invite.user } + it { is_expected.to have_http_status(:ok) } end end - context 'when email is set' do - before do - get :show, params: { id: invite.id, email: email } - end - - context 'when email is blank' do - let(:email) { '' } + context 'when email is blank' do + let(:email) { '' } + it { is_expected.to redirect_to new_user_session_path } + end + context 'when email is not blank' do + context 'when email is affected at an user' do + let(:email) { user.email } it { is_expected.to redirect_to new_user_session_path } end - context 'when email is not blank' do - context 'when email is affected at an user' do - let(:email) { user.email } - - it { is_expected.to redirect_to new_user_session_path } - end - - context 'when email is not affected at an user' do - let(:email) { 'new_user@octo.com' } - - it { is_expected.to redirect_to new_user_registration_path(user_email: email) } - end + context 'when email is not affected at an user' do + let(:email) { 'new_user@octo.com' } + it { is_expected.to redirect_to new_user_registration_path(user_email: email) } end end end describe '#GET show' do let(:user) { create :user } - - let(:invite) { create :invite, email: email, dossier: (create :dossier) } - - subject { get :show, params: { id: invite.id } } + let(:dossier) { create :dossier } + let(:invite) { create :invite, email: email, dossier: dossier } before do sign_in user end - context 'when invitation ID is attach at the user email account' do + subject! { get :show, params: { id: invite.id } } + + context 'when invitation ID is attached at the user email account' do let(:email) { user.email } - it { expect(subject.status).to eq 200 } + + context 'and dossier is a brouillon' do + let(:dossier) { create :dossier, state: 'brouillon' } + + it { is_expected.to have_http_status(302) } + it { is_expected.to redirect_to modifier_dossier_path(dossier) } + end + + context 'and dossier is not a brouillon' do + let(:dossier) { create :dossier, :en_construction } + + it { is_expected.to have_http_status(:ok) } + it { is_expected.to render_template('users/recapitulatif/show') } + end end - context 'when invitation ID is not attach at the user email account' do + context 'when invitation ID is not attached at the user email account' do let(:email) { 'fake@email.com' } - it { expect(subject.status).to eq 302 } + it { is_expected.to have_http_status(302) } it { is_expected.to redirect_to dossiers_path } + it { expect(flash[:alert]).to be_present } end end end diff --git a/spec/features/new_user/invite_spec.rb b/spec/features/new_user/invite_spec.rb new file mode 100644 index 000000000..d907e870f --- /dev/null +++ b/spec/features/new_user/invite_spec.rb @@ -0,0 +1,135 @@ +require 'spec_helper' + +feature 'Invitations' do + let(:user) { create(:user) } + let(:invited_user) { create(:user, email: 'user_invite@exemple.fr') } + let(:procedure) { create(:procedure, :published, :with_type_de_champ) } + let(:invite) { create(:invite_user, user: invited_user, dossier: dossier) } + + context 'when the dossier is a brouillon' do + let!(:dossier) { create(:dossier, :for_individual, state: 'brouillon', user: user, procedure: procedure) } + + scenario 'on the form, a user can invite another user to collaborate on the dossier', js: true do + log_in(user) + navigate_to_brouillon(dossier) + + fill_in 'Libelle du champ', with: 'Some edited value' + send_invite_to "user_invite@exemple.fr" + + expect(page).to have_current_path(modifier_dossier_path(dossier)) + expect(page).to have_text("Une invitation a été envoyée à user_invite@exemple.fr.") + expect(page).to have_text("user_invite@exemple.fr") + + # Ensure unsaved edits to the form are not lost + expect(page).to have_field('Libelle du champ', with: 'Some edited value') + end + + scenario 'an invited user can see and edit the draft', js: true do + visit users_dossiers_invite_path(invite) + expect(page).to have_current_path(new_user_session_path) + + submit_login_form(invited_user) + expect(page).to have_current_path(modifier_dossier_path(dossier)) + expect(page).to have_no_selector('.button.invite-user-action') + + fill_in 'Libelle du champ', with: 'Some edited value' + click_button 'Enregistrer le brouillon' + expect(page).to have_text('Votre brouillon a bien été sauvegardé') + expect(page).to have_field('Libelle du champ', with: 'Some edited value') + end + + scenario 'an invited user cannot submit the draft' do + visit users_dossiers_invite_path(invite) + expect(page).to have_current_path(new_user_session_path) + + submit_login_form(invited_user) + expect(page).to have_current_path(modifier_dossier_path(dossier)) + + expect(page).to have_button('Soumettre le dossier', disabled: true) + expect(page).to have_selector('.invite-cannot-submit') + end + end + + context 'when the dossier is en_construction' do + let!(:dossier) { create(:dossier, :for_individual, :en_construction, user: user, procedure: procedure) } + + scenario 'on dossier details, a user can invite another user to collaborate on the dossier', js: true do + log_in(user) + navigate_to_recapitulatif(dossier) + + legacy_send_invite_to "user_invite@exemple.fr" + + expect(page).to have_current_path(users_dossier_recapitulatif_path(dossier)) + expect(page).to have_text("Une invitation a été envoyée à user_invite@exemple.fr.") + expect(page).to have_text("user_invite@exemple.fr") + end + + scenario 'an invited user can see and edit the dossier', js: true do + visit users_dossiers_invite_path(invite) + expect(page).to have_current_path(new_user_session_path) + + submit_login_form(invited_user) + expect(page).to have_current_path(users_dossiers_invite_path(invite)) + expect(page).to have_no_selector('.button.invite-user-action') + expect(page).to have_text("Dossier nº #{dossier.id}") + + # We should be able to just click() the link, but Capybara detects that the + # enclosing div would be clicked instead. + expect(page).to have_link("MODIFIER", href: modifier_dossier_path(dossier)) + visit modifier_dossier_path(dossier) + + expect(page).to have_current_path(modifier_dossier_path(dossier)) + fill_in "Libelle du champ", with: "Some edited value" + click_button "Enregistrer les modifications du dossier" + + expect(page).to have_current_path(users_dossiers_invite_path(invite)) + expect(page).to have_text("Some edited value") + end + end + + private + + def log_in(user) + visit '/' + click_on 'Connexion' + submit_login_form(user) + expect(page).to have_current_path(dossiers_path) + end + + def submit_login_form(user) + fill_in 'user_email', with: user.email + fill_in 'user_password', with: user.password + click_on 'Se connecter' + end + + def navigate_to_brouillon(dossier) + expect(page).to have_current_path(dossiers_path) + click_on(dossier.id) + expect(page).to have_current_path(modifier_dossier_path(dossier)) + end + + def navigate_to_recapitulatif(dossier) + expect(page).to have_current_path(dossiers_path) + click_on(dossier.id) + expect(page).to have_current_path(users_dossier_recapitulatif_path(dossier)) + end + + def send_invite_to(invited_email) + find('.button.invite-user-action').click() + expect(page).to have_button("Envoyer une invitation", visible: true) + + fill_in 'invite_email', with: invited_email + click_on "Envoyer une invitation" + end + + def legacy_send_invite_to(invited_email) + find('.dropdown-toggle', text: "Voir les personnes impliquées").click() + expect(page).to have_button("Ajouter", visible: true) + + fill_in 'invite_email', with: invited_email + + page.accept_alert "Envoyer l'invitation ?" do + click_on "Ajouter" + end + end +end diff --git a/spec/features/users/list_dossiers_spec.rb b/spec/features/new_user/list_dossiers_spec.rb similarity index 100% rename from spec/features/users/list_dossiers_spec.rb rename to spec/features/new_user/list_dossiers_spec.rb diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 51f793f67..dbc79fffe 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -189,6 +189,14 @@ describe Dossier do let(:dossier) { create(:dossier, :with_entreprise, user: user, procedure: procedure, en_construction_at: date1, en_instruction_at: date2, processed_at: date3, motivation: "Motivation") } let!(:follow) { create(:follow, gestionnaire: gestionnaire, dossier: dossier) } + describe "followers_gestionnaires" do + let(:non_following_gestionnaire) { create(:gestionnaire) } + subject { dossier.followers_gestionnaires } + + it { expect(subject).to eq [gestionnaire] } + it { expect(subject).not_to include(non_following_gestionnaire) } + end + describe '#export_headers' do subject { dossier.export_headers }