diff --git a/.circleci/config.yml b/.circleci/config.yml index eb8386573..d08c361ae 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -69,6 +69,11 @@ jobs: DATABASE_URL: "postgres://tps_test@localhost:5432/tps_test" name: Create DB command: bundle exec rake db:create db:schema:load db:migrate RAILS_ENV=test + - run: + environment: + RAILS_ENV: test + name: Precompile Webpack assets + command: bin/webpack - run: environment: DATABASE_URL: "postgres://tps_test@localhost:5432/tps_test" diff --git a/app/assets/stylesheets/new_design/_typography.scss b/app/assets/stylesheets/new_design/_typography.scss index 351aac902..da7c458df 100644 --- a/app/assets/stylesheets/new_design/_typography.scss +++ b/app/assets/stylesheets/new_design/_typography.scss @@ -1,6 +1,6 @@ @import "colors"; %new-type { - font-family: "Muli"; + font-family: "Muli", system-ui, -apple-system, sans-serif; color: $black; } diff --git a/app/controllers/api/v1/dossiers_controller.rb b/app/controllers/api/v1/dossiers_controller.rb index 40cbcc1fa..5eb8eaadd 100644 --- a/app/controllers/api/v1/dossiers_controller.rb +++ b/app/controllers/api/v1/dossiers_controller.rb @@ -45,7 +45,7 @@ class API::V1::DossiersController < APIController render json: {}, status: :unauthorized end - @dossiers = @procedure.dossiers.state_not_brouillon + @dossiers = @procedure.dossiers.state_not_brouillon.order_for_api rescue ActiveRecord::RecordNotFound render json: {}, status: :not_found diff --git a/app/controllers/attachments_controller.rb b/app/controllers/attachments_controller.rb index c2edd378c..93f86f718 100644 --- a/app/controllers/attachments_controller.rb +++ b/app/controllers/attachments_controller.rb @@ -6,4 +6,11 @@ class AttachmentsController < ApplicationController @attachment = @blob.attachments.find(params[:id]) @user_can_upload = params[:user_can_upload] end + + def destroy + attachment = @blob.attachments.find(params[:id]) + @attachment_id = attachment.id + attachment.purge_later + flash.now.notice = 'La pièce jointe a bien été supprimée.' + end end diff --git a/app/controllers/gestionnaires/dossiers_controller.rb b/app/controllers/gestionnaires/dossiers_controller.rb index 02512b173..7a36bd6d0 100644 --- a/app/controllers/gestionnaires/dossiers_controller.rb +++ b/app/controllers/gestionnaires/dossiers_controller.rb @@ -141,13 +141,6 @@ module Gestionnaires redirect_to annotations_privees_gestionnaire_dossier_path(procedure, dossier) end - def purge_champ_piece_justificative - @champ = dossier.champs_private.find(params[:champ_id]) - @champ.piece_justificative_file.purge_later - - flash.notice = 'La pièce jointe a bien été supprimée.' - end - def print @dossier = dossier render layout: "print" diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index 420a81891..9e599a194 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -6,11 +6,11 @@ module Users layout 'procedure_context', only: [:identite, :update_identite, :siret, :update_siret] ACTIONS_ALLOWED_TO_ANY_USER = [:index, :recherche, :new] - ACTIONS_ALLOWED_TO_OWNER_OR_INVITE = [:show, :demande, :messagerie, :brouillon, :update_brouillon, :modifier, :update, :create_commentaire, :purge_champ_piece_justificative] + ACTIONS_ALLOWED_TO_OWNER_OR_INVITE = [:show, :demande, :messagerie, :brouillon, :update_brouillon, :modifier, :update, :create_commentaire] before_action :ensure_ownership!, except: ACTIONS_ALLOWED_TO_ANY_USER + ACTIONS_ALLOWED_TO_OWNER_OR_INVITE before_action :ensure_ownership_or_invitation!, only: ACTIONS_ALLOWED_TO_OWNER_OR_INVITE - before_action :ensure_dossier_can_be_updated, only: [:update_identite, :update_brouillon, :modifier, :update, :purge_champ_piece_justificative] + before_action :ensure_dossier_can_be_updated, only: [:update_identite, :update_brouillon, :modifier, :update] before_action :forbid_invite_submission!, only: [:update_brouillon] before_action :forbid_closed_submission!, only: [:update_brouillon] before_action :show_demarche_en_test_banner @@ -236,14 +236,6 @@ module Users redirect_to url_for dossiers_path end - def purge_champ_piece_justificative - @champ = dossier.champs.find(params[:champ_id]) - - @champ.piece_justificative_file.purge_later - - flash.notice = 'La pièce jointe a bien été supprimée.' - end - def dossier_for_help dossier_id = params[:id] || params[:dossier_id] @dossier || (dossier_id.present? && Dossier.find_by(id: dossier_id.to_i)) diff --git a/app/javascript/shared/carte.js b/app/javascript/shared/carte.js index 2ba1f7445..b6f76a27a 100644 --- a/app/javascript/shared/carte.js +++ b/app/javascript/shared/carte.js @@ -13,10 +13,13 @@ export function initMap(element, position, editable = false) { scrollWheelZoom: false }).setView([position.lat, position.lon], editable ? 18 : position.zoom); - L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { - attribution: - '© OpenStreetMap contributors' - }).addTo(map); + const loadTilesLayer = process.env.RAILS_ENV != 'test'; + if (loadTilesLayer) { + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: + '© OpenStreetMap contributors' + }).addTo(map); + } if (editable) { const freeDraw = new FreeDraw({ diff --git a/app/javascript/shared/utils.js b/app/javascript/shared/utils.js index f78c376fc..44297da9c 100644 --- a/app/javascript/shared/utils.js +++ b/app/javascript/shared/utils.js @@ -26,14 +26,17 @@ export function delegate(eventNames, selector, callback) { } export function getJSON(url, data, method = 'get') { + incrementActiveRequestsCount(); data = method !== 'get' ? JSON.stringify(data) : data; - return $.ajax({ - method, - url, - data, - contentType: 'application/json', - dataType: 'json' - }); + return Promise.resolve( + $.ajax({ + method, + url, + data, + contentType: 'application/json', + dataType: 'json' + }) + ).finally(decrementActiveRequestsCount); } export function scrollTo(container, scrollTo) { @@ -62,3 +65,15 @@ function offset(element) { left: rect.left + document.body.scrollLeft }; } + +const DATA_ACTIVE_REQUESTS_COUNT = 'data-active-requests-count'; + +function incrementActiveRequestsCount() { + const count = document.body.getAttribute(DATA_ACTIVE_REQUESTS_COUNT) || '0'; + document.body.setAttribute(DATA_ACTIVE_REQUESTS_COUNT, parseInt(count) + 1); +} + +function decrementActiveRequestsCount() { + const count = document.body.getAttribute(DATA_ACTIVE_REQUESTS_COUNT) || '0'; + document.body.setAttribute(DATA_ACTIVE_REQUESTS_COUNT, parseInt(count) - 1); +} diff --git a/app/models/dossier.rb b/app/models/dossier.rb index abcfd983f..0bcb8669a 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -51,6 +51,7 @@ class Dossier < ApplicationRecord scope :not_archived, -> { where(archived: false) } scope :order_by_updated_at, -> (order = :desc) { order(updated_at: order) } + scope :order_for_api, -> (order = :asc) { order(en_construction_at: order, created_at: order, id: order) } scope :all_state, -> { not_archived.state_not_brouillon } scope :en_construction, -> { not_archived.state_en_construction } diff --git a/app/views/attachments/destroy.js.erb b/app/views/attachments/destroy.js.erb new file mode 100644 index 000000000..ca79c22a1 --- /dev/null +++ b/app/views/attachments/destroy.js.erb @@ -0,0 +1,3 @@ +<%= render_flash(timeout: 5000, sticky: true) %> +<%= remove_element("#piece_justificative_#{@attachment_id}") %> +<%= show_element("#piece_justificative_file_#{@attachment_id}") %> diff --git a/app/views/gestionnaires/avis/instruction.html.haml b/app/views/gestionnaires/avis/instruction.html.haml index 25f03d299..dadee5f12 100644 --- a/app/views/gestionnaires/avis/instruction.html.haml +++ b/app/views/gestionnaires/avis/instruction.html.haml @@ -13,7 +13,7 @@ = form_for @avis, url: gestionnaire_avis_path(@avis), html: { class: 'form' } do |f| = f.text_area :answer, rows: 3, placeholder: 'Votre avis', required: true - = render partial: "shared/attachment/update", locals: { pj: @avis.piece_justificative_file, object: @avis, form: f } + = render partial: "shared/attachment/update", locals: { attachment: @avis.piece_justificative_file.attachment, user_can_destroy: true, form: f } .flex.justify-between.align-baseline %p.confidentiel.flex diff --git a/app/views/gestionnaires/dossiers/purge_champ_piece_justificative.js.erb b/app/views/gestionnaires/dossiers/purge_champ_piece_justificative.js.erb deleted file mode 100644 index 9d8807116..000000000 --- a/app/views/gestionnaires/dossiers/purge_champ_piece_justificative.js.erb +++ /dev/null @@ -1,3 +0,0 @@ -<%= render_flash(timeout: 5000, sticky: true) %> -<%= remove_element("#piece_justificative_#{@champ.id}") %> -<%= show_element("#champs_#{@champ.id}") %> diff --git a/app/views/shared/attachment/_update.html.haml b/app/views/shared/attachment/_update.html.haml index eb5c9dc83..ee86ddd7b 100644 --- a/app/views/shared/attachment/_update.html.haml +++ b/app/views/shared/attachment/_update.html.haml @@ -1,12 +1,22 @@ .piece-justificative - - if pj.attached? - .piece-justificative-actions{ id: "piece_justificative_#{object.id}" } + - if defined?(template) && template.attached? + %p.edit-pj-template.mb-1 + Veuillez télécharger, remplir et joindre + = link_to('le modèle suivant', url_for(template), target: '_blank', rel: 'noopener') + + - attachment_id = attachment ? attachment.id : SecureRandom.uuid + - user_can_destroy = defined?(user_can_destroy) ? user_can_destroy : false + - if attachment + .piece-justificative-actions{ id: "piece_justificative_#{attachment_id}" } .piece-justificative-action - = render partial: "shared/attachment/show", locals: { attachment: pj.attachment, user_can_upload: true } + = render partial: "shared/attachment/show", locals: { attachment: attachment, user_can_upload: true } + - if user_can_destroy + .piece-justificative-action + = link_to 'Supprimer', attachment_url(attachment.id, { signed_id: attachment.blob.signed_id }), remote: true, method: :delete, class: 'button small danger' .piece-justificative-action - = button_tag 'Remplacer', type: 'button', class: 'button small', data: { 'toggle-target': "#champs_#{object.id}" } + = button_tag 'Remplacer', type: 'button', class: 'button small', data: { 'toggle-target': "#piece_justificative_file_#{attachment_id}" } = form.file_field :piece_justificative_file, - id: "champs_#{object.id}", - class: "piece-justificative-input #{'hidden' if pj.attached?}", + id: "piece_justificative_file_#{attachment_id}", + class: "piece-justificative-input #{'hidden' if attachment}", direct_upload: true diff --git a/app/views/shared/dossiers/editable_champs/_piece_justificative.html.haml b/app/views/shared/dossiers/editable_champs/_piece_justificative.html.haml index 346317669..3fefd0238 100644 --- a/app/views/shared/dossiers/editable_champs/_piece_justificative.html.haml +++ b/app/views/shared/dossiers/editable_champs/_piece_justificative.html.haml @@ -1,24 +1 @@ -- pj = champ.piece_justificative_file - -.piece-justificative - - if champ.type_de_champ.piece_justificative_template.attached? - %p.edit-pj-template.mb-1 - Veuillez télécharger, remplir et joindre - = link_to('le modèle suivant', url_for(champ.type_de_champ.piece_justificative_template), target: '_blank', rel: 'noopener') - - - if pj.attached? - .piece-justificative-actions{ id: "piece_justificative_#{champ.id}" } - .piece-justificative-action - = render partial: "shared/attachment/show", locals: { attachment: pj.attachment, user_can_upload: true } - .piece-justificative-action - - if champ.private? - = link_to 'Supprimer', gestionnaire_champ_purge_champ_piece_justificative_path(procedure_id: champ.dossier.procedure_id, dossier_id: champ.dossier_id, champ_id: champ.id), remote: true, method: :delete, class: 'button small danger' - - else - = link_to 'Supprimer', champ_purge_champ_piece_justificative_path(id: champ.dossier_id, champ_id: champ.id), remote: true, method: :delete, class: 'button small danger' - .piece-justificative-action - = button_tag 'Remplacer', type: 'button', class: 'button small', data: { 'toggle-target': "#champs_#{champ.id}" } - - = form.file_field :piece_justificative_file, - id: "champs_#{champ.id}", - class: "piece-justificative-input #{'hidden' if pj.attached?}", - direct_upload: true += render partial: "shared/attachment/update", locals: { attachment: champ.piece_justificative_file.attachment, template: champ.type_de_champ.piece_justificative_template, user_can_destroy: true, form: form } diff --git a/app/views/users/dossiers/purge_champ_piece_justificative.js.erb b/app/views/users/dossiers/purge_champ_piece_justificative.js.erb deleted file mode 100644 index 9d8807116..000000000 --- a/app/views/users/dossiers/purge_champ_piece_justificative.js.erb +++ /dev/null @@ -1,3 +0,0 @@ -<%= render_flash(timeout: 5000, sticky: true) %> -<%= remove_element("#piece_justificative_#{@champ.id}") %> -<%= show_element("#champs_#{@champ.id}") %> diff --git a/config/routes.rb b/config/routes.rb index 16c0d618a..da1c66ecc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -134,6 +134,7 @@ Rails.application.routes.draw do end get 'attachments/:id', to: 'attachments#show', as: :attachment + delete 'attachments/:id', to: 'attachments#destroy' get 'tour-de-france' => 'root#tour_de_france' get "patron" => "root#patron" @@ -281,10 +282,6 @@ Rails.application.routes.draw do post 'commentaire' => 'dossiers#create_commentaire' post 'ask_deletion' get 'attestation' - - resources :champs, only: [] do - delete 'purge_champ_piece_justificative' => 'dossiers#purge_champ_piece_justificative' - end end collection do @@ -330,10 +327,6 @@ Rails.application.routes.draw do post 'send-to-instructeurs' => 'dossiers#send_to_instructeurs' post 'avis' => 'dossiers#create_avis' get 'print' => 'dossiers#print' - - resources :champs, only: [] do - delete 'purge_champ_piece_justificative' => 'dossiers#purge_champ_piece_justificative' - end end end end diff --git a/spec/controllers/attachments_controller_spec.rb b/spec/controllers/attachments_controller_spec.rb new file mode 100644 index 000000000..c9b732ca5 --- /dev/null +++ b/spec/controllers/attachments_controller_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe AttachmentsController, type: :controller do + let(:user) { create(:user) } + + describe '#destroy' do + render_views + + let(:attachment) { champ.piece_justificative_file.attachment } + let(:dossier) { create(:dossier, user: user) } + let(:champ) { create(:champ_piece_justificative, dossier_id: dossier.id) } + let(:signed_id) { attachment.blob.signed_id } + + subject do + delete :destroy, params: { id: attachment.id, signed_id: signed_id }, format: :js + end + + context "when authenticated" do + before { sign_in(user) } + + context 'and dossier is owned by user' do + it { is_expected.to have_http_status(200) } + + it do + subject + expect(champ.reload.piece_justificative_file.attached?).to be(false) + end + end + + context 'and signed_id is invalid' do + let(:signed_id) { 'yolo' } + + it { is_expected.to have_http_status(404) } + + it do + subject + expect(champ.reload.piece_justificative_file.attached?).to be(true) + end + end + end + + context 'when not authenticated' do + it { is_expected.to have_http_status(401) } + + it do + subject + expect(champ.reload.piece_justificative_file.attached?).to be(true) + end + end + end +end diff --git a/spec/controllers/gestionnaires/dossiers_controller_spec.rb b/spec/controllers/gestionnaires/dossiers_controller_spec.rb index ad7415874..570f6b3c4 100644 --- a/spec/controllers/gestionnaires/dossiers_controller_spec.rb +++ b/spec/controllers/gestionnaires/dossiers_controller_spec.rb @@ -469,44 +469,4 @@ describe Gestionnaires::DossiersController, type: :controller do it { expect(champ_repetition.champs.first.value).to eq('text') } it { expect(response).to redirect_to(annotations_privees_gestionnaire_dossier_path(dossier.procedure, dossier)) } end - - describe '#purge_champ_piece_justificative' do - before { sign_in(gestionnaire) } - - subject { delete :purge_champ_piece_justificative, params: { procedure_id: champ.dossier.procedure.id, dossier_id: champ.dossier.id, champ_id: champ.id }, format: :js } - - context 'when gestionnaire can process dossier' do - let(:champ) { create(:champ_piece_justificative, dossier_id: dossier.id, private: true) } - - it { is_expected.to have_http_status(200) } - - it do - subject - expect(champ.reload.piece_justificative_file.attached?).to be(false) - end - - context 'but champ is not linked to this dossier' do - let(:champ) { create(:champ_piece_justificative, dossier: create(:dossier), private: true) } - - it { is_expected.to redirect_to(root_path) } - - it do - subject - expect(champ.reload.piece_justificative_file.attached?).to be(true) - end - end - end - - context 'when gestionnaire cannot process dossier' do - let(:dossier) { create(:dossier, procedure: create(:procedure)) } - let(:champ) { create(:champ_piece_justificative, dossier_id: dossier.id, private: true) } - - it { is_expected.to redirect_to(root_path) } - - it do - subject - expect(champ.reload.piece_justificative_file.attached?).to be(true) - end - end - end end diff --git a/spec/controllers/users/dossiers_controller_spec.rb b/spec/controllers/users/dossiers_controller_spec.rb index 351e2fae7..9d68d2003 100644 --- a/spec/controllers/users/dossiers_controller_spec.rb +++ b/spec/controllers/users/dossiers_controller_spec.rb @@ -919,47 +919,6 @@ describe Users::DossiersController, type: :controller do end end - describe '#purge_champ_piece_justificative' do - before { sign_in(user) } - - subject { delete :purge_champ_piece_justificative, params: { id: champ.dossier.id, champ_id: champ.id }, format: :js } - - context 'when dossier is owned by user' do - let(:dossier) { create(:dossier, user: user) } - let(:champ) { create(:champ_piece_justificative, dossier_id: dossier.id) } - - it { is_expected.to have_http_status(200) } - - it do - subject - expect(champ.reload.piece_justificative_file.attached?).to be(false) - end - - context 'but champ is not linked to this dossier' do - let(:champ) { create(:champ_piece_justificative, dossier: create(:dossier)) } - - it { is_expected.to redirect_to(root_path) } - - it do - subject - expect(champ.reload.piece_justificative_file.attached?).to be(true) - end - end - end - - context 'when dossier is not owned by user' do - let(:dossier) { create(:dossier, user: create(:user)) } - let(:champ) { create(:champ_piece_justificative, dossier_id: dossier.id) } - - it { is_expected.to redirect_to(root_path) } - - it do - subject - expect(champ.reload.piece_justificative_file.attached?).to be(true) - end - end - end - describe "#dossier_for_help" do before do sign_in(user) diff --git a/spec/features/admin/add_type_de_piece_justificative_spec.rb b/spec/features/admin/add_type_de_piece_justificative_spec.rb deleted file mode 100644 index 5c0ddc154..000000000 --- a/spec/features/admin/add_type_de_piece_justificative_spec.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'spec_helper' - -feature 'add a new type de piece justificative', js: true do - let(:administrateur) { create(:administrateur) } - - before do - login_as administrateur, scope: :administrateur - end - context 'when there is an existing piece justificative' do - let(:procedure) { create(:procedure, administrateur: administrateur) } - before do - # Create a dummy PJ, because adding PJs is no longer allowed on procedures that - # do not already have one - procedure.types_de_piece_justificative.create(libelle: "dummy PJ") - visit admin_procedure_pieces_justificatives_path(procedure) - end - scenario 'displays a form to add new type de piece justificative' do - within '#new_type_de_piece_justificative' do - expect(page).to have_css('#procedure_types_de_piece_justificative_attributes_1_libelle') - end - end - context 'when user fills field and submit' do - let(:libelle) { 'ma piece' } - let(:description) { 'ma description' } - before do - page.find_by_id('procedure_types_de_piece_justificative_attributes_1_libelle').set(libelle) - page.find_by_id('procedure_types_de_piece_justificative_attributes_1_description').set(description) - page.click_on 'Ajouter la pièce' - wait_for_ajax - end - subject do - procedure.reload - procedure.types_de_piece_justificative.second - end - scenario 'creates new type de piece' do - expect(subject.libelle).to eq(libelle) - expect(subject.description).to eq(description) - end - scenario 'displays new created pj' do - within '#liste_piece_justificative' do - expect(page).to have_css('#procedure_types_de_piece_justificative_attributes_1_libelle') - expect(page.body).to match(libelle) - expect(page.body).to match(description) - end - within '#new_type_de_piece_justificative' do - expect(page).to have_css('#procedure_types_de_piece_justificative_attributes_2_libelle') - end - end - context 'when user delete pj' do - before do - pj = procedure.types_de_piece_justificative.second - page.find_by_id("delete_type_de_piece_justificative_#{pj.id}").click - wait_for_ajax - end - scenario 'removes pj from page' do - within '#liste_piece_justificative' do - expect(page).not_to have_css('#procedure_types_de_piece_justificative_attributes_1_libelle') - expect(page.body).not_to match(libelle) - expect(page.body).not_to match(description) - end - end - end - context 'when user change existing type de pj' do - let(:new_libelle) { 'mon nouveau libelle' } - before do - page.find_by_id('procedure_types_de_piece_justificative_attributes_1_libelle').set(new_libelle) - page.find_by_id('save').click - wait_for_ajax - end - scenario 'saves change in database' do - pj = procedure.types_de_piece_justificative.second - expect(pj.libelle).to eq(new_libelle) - end - end - end - end -end diff --git a/spec/features/new_administrateur/types_de_champ_spec.rb b/spec/features/new_administrateur/types_de_champ_spec.rb index dd68532bb..2046fd10e 100644 --- a/spec/features/new_administrateur/types_de_champ_spec.rb +++ b/spec/features/new_administrateur/types_de_champ_spec.rb @@ -121,14 +121,15 @@ feature 'As an administrateur I can edit types de champ', js: true do it "Add carte champ" do select('Carte', from: 'champ-0-type_champ') - fill_in 'champ-0-libelle', with: 'libellé de champ carte' - blur + fill_in 'champ-0-libelle', with: 'Libellé de champ carte', fill_options: { clear: :backspace } check 'Quartiers prioritaires' + + wait_until { procedure.types_de_champ.first.quartiers_prioritaires == true } expect(page).to have_content('Formulaire enregistré') preview_window = window_opened_by { click_on 'Prévisualiser le formulaire' } within_window(preview_window) do - expect(page).to have_content('libellé de champ carte') + expect(page).to have_content('Libellé de champ carte') expect(page).to have_content('Quartiers prioritaires') expect(page).not_to have_content('Cadastres') end @@ -136,9 +137,10 @@ feature 'As an administrateur I can edit types de champ', js: true do it "Add dropdown champ" do select('Menu déroulant', from: 'champ-0-type_champ') - fill_in 'champ-0-libelle', with: 'libellé de champ menu déroulant' - blur - fill_in 'champ-0-drop_down_list_value', with: 'Un menu' + fill_in 'champ-0-libelle', with: 'Libellé de champ menu déroulant', fill_options: { clear: :backspace } + fill_in 'champ-0-drop_down_list_value', with: 'Un menu', fill_options: { clear: :backspace } + + wait_until { procedure.types_de_champ.first.drop_down_list&.value == 'Un menu' } expect(page).to have_content('Formulaire enregistré') page.refresh diff --git a/spec/features/users/brouillon_spec.rb b/spec/features/users/brouillon_spec.rb index 4652c2366..a0eef004f 100644 --- a/spec/features/users/brouillon_spec.rb +++ b/spec/features/users/brouillon_spec.rb @@ -14,7 +14,7 @@ feature 'The user' do allow(Champs::RegionChamp).to receive(:regions).and_return(['region1', 'region2']).at_least(:once) allow(Champs::DepartementChamp).to receive(:departements).and_return(['dep1', 'dep2']).at_least(:once) - log_in(user.email, password, procedure) + log_in(user, procedure) fill_individual @@ -75,11 +75,11 @@ feature 'The user' do expect(page).to have_field('email', with: 'loulou@yopmail.com') expect(page).to have_field('phone', with: '1234567890') expect(page).to have_checked_field('Non') - expect(page).to have_select('simple_drop_down_list', selected: 'val2') - expect(page).to have_select('multiple_drop_down_list', selected: ['val1', 'val3']) - expect(page).to have_select('pays', selected: 'AUSTRALIE') - expect(page).to have_select('regions', selected: 'region2') - expect(page).to have_select('departement', selected: 'dep2') + expect(page).to have_selected_value('simple_drop_down_list', selected: 'val2') + expect(page).to have_selected_value('multiple_drop_down_list', selected: ['val1', 'val3']) + expect(page).to have_selected_value('pays', selected: 'AUSTRALIE') + expect(page).to have_selected_value('regions', selected: 'region2') + expect(page).to have_selected_value('departement', selected: 'dep2') expect(page).to have_checked_field('engagement') expect(page).to have_field('dossier_link', with: '123') expect(page).to have_text('file.pdf') @@ -93,7 +93,7 @@ feature 'The user' do end scenario 'fill a dossier with repetition', js: true do - log_in(user.email, password, procedure_with_repetition) + log_in(user, procedure_with_repetition) fill_individual @@ -127,7 +127,7 @@ feature 'The user' do end scenario 'save an incomplete dossier as draft but cannot not submit it', js: true do - log_in(user.email, password, simple_procedure) + log_in(user, simple_procedure) fill_individual # Check an incomplete dossier can be saved as a draft, even when mandatory fields are missing @@ -156,7 +156,7 @@ feature 'The user' do end scenario 'adding, replacing and removing attachments', js: true do - log_in(user.email, password, procedure_with_pj) + log_in(user, procedure_with_pj) fill_individual # Add an attachment @@ -194,14 +194,10 @@ feature 'The user' do private - def log_in(email, password, procedure) + def log_in(user, procedure) + login_as user, scope: :user + visit "/commencer/#{procedure.path}" - click_on 'J’ai déjà un compte' - - expect(page).to have_current_path(new_user_session_path) - sign_in_with(email, password) - - expect(page).to have_current_path("/commencer/#{procedure.path}") click_on 'Commencer la démarche' expect(page).to have_content("Données d'identité") @@ -233,10 +229,10 @@ feature 'The user' do end def check_date_and_time(date, field) - expect(page).to have_select("#{field}_1i", selected: date.strftime('%Y')) - expect(page).to have_select("#{field}_2i", selected: I18n.l(date, format: '%B')) - expect(page).to have_select("#{field}_3i", selected: date.strftime('%-d')) - expect(page).to have_select("#{field}_4i", selected: date.strftime('%H')) - expect(page).to have_select("#{field}_5i", selected: date.strftime('%M')) + expect(page).to have_selected_value("#{field}_1i", selected: date.strftime('%Y')) + expect(page).to have_selected_value("#{field}_2i", selected: I18n.l(date, format: '%B')) + expect(page).to have_selected_value("#{field}_3i", selected: date.strftime('%-d')) + expect(page).to have_selected_value("#{field}_4i", selected: date.strftime('%H')) + expect(page).to have_selected_value("#{field}_5i", selected: date.strftime('%M')) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6072acede..291611d37 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -40,7 +40,7 @@ end Capybara.register_driver :headless_chrome do |app| capabilities = Selenium::WebDriver::Remote::Capabilities.chrome( - chromeOptions: { args: ['headless', 'disable-gpu', 'disable-dev-shm-usage', 'disable-software-rasterizer', 'mute-audio', 'window-size=1440,900'] } + chromeOptions: { args: ['headless', 'disable-dev-shm-usage', 'disable-software-rasterizer', 'mute-audio', 'window-size=1440,900'] } ) Capybara::Selenium::Driver.new app, @@ -167,4 +167,22 @@ RSpec.configure do |config| actual.attributes.with_indifferent_access.except(*ignored) == expected.attributes.with_indifferent_access.except(*ignored) end end + + # Asserts that a given select element exists in the page, + # and that the option(s) with the given value(s) are selected. + # + # Usage: expect(page).to have_selected_value('Country', selected: 'Australia') + # + # For large lists, this is much faster than `have_select(location, selected: value)`, + # as it doesn’t check that every other options are not selected. + RSpec::Matchers.define(:have_selected_value) do |select_locator, options| + match do |page| + values = options[:selected].is_a?(String) ? [options[:selected]] : options[:selected] + + select_element = page.first(:select, select_locator) + select_element && values.all? do |value| + select_element.first(:option, value).selected? + end + end + end end diff --git a/spec/support/feature_helpers.rb b/spec/support/feature_helpers.rb index ee4dcd103..6c0eb299a 100644 --- a/spec/support/feature_helpers.rb +++ b/spec/support/feature_helpers.rb @@ -62,6 +62,18 @@ module FeatureHelpers def blur page.find('body').click end + + def pause + $stderr.write 'Spec paused. Press enter to continue:' + $stdin.gets + end + + def wait_until + Timeout.timeout(Capybara.default_max_wait_time) do + sleep(0.1) until (value = yield) + value + end + end end RSpec.configure do |config| diff --git a/spec/support/wait_for_ajax.rb b/spec/support/wait_for_ajax.rb index 3e84d42f5..e57061cdb 100644 --- a/spec/support/wait_for_ajax.rb +++ b/spec/support/wait_for_ajax.rb @@ -1,12 +1,6 @@ module WaitForAjax def wait_for_ajax - Timeout.timeout(Capybara.default_max_wait_time) do - loop until finished_all_ajax_requests? - end - end - - def finished_all_ajax_requests? - page.evaluate_script('jQuery.active').zero? + expect(page).to have_selector('body[data-active-requests-count="0"]') end end diff --git a/yarn.lock b/yarn.lock index 3f7c3b20f..cc2d47945 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3541,9 +3541,9 @@ fsevents@^1.2.7: node-pre-gyp "^0.12.0" fstream@^1.0.0, fstream@^1.0.2: - version "1.0.11" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" - integrity sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE= + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== dependencies: graceful-fs "^4.1.2" inherits "~2.0.0" @@ -3644,7 +3644,7 @@ glob-stream@^6.1.0: to-absolute-glob "^2.0.0" unique-stream "^2.0.2" -glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1: +glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== @@ -3656,6 +3656,18 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.3: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"