Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Léo-Paul Géneau
gitlab-ce
Commits
5b0e0869
Commit
5b0e0869
authored
May 16, 2017
by
blackst0ne
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add an ability to cancel attaching file and redesign attaching files UI
parent
20987f4f
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
330 additions
and
117 deletions
+330
-117
app/assets/javascripts/dropzone_input.js
app/assets/javascripts/dropzone_input.js
+158
-94
app/assets/stylesheets/pages/note_form.scss
app/assets/stylesheets/pages/note_form.scss
+43
-0
app/helpers/icons_helper.rb
app/helpers/icons_helper.rb
+3
-2
app/views/shared/notes/_hints.html.haml
app/views/shared/notes/_hints.html.haml
+24
-3
changelogs/unreleased/add_ability_to_cancel_attaching_file_and_redesign_attaching_files_ui.yml
...cancel_attaching_file_and_redesign_attaching_files_ui.yml
+4
-0
spec/features/uploads/user_uploads_file_to_note_spec.rb
spec/features/uploads/user_uploads_file_to_note_spec.rb
+68
-8
spec/support/dropzone_helper.rb
spec/support/dropzone_helper.rb
+30
-10
No files found.
app/assets/javascripts/dropzone_input.js
View file @
5b0e0869
...
...
@@ -5,104 +5,154 @@ require('./preview_markdown');
window
.
DropzoneInput
=
(
function
()
{
function
DropzoneInput
(
form
)
{
var
$mdArea
,
alertAttr
,
alertClass
,
appendToTextArea
,
btnAlert
,
child
,
closeAlertMessage
,
closeSpinner
,
divAlert
,
divHover
,
divSpinner
,
dropzone
,
form_dropzone
,
form_textarea
,
getFilename
,
handlePaste
,
iconPaperclip
,
iconSpinner
,
insertToTextArea
,
isImage
,
max_file_size
,
pasteText
,
uploads_path
,
showError
,
showSpinner
,
uploadFile
,
uploadProgress
;
var
updateAttachingMessage
,
$attachingFileMessage
,
$mdArea
,
$attachButton
,
$cancelButton
,
$retryLink
,
$uploadingErrorContainer
,
$uploadingErrorMessage
,
$uploadProgress
,
$uploadingProgressContainer
,
appendToTextArea
,
btnAlert
,
child
,
closeAlertMessage
,
closeSpinner
,
divHover
,
divSpinner
,
dropzone
,
$formDropzone
,
formTextarea
,
getFilename
,
handlePaste
,
iconPaperclip
,
iconSpinner
,
insertToTextArea
,
isImage
,
maxFileSize
,
pasteText
,
uploadsPath
,
showError
,
showSpinner
,
uploadFile
;
Dropzone
.
autoDiscover
=
false
;
alertClass
=
"
alert alert-danger alert-dismissable div-dropzone-alert
"
;
alertAttr
=
"
class=
\"
close
\"
data-dismiss=
\"
alert
\"
"
+
"
aria-hidden=
\"
true
\"
"
;
divHover
=
"
<div class=
\"
div-dropzone-hover
\"
></div>
"
;
divSpinner
=
"
<div class=
\"
div-dropzone-spinner
\"
></div>
"
;
divAlert
=
"
<div class=
\"
"
+
alertClass
+
"
\"
></div>
"
;
iconPaperclip
=
"
<i class=
\"
fa fa-paperclip div-dropzone-icon
\"
></i>
"
;
iconSpinner
=
"
<i class=
\"
fa fa-spinner fa-spin div-dropzone-icon
\"
></i>
"
;
uploadProgress
=
$
(
"
<div class=
\"
div-dropzone-progress
\"
></div>
"
);
btnAlert
=
"
<button type=
\"
button
\"
"
+
alertAttr
+
"
>×</button>
"
;
uploads_path
=
window
.
uploads_path
||
null
;
max_file_size
=
gon
.
max_file_size
||
10
;
form_textarea
=
$
(
form
).
find
(
"
.js-gfm-input
"
);
form_textarea
.
wrap
(
"
<div class=
\"
div-dropzone
\"
></div>
"
);
form_textarea
.
on
(
'
paste
'
,
(
function
(
_this
)
{
divHover
=
'
<div class="div-dropzone-hover"></div>
'
;
iconPaperclip
=
'
<i class="fa fa-paperclip div-dropzone-icon"></i>
'
;
$attachButton
=
form
.
find
(
'
.button-attach-file
'
);
$attachingFileMessage
=
form
.
find
(
'
.attaching-file-message
'
);
$cancelButton
=
form
.
find
(
'
.button-cancel-uploading-files
'
);
$retryLink
=
form
.
find
(
'
.retry-uploading-link
'
);
$uploadProgress
=
form
.
find
(
'
.uploading-progress
'
);
$uploadingErrorContainer
=
form
.
find
(
'
.uploading-error-container
'
);
$uploadingErrorMessage
=
form
.
find
(
'
.uploading-error-message
'
);
$uploadingProgressContainer
=
form
.
find
(
'
.uploading-progress-container
'
);
uploadsPath
=
window
.
uploads_path
||
null
;
maxFileSize
=
gon
.
max_file_size
||
10
;
formTextarea
=
form
.
find
(
'
.js-gfm-input
'
);
formTextarea
.
wrap
(
'
<div class="div-dropzone"></div>
'
);
formTextarea
.
on
(
'
paste
'
,
(
function
(
_this
)
{
return
function
(
event
)
{
return
handlePaste
(
event
);
};
})(
this
));
$mdArea
=
$
(
form_textarea
).
closest
(
'
.md-area
'
);
$
(
form
).
setupMarkdownPreview
();
form_dropzone
=
$
(
form
).
find
(
'
.div-dropzone
'
);
form_dropzone
.
parent
().
addClass
(
"
div-dropzone-wrapper
"
);
form_dropzone
.
append
(
divHover
);
form_dropzone
.
find
(
"
.div-dropzone-hover
"
).
append
(
iconPaperclip
);
form_dropzone
.
append
(
divSpinner
);
form_dropzone
.
find
(
"
.div-dropzone-spinner
"
).
append
(
iconSpinner
);
form_dropzone
.
find
(
"
.div-dropzone-spinner
"
).
append
(
uploadProgress
);
form_dropzone
.
find
(
"
.div-dropzone-spinner
"
).
css
({
"
opacity
"
:
0
,
"
display
"
:
"
none
"
});
if
(
!
uploads_path
)
return
;
// Add dropzone area to the form.
$mdArea
=
formTextarea
.
closest
(
'
.md-area
'
);
form
.
setupMarkdownPreview
();
$formDropzone
=
form
.
find
(
'
.div-dropzone
'
);
$formDropzone
.
parent
().
addClass
(
'
div-dropzone-wrapper
'
);
$formDropzone
.
append
(
divHover
);
$formDropzone
.
find
(
'
.div-dropzone-hover
'
).
append
(
iconPaperclip
);
if
(
!
uploadsPath
)
return
;
dropzone
=
form_d
ropzone
.
dropzone
({
url
:
uploads
_p
ath
,
dictDefaultMessage
:
""
,
dropzone
=
$formD
ropzone
.
dropzone
({
url
:
uploads
P
ath
,
dictDefaultMessage
:
''
,
clickable
:
true
,
paramName
:
"
file
"
,
maxFilesize
:
max
_file_s
ize
,
paramName
:
'
file
'
,
maxFilesize
:
max
FileS
ize
,
uploadMultiple
:
false
,
headers
:
{
"
X-CSRF-Token
"
:
$
(
"
meta[name=
\"
csrf-token
\"
]
"
).
attr
(
"
content
"
)
'
X-CSRF-Token
'
:
$
(
'
meta[name="csrf-token"]
'
).
attr
(
'
content
'
)
},
previewContainer
:
false
,
processing
:
function
()
{
return
$
(
"
.div-dropzone-alert
"
).
alert
(
"
close
"
);
return
$
(
'
.div-dropzone-alert
'
).
alert
(
'
close
'
);
},
dragover
:
function
()
{
$mdArea
.
addClass
(
'
is-dropzone-hover
'
);
form
.
find
(
"
.div-dropzone-hover
"
).
css
(
"
opacity
"
,
0.7
);
form
.
find
(
'
.div-dropzone-hover
'
).
css
(
'
opacity
'
,
0.7
);
},
dragleave
:
function
()
{
$mdArea
.
removeClass
(
'
is-dropzone-hover
'
);
form
.
find
(
"
.div-dropzone-hover
"
).
css
(
"
opacity
"
,
0
);
form
.
find
(
'
.div-dropzone-hover
'
).
css
(
'
opacity
'
,
0
);
},
drop
:
function
()
{
$mdArea
.
removeClass
(
'
is-dropzone-hover
'
);
form
.
find
(
"
.div-dropzone-hover
"
).
css
(
"
opacity
"
,
0
);
form
_t
extarea
.
focus
();
form
.
find
(
'
.div-dropzone-hover
'
).
css
(
'
opacity
'
,
0
);
form
T
extarea
.
focus
();
},
success
:
function
(
header
,
response
)
{
const
processingFileCount
=
this
.
getQueuedFiles
().
length
+
this
.
getUploadingFiles
().
length
;
const
shouldPad
=
processingFileCount
>=
1
;
pasteText
(
response
.
link
.
markdown
,
shouldPad
);
// Show 'Attach a file' link only when all files have been uploaded.
if
(
!
processingFileCount
)
$attachButton
.
removeClass
(
'
hide
'
);
},
error
:
function
(
temp
)
{
var
checkIfMsgExists
,
errorAlert
;
errorAlert
=
$
(
form
).
find
(
'
.error-alert
'
);
checkIfMsgExists
=
errorAlert
.
children
().
length
;
if
(
checkIfMsgExists
===
0
)
{
errorAlert
.
append
(
divAlert
);
$
(
"
.div-dropzone-alert
"
).
append
(
btnAlert
+
"
Attaching the file failed.
"
);
}
error
:
function
(
file
,
errorMessage
=
'
Attaching the file failed.
'
,
xhr
)
{
// If 'error' event is fired by dropzone, the second parameter is error message.
// If the 'errorMessage' parameter is empty, the default error message is set.
// If the 'error' event is fired by backend (xhr) error response, the third parameter is
// xhr object (xhr.responseText is error message).
// On error we hide the 'Attach' and 'Cancel' buttons
// and show an error.
// If there's xhr error message, let's show it instead of dropzone's one.
const
message
=
xhr
?
xhr
.
responseText
:
errorMessage
;
$uploadingErrorContainer
.
removeClass
(
'
hide
'
);
$uploadingErrorMessage
.
html
(
message
);
$attachButton
.
addClass
(
'
hide
'
);
$cancelButton
.
addClass
(
'
hide
'
);
},
totaluploadprogress
:
function
(
totalUploadProgress
)
{
uploadProgress
.
text
(
Math
.
round
(
totalUploadProgress
)
+
"
%
"
);
updateAttachingMessage
(
this
.
files
,
$attachingFileMessage
);
$uploadProgress
.
text
(
Math
.
round
(
totalUploadProgress
)
+
'
%
'
);
},
sending
:
function
()
{
form_dropzone
.
find
(
"
.div-dropzone-spinner
"
).
css
({
"
opacity
"
:
0.7
,
"
display
"
:
"
inherit
"
});
sending
:
function
(
file
)
{
// DOM elements already exist.
// Instead of dynamically generating them,
// we just either hide or show them.
$attachButton
.
addClass
(
'
hide
'
);
$uploadingErrorContainer
.
addClass
(
'
hide
'
);
$uploadingProgressContainer
.
removeClass
(
'
hide
'
);
$cancelButton
.
removeClass
(
'
hide
'
);
},
removedfile
:
function
()
{
$attachButton
.
removeClass
(
'
hide
'
);
$cancelButton
.
addClass
(
'
hide
'
);
$uploadingProgressContainer
.
addClass
(
'
hide
'
);
$uploadingErrorContainer
.
addClass
(
'
hide
'
);
},
queuecomplete
:
function
()
{
uploadProgress
.
text
(
""
);
$
(
"
.dz-preview
"
).
remove
(
);
$
(
"
.markdown-area
"
).
trigger
(
"
input
"
);
$
(
"
.div-dropzone-spinner
"
).
css
({
"
opacity
"
:
0
,
"
display
"
:
"
none
"
$
(
'
.dz-preview
'
).
remove
(
);
$
(
'
.markdown-area
'
).
trigger
(
'
input
'
);
$
uploadingProgressContainer
.
addClass
(
'
hide
'
);
$cancelButton
.
addClass
(
'
hide
'
);
}
});
child
=
$
(
dropzone
[
0
]).
children
(
'
textarea
'
);
// removeAllFiles(true) stops uploading files (if any)
// and remove them from dropzone files queue.
$cancelButton
.
on
(
'
click
'
,
(
e
)
=>
{
const
target
=
e
.
target
.
closest
(
'
form
'
).
querySelector
(
'
.div-dropzone
'
);
e
.
preventDefault
();
e
.
stopPropagation
();
Dropzone
.
forElement
(
target
).
removeAllFiles
(
true
);
});
// If 'error' event is fired, we store a failed files,
// clear dropzone files queue, change status of failed files to undefined,
// and add that files to the dropzone files queue again.
// addFile() adds file to dropzone files queue and upload it.
$retryLink
.
on
(
'
click
'
,
(
e
)
=>
{
const
dropzoneInstance
=
Dropzone
.
forElement
(
e
.
target
.
closest
(
'
form
'
).
querySelector
(
'
.div-dropzone
'
));
const
failedFiles
=
dropzoneInstance
.
files
;
e
.
preventDefault
();
// 'true' parameter of removeAllFiles() cancels uploading of files that are being uploaded at the moment.
dropzoneInstance
.
removeAllFiles
(
true
);
failedFiles
.
map
((
failedFile
,
i
)
=>
{
const
file
=
failedFile
;
if
(
file
.
status
===
Dropzone
.
ERROR
)
{
file
.
status
=
undefined
;
file
.
accepted
=
undefined
;
}
return
dropzoneInstance
.
addFile
(
file
);
});
child
=
$
(
dropzone
[
0
]).
children
(
"
textarea
"
);
});
handlePaste
=
function
(
event
)
{
var
filename
,
image
,
pasteEvent
,
text
;
pasteEvent
=
event
.
originalEvent
;
...
...
@@ -110,25 +160,27 @@ window.DropzoneInput = (function() {
image
=
isImage
(
pasteEvent
);
if
(
image
)
{
event
.
preventDefault
();
filename
=
getFilename
(
pasteEvent
)
||
"
image.png
"
;
text
=
"
{{
"
+
filename
+
"
}}
"
;
filename
=
getFilename
(
pasteEvent
)
||
'
image.png
'
;
text
=
`{{
${
filename
}
}}`
;
pasteText
(
text
);
return
uploadFile
(
image
.
getAsFile
(),
filename
);
}
}
};
isImage
=
function
(
data
)
{
var
i
,
item
;
i
=
0
;
while
(
i
<
data
.
clipboardData
.
items
.
length
)
{
item
=
data
.
clipboardData
.
items
[
i
];
if
(
item
.
type
.
indexOf
(
"
image
"
)
!==
-
1
)
{
if
(
item
.
type
.
indexOf
(
'
image
'
)
!==
-
1
)
{
return
item
;
}
i
+=
1
;
}
return
false
;
};
pasteText
=
function
(
text
,
shouldPad
)
{
var
afterSelection
,
beforeSelection
,
caretEnd
,
caretStart
,
textEnd
;
var
formattedText
=
text
;
...
...
@@ -142,31 +194,33 @@ window.DropzoneInput = (function() {
$
(
child
).
val
(
beforeSelection
+
formattedText
+
afterSelection
);
textarea
.
setSelectionRange
(
caretStart
+
formattedText
.
length
,
caretEnd
+
formattedText
.
length
);
textarea
.
style
.
height
=
`
${
textarea
.
scrollHeight
}
px`
;
return
form
_textarea
.
trigger
(
"
input
"
);
return
form
Textarea
.
trigger
(
'
input
'
);
};
getFilename
=
function
(
e
)
{
var
value
;
if
(
window
.
clipboardData
&&
window
.
clipboardData
.
getData
)
{
value
=
window
.
clipboardData
.
getData
(
"
Text
"
);
value
=
window
.
clipboardData
.
getData
(
'
Text
'
);
}
else
if
(
e
.
clipboardData
&&
e
.
clipboardData
.
getData
)
{
value
=
e
.
clipboardData
.
getData
(
"
text/plain
"
);
value
=
e
.
clipboardData
.
getData
(
'
text/plain
'
);
}
value
=
value
.
split
(
"
\r
"
);
return
value
.
first
();
};
uploadFile
=
function
(
item
,
filename
)
{
var
formData
;
formData
=
new
FormData
();
formData
.
append
(
"
file
"
,
item
,
filename
);
formData
.
append
(
'
file
'
,
item
,
filename
);
return
$
.
ajax
({
url
:
uploads
_p
ath
,
type
:
"
POST
"
,
url
:
uploads
P
ath
,
type
:
'
POST
'
,
data
:
formData
,
dataType
:
"
json
"
,
dataType
:
'
json
'
,
processData
:
false
,
contentType
:
false
,
headers
:
{
"
X-CSRF-Token
"
:
$
(
"
meta[name=
\"
csrf-token
\"
]
"
).
attr
(
"
content
"
)
'
X-CSRF-Token
'
:
$
(
'
meta[name="csrf-token"]
'
).
attr
(
'
content
'
)
},
beforeSend
:
function
()
{
showSpinner
();
...
...
@@ -183,44 +237,54 @@ window.DropzoneInput = (function() {
}
});
};
updateAttachingMessage
=
(
files
,
messageContainer
)
=>
{
let
attachingMessage
;
const
filesCount
=
files
.
filter
(
function
(
file
)
{
return
file
.
status
===
'
uploading
'
||
file
.
status
===
'
queued
'
;
}).
length
;
// Dinamycally change uploading files text depending on files number in
// dropzone files queue.
if
(
filesCount
>
1
)
{
attachingMessage
=
'
Attaching
'
+
filesCount
+
'
files -
'
;
}
else
{
attachingMessage
=
'
Attaching a file -
'
;
}
messageContainer
.
text
(
attachingMessage
);
};
insertToTextArea
=
function
(
filename
,
url
)
{
return
$
(
child
).
val
(
function
(
index
,
val
)
{
return
val
.
replace
(
"
{{
"
+
filename
+
"
}}
"
,
url
);
return
val
.
replace
(
`{{
${
filename
}
}}`
,
url
);
});
};
appendToTextArea
=
function
(
url
)
{
return
$
(
child
).
val
(
function
(
index
,
val
)
{
return
val
+
url
+
"
\n
"
;
});
};
showSpinner
=
function
(
e
)
{
return
form
.
find
(
"
.div-dropzone-spinner
"
).
css
({
"
opacity
"
:
0.7
,
"
display
"
:
"
inherit
"
});
return
$uploadingProgressContainer
.
removeClass
(
'
hide
'
);
};
closeSpinner
=
function
()
{
return
form
.
find
(
"
.div-dropzone-spinner
"
).
css
({
"
opacity
"
:
0
,
"
display
"
:
"
none
"
});
return
$uploadingProgressContainer
.
addClass
(
'
hide
'
);
};
showError
=
function
(
message
)
{
var
checkIfMsgExists
,
errorAlert
;
errorAlert
=
$
(
form
).
find
(
'
.error-alert
'
);
checkIfMsgExists
=
errorAlert
.
children
().
length
;
if
(
checkIfMsgExists
===
0
)
{
errorAlert
.
append
(
divAlert
);
return
$
(
"
.div-dropzone-alert
"
).
append
(
btnAlert
+
message
);
}
$uploadingErrorContainer
.
removeClass
(
'
hide
'
);
$uploadingErrorMessage
.
html
(
message
);
};
closeAlertMessage
=
function
()
{
return
form
.
find
(
"
.div-dropzone-alert
"
).
alert
(
"
close
"
);
};
form
.
find
(
"
.markdown-selector
"
).
click
(
function
(
e
)
{
form
.
find
(
'
.markdown-selector
'
).
click
(
function
(
e
)
{
e
.
preventDefault
();
$
(
this
).
closest
(
'
.gfm-form
'
).
find
(
'
.div-dropzone
'
).
click
();
form
_t
extarea
.
focus
();
form
T
extarea
.
focus
();
});
}
...
...
app/assets/stylesheets/pages/note_form.scss
View file @
5b0e0869
...
...
@@ -277,6 +277,7 @@
.toolbar-text
{
font-size
:
14px
;
line-height
:
16px
;
margin-top
:
2px
;
@media
(
min-width
:
$screen-md-min
)
{
float
:
left
;
...
...
@@ -402,3 +403,45 @@
}
}
}
.uploading-container
{
float
:
right
;
@media
(
max-width
:
$screen-xs-max
)
{
float
:
left
;
margin-top
:
5px
;
}
}
.uploading-error-icon
,
.uploading-error-message
{
color
:
$gl-text-red
;
}
.uploading-error-message
{
@media
(
max-width
:
$screen-xs-max
)
{
&
:
:
after
{
content
:
"\a"
;
white-space
:
pre
;
}
}
}
.uploading-progress
{
margin-right
:
5px
;
}
.attach-new-file
,
.button-attach-file
,
.retry-uploading-link
{
color
:
$gl-link-color
;
padding
:
0
;
background
:
none
;
border
:
0
;
font-size
:
14px
;
line-height
:
16px
;
}
.markdown-selector
{
color
:
$gl-link-color
;
}
app/helpers/icons_helper.rb
View file @
5b0e0869
...
...
@@ -7,9 +7,10 @@ module IconsHelper
# font-awesome-rails gem, but should we ever use a different icon pack in the
# future we won't have to change hundreds of method calls.
def
icon
(
names
,
options
=
{})
if
(
options
.
keys
&
%w[aria-hidden aria-label]
).
empty?
# Add
`aria-hidden` if there are no aria's set
if
(
options
.
keys
&
%w[aria-hidden aria-label
data-hidden
]
).
empty?
# Add
'aria-hidden' and 'data-hidden' if they are not set in options.
options
[
'aria-hidden'
]
=
true
options
[
'data-hidden'
]
=
true
end
options
.
include?
(
:base
)
?
fa_stacked_icon
(
names
,
options
)
:
fa_icon
(
names
,
options
)
...
...
app/views/shared/notes/_hints.html.haml
View file @
5b0e0869
...
...
@@ -9,6 +9,27 @@
-
else
is
supported
%button
.toolbar-button.markdown-selector
{
type:
'button'
,
tabindex:
'-1'
}
%span
.uploading-container
%span
.uploading-progress-container.hide
=
icon
(
'file-image-o'
,
class:
'toolbar-button-icon'
)
%span
.attaching-file-message
-# Populated by app/assets/javascripts/dropzone_input.js
%span
.uploading-progress
0%
%span
.uploading-spinner
=
icon
(
'spinner spin'
,
class:
'toolbar-button-icon'
)
%span
.uploading-error-container.hide
%span
.uploading-error-icon
=
icon
(
'file-image-o'
,
class:
'toolbar-button-icon'
)
%span
.uploading-error-message
-# Populated by app/assets/javascripts/dropzone_input.js
%button
.retry-uploading-link
{
type:
'button'
}
Try again
or
%button
.attach-new-file.markdown-selector
{
type:
'button'
}
attach a new file
%button
.markdown-selector.button-attach-file
{
type:
'button'
,
tabindex:
'-1'
}
=
icon
(
'file-image-o'
,
class:
'toolbar-button-icon'
)
Attach a file
%button
.btn.btn-default.btn-xs.hide.button-cancel-uploading-files
{
type:
'button'
}
Cancel
changelogs/unreleased/add_ability_to_cancel_attaching_file_and_redesign_attaching_files_ui.yml
0 → 100644
View file @
5b0e0869
---
title
:
Add an ability to cancel attaching file and redesign attaching files UI
merge_request
:
9431
author
:
blackst0ne
spec/features/uploads/user_uploads_file_to_note_spec.rb
View file @
5b0e0869
...
...
@@ -5,18 +5,78 @@ feature 'User uploads file to note', feature: true do
let
(
:user
)
{
create
(
:user
)
}
let
(
:project
)
{
create
(
:empty_project
,
creator:
user
,
namespace:
user
.
namespace
)
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
,
author:
user
)
}
scenario
'they see the attached file'
,
js:
true
do
issue
=
create
(
:issue
,
project:
project
,
author:
user
)
before
do
login_as
(
user
)
visit
namespace_project_issue_path
(
project
.
namespace
,
project
,
issue
)
end
context
'before uploading'
do
it
'shows "Attach a file" button'
,
js:
true
do
expect
(
page
).
to
have_button
(
'Attach a file'
)
expect
(
page
).
not_to
have_selector
(
'.uploading-progress-container'
,
visible:
true
)
end
end
context
'uploading is in progress'
do
it
'shows "Cancel" button on uploading'
,
js:
true
do
dropzone_file
([
Rails
.
root
.
join
(
'spec'
,
'fixtures'
,
'dk.png'
)],
0
,
false
)
expect
(
page
).
to
have_button
(
'Cancel'
)
end
it
'cancels uploading on clicking to "Cancel" button'
,
js:
true
do
dropzone_file
([
Rails
.
root
.
join
(
'spec'
,
'fixtures'
,
'dk.png'
)],
0
,
false
)
click_button
'Cancel'
expect
(
page
).
to
have_button
(
'Attach a file'
)
expect
(
page
).
not_to
have_button
(
'Cancel'
)
expect
(
page
).
not_to
have_selector
(
'.uploading-progress-container'
,
visible:
true
)
end
it
'shows "Attaching a file" message on uploading 1 file'
,
js:
true
do
dropzone_file
([
Rails
.
root
.
join
(
'spec'
,
'fixtures'
,
'dk.png'
)],
0
,
false
)
expect
(
page
).
to
have_selector
(
'.attaching-file-message'
,
visible:
true
,
text:
'Attaching a file -'
)
end
it
'shows "Attaching 2 files" message on uploading 2 file'
,
js:
true
do
dropzone_file
([
Rails
.
root
.
join
(
'spec'
,
'fixtures'
,
'video_sample.mp4'
),
Rails
.
root
.
join
(
'spec'
,
'fixtures'
,
'dk.png'
)],
0
,
false
)
expect
(
page
).
to
have_selector
(
'.attaching-file-message'
,
visible:
true
,
text:
'Attaching 2 files -'
)
end
dropzone_file
(
Rails
.
root
.
join
(
'spec'
,
'fixtures'
,
'dk.png'
))
it
'shows error message, "retry" and "attach a new file" link a if file is too big'
,
js:
true
do
dropzone_file
([
Rails
.
root
.
join
(
'spec'
,
'fixtures'
,
'video_sample.mp4'
)],
0.01
)
error_text
=
'File is too big (0.06MiB). Max filesize: 0.01MiB.'
expect
(
page
).
to
have_selector
(
'.uploading-error-message'
,
visible:
true
,
text:
error_text
)
expect
(
page
).
to
have_selector
(
'.retry-uploading-link'
,
visible:
true
,
text:
'Try again'
)
expect
(
page
).
to
have_selector
(
'.attach-new-file'
,
visible:
true
,
text:
'attach a new file'
)
expect
(
page
).
not_to
have_button
(
'Attach a file'
)
end
end
context
'uploading is complete'
do
it
'shows "Attach a file" button on uploading complete'
,
js:
true
do
dropzone_file
([
Rails
.
root
.
join
(
'spec'
,
'fixtures'
,
'dk.png'
)])
wait_for_ajax
expect
(
page
).
to
have_button
(
'Attach a file'
)
expect
(
page
).
not_to
have_selector
(
'.uploading-progress-container'
,
visible:
true
)
end
scenario
'they see the attached file'
,
js:
true
do
dropzone_file
([
Rails
.
root
.
join
(
'spec'
,
'fixtures'
,
'dk.png'
)])
click_button
'Comment'
wait_for_ajax
expect
(
find
(
'a.no-attachment-icon img[alt="dk"]'
)[
'src'
])
.
to
match
(
%r{/
#{
project
.
full_path
}
/uploads/
\h
{32}/dk
\.
png$}
)
end
end
end
spec/support/dropzone_helper.rb
View file @
5b0e0869
...
...
@@ -6,32 +6,52 @@ module DropzoneHelper
# Dropzone events to perform the actual upload.
#
# This method waits for the upload to complete before returning.
def
dropzone_file
(
file_path
)
# max_file_size is an optional parameter.
# If it's not 0, then it used in dropzone.maxFilesize parameter.
# wait_for_queuecomplete is an optional parameter.
# If it's 'false', then the helper will NOT wait for backend response
# It lets to test behaviors while AJAX is processing.
def
dropzone_file
(
files
,
max_file_size
=
0
,
wait_for_queuecomplete
=
true
)
# Generate a fake file input that Capybara can attach to
page
.
execute_script
<<-
JS
.
strip_heredoc
$('#fakeFileInput').remove();
var fakeFileInput = window.$('<input/>').attr(
{id: 'fakeFileInput', type: 'file'}
{id: 'fakeFileInput', type: 'file'
, multiple: true
}
).appendTo('body');
window._dropzoneComplete = false;
JS
# Attach
the file
to the fake input selector with Capybara
attach_file
(
'fakeFileInput'
,
file
_path
)
# Attach
files
to the fake input selector with Capybara
attach_file
(
'fakeFileInput'
,
file
s
)
# Manually trigger a Dropzone "drop" event with the fake input's file list
page
.
execute_script
<<-
JS
.
strip_heredoc
var fileList = [$('#fakeFileInput')[0].files[0]];
var e = jQuery.Event('drop', { dataTransfer : { files : fileList } });
var dropzone = $('.div-dropzone')[0].dropzone;
dropzone.options.autoProcessQueue = false;
if (
#{
max_file_size
}
> 0) {
dropzone.options.maxFilesize =
#{
max_file_size
}
;
}
dropzone.on('queuecomplete', function() {
window._dropzoneComplete = true;
});
var fileList = [$('#fakeFileInput')[0].files];
$.map(fileList, function(file){
var e = jQuery.Event('drop', { dataTransfer : { files : file } });
dropzone.listeners[0].events.drop(e);
});
dropzone.processQueue();
JS
if
wait_for_queuecomplete
# Wait until Dropzone's fired `queuecomplete`
loop
until
page
.
evaluate_script
(
'window._dropzoneComplete === true'
)
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment