diff --git a/app/assets/images/icons/retry.svg b/app/assets/images/icons/retry.svg
new file mode 100644
index 000000000..84797f123
--- /dev/null
+++ b/app/assets/images/icons/retry.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/assets/stylesheets/new_design/_colors.scss b/app/assets/stylesheets/new_design/_colors.scss
index f1bd66076..052e2a51b 100644
--- a/app/assets/stylesheets/new_design/_colors.scss
+++ b/app/assets/stylesheets/new_design/_colors.scss
@@ -10,6 +10,7 @@ $dark-red: #A10005;
$medium-red: rgba(161, 0, 5, 0.9);
$light-red: #ED1C24;
$lighter-red: #F52A2A;
+$background-red: #FFDFDF;
$green: #15AD70;
$lighter-green: lighten($green, 30%);
$light-green: lighten($green, 25%);
diff --git a/app/assets/stylesheets/new_design/attachment.scss b/app/assets/stylesheets/new_design/attachment.scss
index 27c585d63..20d21a2fc 100644
--- a/app/assets/stylesheets/new_design/attachment.scss
+++ b/app/assets/stylesheets/new_design/attachment.scss
@@ -1,3 +1,4 @@
+@import "colors";
@import "constants";
.attachment-actions {
@@ -13,6 +14,38 @@
}
}
+.attachment-error {
+ display: flex;
+ width: max-content;
+ max-width: 100%;
+ align-items: center;
+ margin-bottom: $default-padding;
+ padding: $default-padding;
+ background: $background-red;
+
+ &.hidden {
+ display: none;
+ }
+}
+
+.attachment-error-message {
+ display: inline-block;
+ margin-right: $default-padding;
+ color: $medium-red;
+}
+
+.attachment-error-title {
+ font-weight: bold;
+}
+
+.attachment-error-retry {
+ white-space: nowrap;
+
+ &.hidden {
+ display: none;
+ }
+}
+
.attachment-input.hidden {
display: none;
}
diff --git a/app/assets/stylesheets/new_design/direct_uploads.scss b/app/assets/stylesheets/new_design/direct_uploads.scss
index febde1bd0..54fa203b1 100644
--- a/app/assets/stylesheets/new_design/direct_uploads.scss
+++ b/app/assets/stylesheets/new_design/direct_uploads.scss
@@ -33,6 +33,7 @@
border-color: red;
}
-input[type=file][data-direct-upload-url][disabled] {
+input[type=file][data-direct-upload-url][disabled],
+input[type=file][data-auto-attach-url][disabled] {
display: none;
}
diff --git a/app/assets/stylesheets/new_design/icons.scss b/app/assets/stylesheets/new_design/icons.scss
index c95fc87df..a05037802 100644
--- a/app/assets/stylesheets/new_design/icons.scss
+++ b/app/assets/stylesheets/new_design/icons.scss
@@ -67,6 +67,10 @@
background-image: image-url("icons/preview.svg");
}
+ &.retry {
+ background-image: image-url("icons/retry.svg");
+ }
+
&.download {
background-image: image-url("icons/download.svg");
}
diff --git a/app/controllers/champs/piece_justificative_controller.rb b/app/controllers/champs/piece_justificative_controller.rb
new file mode 100644
index 000000000..3aa6578b7
--- /dev/null
+++ b/app/controllers/champs/piece_justificative_controller.rb
@@ -0,0 +1,21 @@
+class Champs::PieceJustificativeController < ApplicationController
+ before_action :authenticate_logged_user!
+
+ def update
+ @champ = policy_scope(Champ).find(params[:champ_id])
+
+ @champ.piece_justificative_file.attach(params[:blob_signed_id])
+ if @champ.save
+ render :show
+ else
+ errors = @champ.errors.full_messages
+
+ # Before Rails 6, the attachment was persisted to database
+ # by 'attach', even before calling save.
+ # So until we're on Rails 6, we need to purge the file explicitely.
+ @champ.piece_justificative_file.purge_later
+
+ render :json => { errors: errors }, :status => 422
+ end
+ end
+end
diff --git a/app/helpers/champ_helper.rb b/app/helpers/champ_helper.rb
index a31ca1b58..ca6aa40b6 100644
--- a/app/helpers/champ_helper.rb
+++ b/app/helpers/champ_helper.rb
@@ -31,4 +31,10 @@ module ChampHelper
"desc-#{champ.type_de_champ.id}-#{champ.row}"
end
end
+
+ def auto_attach_url(form, object)
+ if feature_enabled?(:autoupload_dossier_attachments) && object.is_a?(Champ) && object.public?
+ champs_piece_justificative_url(form.index)
+ end
+ end
end
diff --git a/app/javascript/new_design/dossiers/auto-upload-controller.js b/app/javascript/new_design/dossiers/auto-upload-controller.js
new file mode 100644
index 000000000..1a5b91282
--- /dev/null
+++ b/app/javascript/new_design/dossiers/auto-upload-controller.js
@@ -0,0 +1,141 @@
+import Uploader from '../../shared/activestorage/uploader';
+import ProgressBar from '../../shared/activestorage/progress-bar';
+import { ajax, show, hide, toggle } from '@utils';
+
+// Given a file input in a champ with a selected file, upload a file,
+// then attach it to the dossier.
+//
+// On success, the champ is replaced by an HTML fragment describing the attachment.
+// On error, a error message is displayed above the input.
+export default class AutoUploadController {
+ constructor(input, file) {
+ this.input = input;
+ this.file = file;
+ }
+
+ async start() {
+ try {
+ this._begin();
+
+ // Sanity checks
+ const autoAttachUrl = this.input.dataset.autoAttachUrl;
+ if (!autoAttachUrl) {
+ throw new Error('L’attribut "data-auto-attach-url" est manquant');
+ }
+
+ const champ = this.input.closest('.editable-champ[data-champ-id]');
+ if (!champ) {
+ throw new Error('Impossible de trouver l’élément ".editable-champ"');
+ }
+ const champId = champ.dataset.champId;
+
+ // Upload the file (using Direct Upload)
+ let blobSignedId = await this._upload();
+
+ // Attach the blob to the champ
+ // (The request responds with Javascript, which displays the attachment HTML fragment).
+ await this._attach(champId, blobSignedId, autoAttachUrl);
+
+ // Everything good: clear the original file input value
+ this.input.value = null;
+ } catch (error) {
+ this._failed(error);
+ throw error;
+ } finally {
+ this._done();
+ }
+ }
+
+ _begin() {
+ this.input.disabled = true;
+ this._hideErrorMessage();
+ }
+
+ async _upload() {
+ const uploader = new Uploader(
+ this.input,
+ this.file,
+ this.input.dataset.directUploadUrl
+ );
+ return await uploader.start();
+ }
+
+ async _attach(champId, blobSignedId, autoAttachUrl) {
+ // Now that the upload is done, display a new progress bar
+ // to show that the attachment request is still pending.
+ const progressBar = new ProgressBar(this.input, champId, this.file);
+ progressBar.progress(100);
+ progressBar.end();
+
+ const attachmentRequest = {
+ url: autoAttachUrl,
+ type: 'PUT',
+ data: `champ_id=${champId}&blob_signed_id=${blobSignedId}`
+ };
+ await ajax(attachmentRequest);
+
+ // The progress bar has been destroyed by the attachment HTML fragment that replaced the input,
+ // so no further cleanup is needed.
+ }
+
+ _failed(error) {
+ if (!document.body.contains(this.input)) {
+ return;
+ }
+
+ let progressBar = this.input.parentElement.querySelector('.direct-upload');
+ if (progressBar) {
+ progressBar.remove();
+ }
+
+ this._displayErrorMessage(error);
+ }
+
+ _done() {
+ this.input.disabled = false;
+ }
+
+ _messageFromError(error) {
+ if (
+ error.xhr &&
+ error.xhr.status == 422 &&
+ error.response &&
+ error.response.errors &&
+ error.response.errors[0]
+ ) {
+ return {
+ title: error.response.errors[0],
+ description: '',
+ retry: false
+ };
+ } else {
+ return {
+ title: 'Une erreur s’est produite pendant l’envoi du fichier.',
+ description: error.message || error.toString(),
+ retry: true
+ };
+ }
+ }
+
+ _displayErrorMessage(error) {
+ let errorNode = this.input.parentElement.querySelector('.attachment-error');
+ if (errorNode) {
+ show(errorNode);
+ let message = this._messageFromError(error);
+ errorNode.querySelector('.attachment-error-title').textContent =
+ message.title || '';
+ errorNode.querySelector('.attachment-error-description').textContent =
+ message.description || '';
+ toggle(errorNode.querySelector('.attachment-error-retry'), message.retry);
+ }
+ }
+
+ _hideErrorMessage() {
+ let errorElement = this.input.parentElement.querySelector(
+ '.attachment-error'
+ );
+ if (errorElement) {
+ hide(errorElement);
+ }
+ }
+}
diff --git a/app/javascript/new_design/dossiers/auto-upload.js b/app/javascript/new_design/dossiers/auto-upload.js
new file mode 100644
index 000000000..627a61000
--- /dev/null
+++ b/app/javascript/new_design/dossiers/auto-upload.js
@@ -0,0 +1,23 @@
+import AutoUploadsControllers from './auto-uploads-controllers.js';
+import { delegate } from '@utils';
+
+// Create a controller responsible for managing several concurrent uploads.
+const autoUploadsControllers = new AutoUploadsControllers();
+
+function startUpload(input) {
+ Array.from(input.files).forEach(file => {
+ autoUploadsControllers.upload(input, file);
+ });
+}
+
+const fileInputSelector = `input[type=file][data-direct-upload-url][data-auto-attach-url]:not([disabled])`;
+delegate('change', fileInputSelector, event => {
+ startUpload(event.target);
+});
+
+const retryButtonSelector = `button.attachment-error-retry`;
+delegate('click', retryButtonSelector, event => {
+ const inputSelector = event.target.dataset.inputTarget;
+ const input = document.querySelector(inputSelector);
+ startUpload(input);
+});
diff --git a/app/javascript/new_design/dossiers/auto-uploads-controllers.js b/app/javascript/new_design/dossiers/auto-uploads-controllers.js
new file mode 100644
index 000000000..a50b083ac
--- /dev/null
+++ b/app/javascript/new_design/dossiers/auto-uploads-controllers.js
@@ -0,0 +1,46 @@
+import Rails from '@rails/ujs';
+import AutoUploadController from './auto-upload-controller.js';
+
+// Manage multiple concurrent uploads.
+//
+// When the first upload starts, all the form "Submit" buttons are disabled.
+// They are enabled again when the last upload ends.
+export default class AutoUploadsControllers {
+ constructor() {
+ this.inFlightUploadsCount = 0;
+ }
+
+ async upload(input, file) {
+ let form = input.form;
+ this._incrementInFlightUploads(form);
+
+ try {
+ let controller = new AutoUploadController(input, file);
+ await controller.start();
+ } finally {
+ this._decrementInFlightUploads(form);
+ }
+ }
+
+ _incrementInFlightUploads(form) {
+ this.inFlightUploadsCount += 1;
+
+ if (form) {
+ form
+ .querySelectorAll('button[type=submit]')
+ .forEach(submitButton => Rails.disableElement(submitButton));
+ }
+ }
+
+ _decrementInFlightUploads(form) {
+ if (this.inFlightUploadsCount > 0) {
+ this.inFlightUploadsCount -= 1;
+ }
+
+ if (this.inFlightUploadsCount == 0 && form) {
+ form
+ .querySelectorAll('button[type=submit]')
+ .forEach(submitButton => Rails.enableElement(submitButton));
+ }
+ }
+}
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index 5d33fb7ae..b436909dd 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -23,6 +23,7 @@ import '../new_design/select2';
import '../new_design/spinner';
import '../new_design/support';
import '../new_design/dossiers/auto-save';
+import '../new_design/dossiers/auto-upload';
import '../new_design/champs/carte';
import '../new_design/champs/linked-drop-down-list';
diff --git a/app/javascript/shared/activestorage/progress-bar.js b/app/javascript/shared/activestorage/progress-bar.js
index e08df104d..14efed061 100644
--- a/app/javascript/shared/activestorage/progress-bar.js
+++ b/app/javascript/shared/activestorage/progress-bar.js
@@ -41,7 +41,7 @@ export default class ProgressBar {
}
static render(id, filename) {
- return `
+ return `
`;
diff --git a/app/javascript/shared/utils.js b/app/javascript/shared/utils.js
index 26603d954..d360af45f 100644
--- a/app/javascript/shared/utils.js
+++ b/app/javascript/shared/utils.js
@@ -13,8 +13,14 @@ export function hide(el) {
el && el.classList.add('hidden');
}
-export function toggle(el) {
- el && el.classList.toggle('hidden');
+export function toggle(el, force) {
+ if (force == undefined) {
+ el & el.classList.toggle('hidden');
+ } else if (force) {
+ el && el.classList.remove('hidden');
+ } else {
+ el && el.classList.add('hidden');
+ }
}
export function enable(el) {
diff --git a/app/views/champs/piece_justificative/show.js.erb b/app/views/champs/piece_justificative/show.js.erb
new file mode 100644
index 000000000..e921f9556
--- /dev/null
+++ b/app/views/champs/piece_justificative/show.js.erb
@@ -0,0 +1,17 @@
+<% dossier = @champ.dossier %>
+
+<%= fields_for dossier do |form| %>
+ <%= form.fields_for :champs, dossier.champs.where(id: @champ.id), include_id: false do |champ_form| %>
+ <% render_to_element(".editable-champ[data-champ-id=\"#{@champ.id}\"]",
+ partial: 'shared/dossiers/editable_champs/editable_champ',
+ locals: {
+ champ: @champ,
+ form: champ_form
+ }) %>
+ <% end %>
+<% end %>
+
+<% attachment = @champ.piece_justificative_file.attachment %>
+<% if attachment.virus_scanner.pending? %>
+ <%= fire_event('attachment:update', { url: attachment_url(attachment.id, { signed_id: attachment.blob.signed_id, user_can_upload: true }) }.to_json ) %>
+<% end %>
diff --git a/app/views/root/patron.html.haml b/app/views/root/patron.html.haml
index 507cb1ee1..0a66b983d 100644
--- a/app/views/root/patron.html.haml
+++ b/app/views/root/patron.html.haml
@@ -32,6 +32,7 @@
%span.icon.phone
%span.icon.clock
%span.icon.preview
+ %span.icon.retry
%span.icon.download
%span.icon.download-white
%span.icon.move-handle
diff --git a/app/views/shared/attachment/_edit.html.haml b/app/views/shared/attachment/_edit.html.haml
index e250fd6e8..9c4cfbd35 100644
--- a/app/views/shared/attachment/_edit.html.haml
+++ b/app/views/shared/attachment/_edit.html.haml
@@ -22,7 +22,17 @@
.attachment-action
= button_tag 'Remplacer', type: 'button', class: 'button small', data: { 'toggle-target': ".attachment-input-#{attachment_id}" }
+ .attachment-error.hidden
+ .attachment-error-message
+ %p.attachment-error-title
+ Une erreur s’est produite pendant l’envoi du fichier.
+ %p.attachment-error-description
+ = button_tag type: 'button', class: 'button attachment-error-retry', data: { 'input-target': ".attachment-input-#{attachment_id}" } do
+ %span.icon.retry
+ Ré-essayer
+
= form.file_field attached_file.name,
class: "attachment-input attachment-input-#{attachment_id} #{'hidden' if persisted}",
accept: accept,
- direct_upload: true
+ direct_upload: true,
+ data: { 'auto-attach-url': auto_attach_url(form, form.object) }
diff --git a/app/views/shared/dossiers/editable_champs/_editable_champ.html.haml b/app/views/shared/dossiers/editable_champs/_editable_champ.html.haml
index 1d7b79355..a446bf5f7 100644
--- a/app/views/shared/dossiers/editable_champs/_editable_champ.html.haml
+++ b/app/views/shared/dossiers/editable_champs/_editable_champ.html.haml
@@ -1,4 +1,4 @@
-.editable-champ{ class: "editable-champ-#{champ.type_champ}" }
+.editable-champ{ class: "editable-champ-#{champ.type_champ}", data: { 'champ-id': champ.id } }
- if champ.repetition?
%h3.header-subsection= champ.libelle
- if champ.description.present?
diff --git a/config/initializers/flipper.rb b/config/initializers/flipper.rb
index d434f6a72..e19f6e978 100644
--- a/config/initializers/flipper.rb
+++ b/config/initializers/flipper.rb
@@ -31,6 +31,7 @@ features = [
:insee_api_v3,
:instructeur_bypass_email_login_token,
:autosave_dossier_draft,
+ :autoupload_dossier_attachments,
:maintenance_mode,
:mini_profiler,
:operation_log_serialize_subject,
diff --git a/config/routes.rb b/config/routes.rb
index c3cb95419..112a71b5b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -119,6 +119,7 @@ Rails.application.routes.draw do
get ':position/dossier_link', to: 'dossier_link#show', as: :dossier_link
post ':position/carte', to: 'carte#show', as: :carte
post ':position/repetition', to: 'repetition#show', as: :repetition
+ put ':position/piece_justificative', to: 'piece_justificative#update', as: :piece_justificative
end
get 'attachments/:id', to: 'attachments#show', as: :attachment
diff --git a/spec/controllers/champs/piece_justificative_controller_spec.rb b/spec/controllers/champs/piece_justificative_controller_spec.rb
new file mode 100644
index 000000000..493d4d46f
--- /dev/null
+++ b/spec/controllers/champs/piece_justificative_controller_spec.rb
@@ -0,0 +1,65 @@
+describe Champs::PieceJustificativeController, type: :controller do
+ let(:user) { create(:user) }
+ let(:procedure) { create(:procedure, :published, :with_piece_justificative) }
+ let(:dossier) { create(:dossier, user: user, procedure: procedure) }
+ let(:champ) { dossier.champs.first }
+
+ describe '#update' do
+ render_views
+ before { sign_in user }
+
+ subject do
+ put :update, params: {
+ position: '1',
+ champ_id: champ.id,
+ blob_signed_id: file
+ }, format: 'js'
+ end
+
+ context 'when the file is valid' do
+ let(:file) { Rack::Test::UploadedFile.new('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') }
+
+ it 'attach the file' do
+ subject
+ champ.reload
+ expect(champ.piece_justificative_file.attached?).to be true
+ expect(champ.piece_justificative_file.filename).to eq('piece_justificative_0.pdf')
+ end
+
+ it 'renders the attachment template as Javascript' do
+ subject
+ expect(response.status).to eq(200)
+ expect(response.body).to include("editable-champ[data-champ-id=\"#{champ.id}\"]")
+ end
+ end
+
+ context 'when the file is invalid' do
+ let(:file) { Rack::Test::UploadedFile.new('spec/fixtures/files/invalid_file_format.json', 'application/json') }
+
+ # TODO: for now there are no validators on the champ piece_justificative_file,
+ # so we have to mock a failing validation.
+ # Once the validators will be enabled, remove those mocks, and let the usual
+ # validation fail naturally.
+ #
+ # See https://github.com/betagouv/demarches-simplifiees.fr/issues/4926
+ before do
+ champ
+ expect_any_instance_of(Champs::PieceJustificativeChamp).to receive(:save).and_return(false)
+ expect_any_instance_of(Champs::PieceJustificativeChamp).to receive(:errors)
+ .and_return(double(full_messages: ['La pièce justificative n’est pas d’un type accepté']))
+ end
+
+ it 'doesn’t attach the file' do
+ subject
+ expect(champ.reload.piece_justificative_file.attached?).to be false
+ end
+
+ it 'renders an error' do
+ subject
+ expect(response.status).to eq(422)
+ expect(response.header['Content-Type']).to include('application/json')
+ expect(JSON.parse(response.body)).to eq({ 'errors' => ['La pièce justificative n’est pas d’un type accepté'] })
+ end
+ end
+ end
+end
diff --git a/spec/features/users/brouillon_spec.rb b/spec/features/users/brouillon_spec.rb
index 2b45260b4..cd992974f 100644
--- a/spec/features/users/brouillon_spec.rb
+++ b/spec/features/users/brouillon_spec.rb
@@ -5,9 +5,6 @@ feature 'The user' do
let!(:procedure) { create(:procedure, :published, :for_individual, :with_all_champs_mandatory) }
let(:user_dossier) { user.dossiers.first }
- # TODO: check
- # the order
- # there are no extraneous input
scenario 'fill a dossier', js: true, vcr: { cassette_name: 'api_geo_departements_regions_et_communes' } do
log_in(user, procedure)
@@ -160,6 +157,14 @@ feature 'The user' do
create(:procedure, :published, :for_individual, types_de_champ: tdcs)
end
+ let(:procedure_with_pjs) do
+ tdcs = [
+ create(:type_de_champ_piece_justificative, mandatory: true, libelle: 'Pièce justificative 1', order_place: 1),
+ create(:type_de_champ_piece_justificative, mandatory: true, libelle: 'Pièce justificative 2', order_place: 2)
+ ]
+ create(:procedure, :published, :for_individual, types_de_champ: tdcs)
+ end
+
scenario 'adding, replacing and removing attachments', js: true do
log_in(user, procedure_with_pj)
fill_individual
@@ -191,6 +196,85 @@ feature 'The user' do
expect(page).to have_no_text('RIB.pdf')
end
+ context 'when the auto-uploads of attachments is enabled' do
+ before do
+ Flipper.enable_actor(:autoupload_dossier_attachments, user)
+ end
+
+ scenario 'add an attachment', js: true do
+ log_in(user, procedure_with_pjs)
+ fill_individual
+
+ # Add attachments
+ find_field('Pièce justificative 1').attach_file(Rails.root + 'spec/fixtures/files/file.pdf')
+ find_field('Pièce justificative 2').attach_file(Rails.root + 'spec/fixtures/files/RIB.pdf')
+
+ # Expect the files to be uploaded immediately
+ expect(page).to have_text('analyse antivirus en cours', count: 2)
+ expect(page).to have_text('file.pdf')
+ expect(page).to have_text('RIB.pdf')
+
+ # Expect the submit buttons to be enabled
+ expect(page).to have_button('Enregistrer le brouillon', disabled: false)
+ expect(page).to have_button('Déposer le dossier', disabled: false)
+
+ # Reload the current page
+ visit current_path
+
+ # Expect the files to have been saved on the dossier
+ expect(page).to have_text('file.pdf')
+ expect(page).to have_text('RIB.pdf')
+ end
+
+ # TODO: once we're running on Rails 6, re-enable the validator on PieceJustificativeChamp,
+ # and unmark this spec as pending.
+ #
+ # See piece_justificative_champ.rb
+ # See https://github.com/betagouv/demarches-simplifiees.fr/issues/4926
+ scenario 'add an invalid attachment', js: true, pending: true do
+ log_in(user, procedure_with_pjs)
+ fill_individual
+
+ # Test invalid file type
+ attach_file('Pièce justificative 1', Rails.root + 'spec/fixtures/files/invalid_file_format.json')
+ expect(page).to have_text('La pièce justificative n’est pas d’un type accepté')
+ expect(page).to have_no_button('Ré-essayer', visible: true)
+
+ # Replace the file by another with a valid type
+ attach_file('Pièce justificative 1', Rails.root + 'spec/fixtures/files/piece_justificative_0.pdf')
+ expect(page).to have_no_text('La pièce justificative n’est pas d’un type accepté')
+ expect(page).to have_text('analyse antivirus en cours')
+ expect(page).to have_text('piece_justificative_0.pdf')
+ end
+
+ scenario 'retry on transcient upload error', js: true do
+ log_in(user, procedure_with_pjs)
+ fill_individual
+
+ # Test auto-upload failure
+ logout(:user) # Make the subsequent auto-upload request fail
+ attach_file('Pièce justificative 1', Rails.root + 'spec/fixtures/files/file.pdf')
+ expect(page).to have_text('Une erreur s’est produite pendant l’envoi du fichier')
+ expect(page).to have_button('Ré-essayer', visible: true)
+ expect(page).to have_button('Enregistrer le brouillon', disabled: false)
+ expect(page).to have_button('Déposer le dossier', disabled: false)
+
+ # Test that retrying after a failure works
+ login_as(user, scope: :user) # Make the auto-upload request work again
+ click_on('Ré-essayer', visible: true)
+ expect(page).to have_text('analyse antivirus en cours')
+ expect(page).to have_text('file.pdf')
+ expect(page).to have_button('Enregistrer le brouillon', disabled: false)
+ expect(page).to have_button('Déposer le dossier', disabled: false)
+
+ # Reload the current page
+ visit current_path
+
+ # Expect the file to have been saved on the dossier
+ expect(page).to have_text('file.pdf')
+ end
+ end
+
context 'when the draft autosave is enabled' do
before do
Flipper.enable_actor(:autosave_dossier_draft, user)
diff --git a/spec/fixtures/files/invalid_file_format.json b/spec/fixtures/files/invalid_file_format.json
new file mode 100644
index 000000000..d3efb6fc9
--- /dev/null
+++ b/spec/fixtures/files/invalid_file_format.json
@@ -0,0 +1,3 @@
+{
+ "text": "The format of this attachment is rejected by most uploaders."
+}