diff --git a/app/javascript/new_design/dossiers/auto-save-controller.js b/app/javascript/new_design/dossiers/auto-save-controller.js index 7bf61c461..ae44e97b6 100644 --- a/app/javascript/new_design/dossiers/auto-save-controller.js +++ b/app/javascript/new_design/dossiers/auto-save-controller.js @@ -10,7 +10,7 @@ export default class AutoSaveController { // Add a new autosave request to the queue. // It will be started after the previous one finishes (to prevent older form data - // to overwrite newer data if the server does not repond in order.) + // to overwrite newer data if the server does not respond in order.) enqueueAutosaveRequest(form) { this.latestPromise = this.latestPromise.finally(() => { return this._sendAutosaveRequest(form) diff --git a/app/javascript/new_design/dossiers/auto-save.js b/app/javascript/new_design/dossiers/auto-save.js index 1b4b9185c..d51bd1dda 100644 --- a/app/javascript/new_design/dossiers/auto-save.js +++ b/app/javascript/new_design/dossiers/auto-save.js @@ -62,6 +62,15 @@ addEventListener('autosave:end', () => { }); addEventListener('autosave:error', (event) => { + let error = event.detail; + + if (error.xhr.status == 401) { + // If we are unauthenticated, reload the page using a GET request. + // This will allow Devise to properly redirect us to sign-in, and then back to this page. + document.location.reload(); + return; + } + enable(document.querySelector('button.autosave-retry')); setState('failed'); logError(event.detail); diff --git a/app/javascript/shared/utils.js b/app/javascript/shared/utils.js index ea0fd01f1..3ac0b7839 100644 --- a/app/javascript/shared/utils.js +++ b/app/javascript/shared/utils.js @@ -50,6 +50,13 @@ export function delegate(eventNames, selector, callback) { ); } +// A promise-based wrapper for Rails.ajax(). +// +// Returns a Promise that is either: +// - resolved in case of a 20* HTTP response code, +// - rejected with an Error object otherwise. +// +// See Rails.ajax() code for more details. export function ajax(options) { return new Promise((resolve, reject) => { Object.assign(options, { @@ -57,7 +64,10 @@ export function ajax(options) { resolve({ response, statusText, xhr }); }, error: (response, statusText, xhr) => { - let error = new Error(`Erreur ${xhr.status} : ${statusText}`); + // NB: on HTTP/2 connections, statusText is always empty. + let error = new Error( + `Erreur ${xhr.status}` + (statusText ? ` : ${statusText}` : '') + ); Object.assign(error, { response, statusText, xhr }); reject(error); } diff --git a/spec/features/users/brouillon_spec.rb b/spec/features/users/brouillon_spec.rb index f25662b73..30c127051 100644 --- a/spec/features/users/brouillon_spec.rb +++ b/spec/features/users/brouillon_spec.rb @@ -255,24 +255,46 @@ feature 'The user' do expect(page).to have_field('texte obligatoire', with: 'a valid user input') end - scenario 'retry on autosave error', js: true do + scenario 'retry on autosave error', :capybara_ignore_server_errors, js: true do log_in(user, simple_procedure) fill_individual # Test autosave failure - logout(:user) # Make the subsequent autosave requests fail + allow_any_instance_of(Users::DossiersController).to receive(:update_brouillon).and_raise("Server is busy") fill_in('texte obligatoire', with: 'a valid user input') blur expect(page).to have_css('span', text: 'Impossible d’enregistrer le brouillon', visible: true) # Test that retrying after a failure works - login_as(user, scope: :user) # Make the autosave requests work again + allow_any_instance_of(Users::DossiersController).to receive(:update_brouillon).and_call_original click_on 'réessayer' expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true) visit current_path expect(page).to have_field('texte obligatoire', with: 'a valid user input') end + + scenario 'autosave redirects to sign-in after being disconnected', js: true do + log_in(user, simple_procedure) + fill_individual + + # When the user is disconnected + # (either because signing-out in another tab, or because the session cookie expired) + logout(:user) + fill_in('texte obligatoire', with: 'a valid user input') + blur + + # … they are redirected to the sign-in page. + expect(page).to have_current_path(new_user_session_path) + + # After sign-in, they are redirected back to their brouillon + sign_in_with(user.email, password) + expect(page).to have_current_path(brouillon_dossier_path(user_dossier)) + + fill_in('texte obligatoire', with: 'a valid user input') + blur + expect(page).to have_css('span', text: 'Brouillon enregistré', visible: true) + end end private diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index c8700a5e7..234aacbd9 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -47,3 +47,16 @@ Capybara::Screenshot.prune_strategy = :keep_last_run Capybara::Screenshot.register_driver :headless_chrome do |driver, path| driver.browser.save_screenshot(path) end + +RSpec.configure do |config| + # Examples tagged with :capybara_ignore_server_errors will allow Capybara + # to continue when an exception in raised by Rails. + # This allows to test for error cases. + config.around(:each, :capybara_ignore_server_errors) do |example| + Capybara.raise_server_errors = false + + example.run + ensure + Capybara.raise_server_errors = true + end +end