Commit 47f78a2c7614b0a32110e909c2244f67310d030e
Exists in
master
and in
4 other branches
Merge pull request #4440 from jackbot/editable-notes
Editable notes
Showing
11 changed files
with
332 additions
and
34 deletions
Show diff stats
app/assets/javascripts/notes.js
@@ -46,6 +46,26 @@ var NoteList = { | @@ -46,6 +46,26 @@ var NoteList = { | ||
46 | ".js-note-delete", | 46 | ".js-note-delete", |
47 | NoteList.removeNote); | 47 | NoteList.removeNote); |
48 | 48 | ||
49 | + // show the edit note form | ||
50 | + $(document).on("click", | ||
51 | + ".js-note-edit", | ||
52 | + NoteList.showEditNoteForm); | ||
53 | + | ||
54 | + // cancel note editing | ||
55 | + $(document).on("click", | ||
56 | + ".note-edit-cancel", | ||
57 | + NoteList.cancelNoteEdit); | ||
58 | + | ||
59 | + // delete note attachment | ||
60 | + $(document).on("click", | ||
61 | + ".js-note-attachment-delete", | ||
62 | + NoteList.deleteNoteAttachment); | ||
63 | + | ||
64 | + // update the note after editing | ||
65 | + $(document).on("ajax:complete", | ||
66 | + "form.edit_note", | ||
67 | + NoteList.updateNote); | ||
68 | + | ||
49 | // reset main target form after submit | 69 | // reset main target form after submit |
50 | $(document).on("ajax:complete", | 70 | $(document).on("ajax:complete", |
51 | ".js-main-target-form", | 71 | ".js-main-target-form", |
@@ -53,12 +73,12 @@ var NoteList = { | @@ -53,12 +73,12 @@ var NoteList = { | ||
53 | 73 | ||
54 | 74 | ||
55 | $(document).on("click", | 75 | $(document).on("click", |
56 | - ".js-choose-note-attachment-button", | ||
57 | - NoteList.chooseNoteAttachment); | 76 | + ".js-choose-note-attachment-button", |
77 | + NoteList.chooseNoteAttachment); | ||
58 | 78 | ||
59 | $(document).on("click", | 79 | $(document).on("click", |
60 | - ".js-show-outdated-discussion", | ||
61 | - function(e) { $(this).next('.outdated-discussion').show(); e.preventDefault() }); | 80 | + ".js-show-outdated-discussion", |
81 | + function(e) { $(this).next('.outdated-discussion').show(); e.preventDefault() }); | ||
62 | }, | 82 | }, |
63 | 83 | ||
64 | 84 | ||
@@ -97,8 +117,8 @@ var NoteList = { | @@ -97,8 +117,8 @@ var NoteList = { | ||
97 | 117 | ||
98 | /** | 118 | /** |
99 | * Called when clicking the "Choose File" button. | 119 | * Called when clicking the "Choose File" button. |
100 | - * | ||
101 | - * Opesn the file selection dialog. | 120 | + * |
121 | + * Opens the file selection dialog. | ||
102 | */ | 122 | */ |
103 | chooseNoteAttachment: function() { | 123 | chooseNoteAttachment: function() { |
104 | var form = $(this).closest("form"); | 124 | var form = $(this).closest("form"); |
@@ -133,7 +153,7 @@ var NoteList = { | @@ -133,7 +153,7 @@ var NoteList = { | ||
133 | 153 | ||
134 | /** | 154 | /** |
135 | * Called in response to "cancel" on a diff note form. | 155 | * Called in response to "cancel" on a diff note form. |
136 | - * | 156 | + * |
137 | * Shows the reply button again. | 157 | * Shows the reply button again. |
138 | * Removes the form and if necessary it's temporary row. | 158 | * Removes the form and if necessary it's temporary row. |
139 | */ | 159 | */ |
@@ -177,6 +197,59 @@ var NoteList = { | @@ -177,6 +197,59 @@ var NoteList = { | ||
177 | }, | 197 | }, |
178 | 198 | ||
179 | /** | 199 | /** |
200 | + * Called in response to clicking the edit note link | ||
201 | + * | ||
202 | + * Replaces the note text with the note edit form | ||
203 | + * Adds a hidden div with the original content of the note to fill the edit note form with | ||
204 | + * if the user cancels | ||
205 | + */ | ||
206 | + showEditNoteForm: function(e) { | ||
207 | + e.preventDefault(); | ||
208 | + var note = $(this).closest(".note"); | ||
209 | + note.find(".note-text").hide(); | ||
210 | + | ||
211 | + // Show the attachment delete link | ||
212 | + note.find(".js-note-attachment-delete").show(); | ||
213 | + | ||
214 | + var form = note.find(".note-edit-form"); | ||
215 | + form.show(); | ||
216 | + | ||
217 | + | ||
218 | + var textarea = form.find("textarea"); | ||
219 | + var p = $("<p></p>").text(textarea.val()); | ||
220 | + var hidden_div = $('<div class="note-original-content"></div>').append(p); | ||
221 | + form.append(hidden_div); | ||
222 | + hidden_div.hide(); | ||
223 | + textarea.focus(); | ||
224 | + }, | ||
225 | + | ||
226 | + /** | ||
227 | + * Called in response to clicking the cancel button when editing a note | ||
228 | + * | ||
229 | + * Resets and hides the note editing form | ||
230 | + */ | ||
231 | + cancelNoteEdit: function(e) { | ||
232 | + e.preventDefault(); | ||
233 | + var note = $(this).closest(".note"); | ||
234 | + NoteList.resetNoteEditing(note); | ||
235 | + }, | ||
236 | + | ||
237 | + | ||
238 | + /** | ||
239 | + * Called in response to clicking the delete attachment link | ||
240 | + * | ||
241 | + * Removes the attachment wrapper view, including image tag if it exists | ||
242 | + * Resets the note editing form | ||
243 | + */ | ||
244 | + deleteNoteAttachment: function() { | ||
245 | + var note = $(this).closest(".note"); | ||
246 | + note.find(".note-attachment").remove(); | ||
247 | + NoteList.resetNoteEditing(note); | ||
248 | + NoteList.rewriteTimestamp(note.find(".note-last-update")); | ||
249 | + }, | ||
250 | + | ||
251 | + | ||
252 | + /** | ||
180 | * Called when clicking on the "reply" button for a diff line. | 253 | * Called when clicking on the "reply" button for a diff line. |
181 | * | 254 | * |
182 | * Shows the note form below the notes. | 255 | * Shows the note form below the notes. |
@@ -426,5 +499,65 @@ var NoteList = { | @@ -426,5 +499,65 @@ var NoteList = { | ||
426 | votes.find(".upvotes").text(votes.find(".upvotes").text().replace(/\d+/, upvotes)); | 499 | votes.find(".upvotes").text(votes.find(".upvotes").text().replace(/\d+/, upvotes)); |
427 | votes.find(".downvotes").text(votes.find(".downvotes").text().replace(/\d+/, downvotes)); | 500 | votes.find(".downvotes").text(votes.find(".downvotes").text().replace(/\d+/, downvotes)); |
428 | } | 501 | } |
502 | + }, | ||
503 | + | ||
504 | + /** | ||
505 | + * Called in response to the edit note form being submitted | ||
506 | + * | ||
507 | + * Updates the current note field. | ||
508 | + * Hides the edit note form | ||
509 | + */ | ||
510 | + updateNote: function(e, xhr, settings) { | ||
511 | + response = JSON.parse(xhr.responseText); | ||
512 | + if (response.success) { | ||
513 | + var note_li = $("#note_" + response.id); | ||
514 | + var note_text = note_li.find(".note-text"); | ||
515 | + note_text.html(response.note).show(); | ||
516 | + | ||
517 | + var note_form = note_li.find(".note-edit-form"); | ||
518 | + note_form.hide(); | ||
519 | + note_form.find(".btn-save").enableButton(); | ||
520 | + | ||
521 | + // Update the "Edited at xxx label" on the note to show it's just been updated | ||
522 | + NoteList.rewriteTimestamp(note_li.find(".note-last-update")); | ||
523 | + } | ||
524 | + }, | ||
525 | + | ||
526 | + /** | ||
527 | + * Called in response to the 'cancel note' link clicked, or after deleting a note attachment | ||
528 | + * | ||
529 | + * Hides the edit note form and shows the note | ||
530 | + * Resets the edit note form textarea with the original content of the note | ||
531 | + */ | ||
532 | + resetNoteEditing: function(note) { | ||
533 | + note.find(".note-text").show(); | ||
534 | + | ||
535 | + // Hide the attachment delete link | ||
536 | + note.find(".js-note-attachment-delete").hide(); | ||
537 | + | ||
538 | + // Put the original content of the note back into the edit form textarea | ||
539 | + var form = note.find(".note-edit-form"); | ||
540 | + var original_content = form.find(".note-original-content"); | ||
541 | + form.find("textarea").val(original_content.text()); | ||
542 | + original_content.remove(); | ||
543 | + | ||
544 | + note.find(".note-edit-form").hide(); | ||
545 | + }, | ||
546 | + | ||
547 | + /** | ||
548 | + * Utility function to generate new timestamp text for a note | ||
549 | + * | ||
550 | + */ | ||
551 | + rewriteTimestamp: function(element) { | ||
552 | + // Strip all newlines from the existing timestamp | ||
553 | + var ts = element.text().replace(/\n/g, ' ').trim(); | ||
554 | + | ||
555 | + // If the timestamp already has '(Edited xxx ago)' text, remove it | ||
556 | + ts = ts.replace(new RegExp("\\(Edited [A-Za-z0-9 ]+\\)$", "gi"), ""); | ||
557 | + | ||
558 | + // Append "(Edited just now)" | ||
559 | + ts = (ts + " <small>(Edited just now)</small>"); | ||
560 | + | ||
561 | + element.html(ts); | ||
429 | } | 562 | } |
430 | }; | 563 | }; |
app/assets/stylesheets/sections/notes.scss
@@ -325,3 +325,32 @@ ul.notes { | @@ -325,3 +325,32 @@ ul.notes { | ||
325 | float: left; | 325 | float: left; |
326 | } | 326 | } |
327 | } | 327 | } |
328 | + | ||
329 | +.note-edit-form { | ||
330 | + display: none; | ||
331 | + | ||
332 | + .note_text { | ||
333 | + border: 1px solid #DDD; | ||
334 | + box-shadow: none; | ||
335 | + font-size: 14px; | ||
336 | + height: 80px; | ||
337 | + width: 98.6%; | ||
338 | + } | ||
339 | + | ||
340 | + .form-actions { | ||
341 | + padding-left: 20px; | ||
342 | + | ||
343 | + .btn-save { | ||
344 | + float: left; | ||
345 | + } | ||
346 | + | ||
347 | + .note-form-option { | ||
348 | + float: left; | ||
349 | + padding: 2px 0 0 25px; | ||
350 | + } | ||
351 | + } | ||
352 | +} | ||
353 | + | ||
354 | +.js-note-attachment-delete { | ||
355 | + display: none; | ||
356 | +} |
app/controllers/notes_controller.rb
@@ -38,6 +38,32 @@ class NotesController < ProjectResourceController | @@ -38,6 +38,32 @@ class NotesController < ProjectResourceController | ||
38 | end | 38 | end |
39 | end | 39 | end |
40 | 40 | ||
41 | + def update | ||
42 | + @note = @project.notes.find(params[:id]) | ||
43 | + return access_denied! unless can?(current_user, :admin_note, @note) | ||
44 | + | ||
45 | + @note.update_attributes(params[:note]) | ||
46 | + | ||
47 | + respond_to do |format| | ||
48 | + format.js do | ||
49 | + render js: { success: @note.valid?, id: @note.id, note: view_context.markdown(@note.note) }.to_json | ||
50 | + end | ||
51 | + format.html do | ||
52 | + redirect_to :back | ||
53 | + end | ||
54 | + end | ||
55 | + end | ||
56 | + | ||
57 | + def delete_attachment | ||
58 | + @note = @project.notes.find(params[:id]) | ||
59 | + @note.remove_attachment! | ||
60 | + @note.update_attribute(:attachment, nil) | ||
61 | + | ||
62 | + respond_to do |format| | ||
63 | + format.js { render nothing: true } | ||
64 | + end | ||
65 | + end | ||
66 | + | ||
41 | def preview | 67 | def preview |
42 | render text: view_context.markdown(params[:note]) | 68 | render text: view_context.markdown(params[:note]) |
43 | end | 69 | end |
app/helpers/notes_helper.rb
@@ -28,4 +28,11 @@ module NotesHelper | @@ -28,4 +28,11 @@ module NotesHelper | ||
28 | def loading_new_notes? | 28 | def loading_new_notes? |
29 | params[:loading_new].present? | 29 | params[:loading_new].present? |
30 | end | 30 | end |
31 | + | ||
32 | + def note_timestamp(note) | ||
33 | + # Shows the created at time and the updated at time if different | ||
34 | + ts = "#{time_ago_in_words(note.created_at)} ago" | ||
35 | + ts << content_tag(:small, " (Edited #{time_ago_in_words(note.updated_at)} ago)") if note.updated_at != note.created_at | ||
36 | + ts.html_safe | ||
37 | + end | ||
31 | end | 38 | end |
app/views/dashboard/projects.html.haml
@@ -20,22 +20,24 @@ | @@ -20,22 +20,24 @@ | ||
20 | = nav_tab :scope, 'joined' do | 20 | = nav_tab :scope, 'joined' do |
21 | = link_to "Joined", projects_dashboard_path(scope: 'joined') | 21 | = link_to "Joined", projects_dashboard_path(scope: 'joined') |
22 | 22 | ||
23 | - %p.light Filter by label: | ||
24 | - %ul.bordered-list | ||
25 | - - @labels.each do |label| | ||
26 | - %li{ class: (label.name == params[:label]) ? 'active' : 'light' } | ||
27 | - = link_to projects_dashboard_path(scope: params[:scope], label: label.name) do | ||
28 | - %i.icon-tag | ||
29 | - = label.name | 23 | + - if @labels.any? |
24 | + %p.light Filter by label: | ||
25 | + %ul.bordered-list | ||
26 | + - @labels.each do |label| | ||
27 | + %li{ class: (label.name == params[:label]) ? 'active' : 'light' } | ||
28 | + = link_to projects_dashboard_path(scope: params[:scope], label: label.name) do | ||
29 | + %i.icon-tag | ||
30 | + = label.name | ||
30 | 31 | ||
31 | 32 | ||
32 | .span9 | 33 | .span9 |
33 | - = form_tag projects_dashboard_path, method: 'get' do | ||
34 | - %fieldset.dashboard-search-filter | ||
35 | - = hidden_field_tag "scope", params[:scope] | ||
36 | - = search_field_tag "search", params[:search], { id: 'dashboard_projects_search', placeholder: 'Search', class: 'left input-xxlarge'} | ||
37 | - = button_tag type: 'submit', class: 'btn' do | ||
38 | - %i.icon-search | 34 | + - if @projects.any? |
35 | + = form_tag projects_dashboard_path, method: 'get' do | ||
36 | + %fieldset.dashboard-search-filter | ||
37 | + = hidden_field_tag "scope", params[:scope] | ||
38 | + = search_field_tag "search", params[:search], { id: 'dashboard_projects_search', placeholder: 'Search', class: 'left input-xxlarge'} | ||
39 | + = button_tag type: 'submit', class: 'btn' do | ||
40 | + %i.icon-search | ||
39 | 41 | ||
40 | %ul.bordered-list | 42 | %ul.bordered-list |
41 | - @projects.each do |project| | 43 | - @projects.each do |project| |
app/views/notes/_note.html.haml
@@ -6,13 +6,14 @@ | @@ -6,13 +6,14 @@ | ||
6 | Link here | 6 | Link here |
7 | | 7 | |
8 | - if(note.author_id == current_user.id) || can?(current_user, :admin_note, @project) | 8 | - if(note.author_id == current_user.id) || can?(current_user, :admin_note, @project) |
9 | - = link_to project_note_path(@project, note), title: "Remove comment", method: :delete, confirm: 'Are you sure you want to remove comment?', remote: true, class: "danger js-note-delete" do | 9 | + = link_to "#", title: "Edit comment", class: "js-note-edit" do |
10 | + %i.icon-edit | ||
11 | + = link_to project_note_path(@project, note), title: "Remove comment", method: :delete, confirm: 'Are you sure you want to remove this comment?', remote: true, class: "danger js-note-delete" do | ||
10 | %i.icon-trash.cred | 12 | %i.icon-trash.cred |
11 | = image_tag gravatar_icon(note.author.email), class: "avatar s32", alt: '' | 13 | = image_tag gravatar_icon(note.author.email), class: "avatar s32", alt: '' |
12 | = link_to_member(@project, note.author, avatar: false) | 14 | = link_to_member(@project, note.author, avatar: false) |
13 | %span.note-last-update | 15 | %span.note-last-update |
14 | - = time_ago_in_words(note.updated_at) | ||
15 | - ago | 16 | + = note_timestamp(note) |
16 | 17 | ||
17 | - if note.upvote? | 18 | - if note.upvote? |
18 | %span.vote.upvote.label.label-success | 19 | %span.vote.upvote.label.label-success |
@@ -25,13 +26,37 @@ | @@ -25,13 +26,37 @@ | ||
25 | 26 | ||
26 | 27 | ||
27 | .note-body | 28 | .note-body |
28 | - = preserve do | ||
29 | - = markdown(note.note) | 29 | + .note-text |
30 | + = preserve do | ||
31 | + = markdown(note.note) | ||
32 | + | ||
33 | + .note-edit-form | ||
34 | + = form_for note, url: project_note_path(@project, note), method: :put, remote: true do |f| | ||
35 | + = f.text_area :note, class: 'note_text js-note-text js-gfm-input turn-on' | ||
36 | + | ||
37 | + .form-actions | ||
38 | + = f.submit 'Save changes', class: "btn btn-primary btn-save" | ||
39 | + | ||
40 | + .note-form-option | ||
41 | + %a.choose-btn.btn.btn-small.js-choose-note-attachment-button | ||
42 | + %i.icon-paper-clip | ||
43 | + %span Choose File ... | ||
44 | + | ||
45 | + %span.file_name.js-attachment-filename File name... | ||
46 | + = f.file_field :attachment, class: "js-note-attachment-input hide" | ||
47 | + | ||
48 | + = link_to 'Cancel', "#", class: "btn btn-cancel note-edit-cancel" | ||
49 | + | ||
50 | + | ||
30 | - if note.attachment.url | 51 | - if note.attachment.url |
31 | - - if note.attachment.image? | ||
32 | - = image_tag note.attachment.url, class: 'note-image-attach' | ||
33 | - .attachment.pull-right | ||
34 | - = link_to note.attachment.secure_url, target: "_blank" do | ||
35 | - %i.icon-paper-clip | ||
36 | - = note.attachment_identifier | ||
37 | - .clear | 52 | + .note-attachment |
53 | + - if note.attachment.image? | ||
54 | + = image_tag note.attachment.url, class: 'note-image-attach' | ||
55 | + .attachment.pull-right | ||
56 | + = link_to note.attachment.secure_url, target: "_blank" do | ||
57 | + %i.icon-paper-clip | ||
58 | + = note.attachment_identifier | ||
59 | + = link_to delete_attachment_project_note_path(@project, note), | ||
60 | + title: "Delete this attachment", method: :delete, remote: true, confirm: 'Are you sure you want to remove the attachment?', class: "danger js-note-attachment-delete" do | ||
61 | + %i.icon-trash.cred | ||
62 | + .clear | ||
38 | \ No newline at end of file | 63 | \ No newline at end of file |
config/routes.rb
@@ -319,7 +319,10 @@ Gitlab::Application.routes.draw do | @@ -319,7 +319,10 @@ Gitlab::Application.routes.draw do | ||
319 | end | 319 | end |
320 | end | 320 | end |
321 | 321 | ||
322 | - resources :notes, only: [:index, :create, :destroy] do | 322 | + resources :notes, only: [:index, :create, :destroy, :update] do |
323 | + member do | ||
324 | + delete :delete_attachment | ||
325 | + end | ||
323 | collection do | 326 | collection do |
324 | post :preview | 327 | post :preview |
325 | end | 328 | end |
spec/factories.rb
1 | +include ActionDispatch::TestProcess | ||
2 | + | ||
1 | FactoryGirl.define do | 3 | FactoryGirl.define do |
2 | sequence :sentence, aliases: [:title, :content] do | 4 | sequence :sentence, aliases: [:title, :content] do |
3 | Faker::Lorem.sentence | 5 | Faker::Lorem.sentence |
@@ -120,6 +122,7 @@ FactoryGirl.define do | @@ -120,6 +122,7 @@ FactoryGirl.define do | ||
120 | factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note] | 122 | factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note] |
121 | factory :note_on_merge_request, traits: [:on_merge_request] | 123 | factory :note_on_merge_request, traits: [:on_merge_request] |
122 | factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff] | 124 | factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff] |
125 | + factory :note_on_merge_request_with_attachment, traits: [:on_merge_request, :with_attachment] | ||
123 | 126 | ||
124 | trait :on_commit do | 127 | trait :on_commit do |
125 | project factory: :project_with_code | 128 | project factory: :project_with_code |
@@ -141,6 +144,10 @@ FactoryGirl.define do | @@ -141,6 +144,10 @@ FactoryGirl.define do | ||
141 | noteable_id 1 | 144 | noteable_id 1 |
142 | noteable_type "Issue" | 145 | noteable_type "Issue" |
143 | end | 146 | end |
147 | + | ||
148 | + trait :with_attachment do | ||
149 | + attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png") } | ||
150 | + end | ||
144 | end | 151 | end |
145 | 152 | ||
146 | factory :event do | 153 | factory :event do |
spec/features/notes_on_merge_requests_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper' | @@ -3,6 +3,7 @@ require 'spec_helper' | ||
3 | describe "On a merge request", js: true do | 3 | describe "On a merge request", js: true do |
4 | let!(:project) { create(:project_with_code) } | 4 | let!(:project) { create(:project_with_code) } |
5 | let!(:merge_request) { create(:merge_request, project: project) } | 5 | let!(:merge_request) { create(:merge_request, project: project) } |
6 | + let!(:note) { create(:note_on_merge_request_with_attachment, project: project) } | ||
6 | 7 | ||
7 | before do | 8 | before do |
8 | login_as :user | 9 | login_as :user |
@@ -72,6 +73,71 @@ describe "On a merge request", js: true do | @@ -72,6 +73,71 @@ describe "On a merge request", js: true do | ||
72 | should_not have_css(".note") | 73 | should_not have_css(".note") |
73 | end | 74 | end |
74 | end | 75 | end |
76 | + | ||
77 | + describe "when editing a note", js: true do | ||
78 | + it "should contain the hidden edit form" do | ||
79 | + within("#note_#{note.id}") { should have_css(".note-edit-form", visible: false) } | ||
80 | + end | ||
81 | + | ||
82 | + describe "editing the note" do | ||
83 | + before do | ||
84 | + find('.note').hover | ||
85 | + find(".js-note-edit").click | ||
86 | + end | ||
87 | + | ||
88 | + it "should show the note edit form and hide the note body" do | ||
89 | + within("#note_#{note.id}") do | ||
90 | + find(".note-edit-form", visible: true).should be_visible | ||
91 | + find(".note-text", visible: false).should_not be_visible | ||
92 | + end | ||
93 | + end | ||
94 | + | ||
95 | + it "should reset the edit note form textarea with the original content of the note if cancelled" do | ||
96 | + find('.note').hover | ||
97 | + find(".js-note-edit").click | ||
98 | + | ||
99 | + within(".note-edit-form") do | ||
100 | + fill_in "note[note]", with: "Some new content" | ||
101 | + find(".btn-cancel").click | ||
102 | + find(".js-note-text", visible: false).text.should == note.note | ||
103 | + end | ||
104 | + end | ||
105 | + | ||
106 | + it "appends the edited at time to the note" do | ||
107 | + find('.note').hover | ||
108 | + find(".js-note-edit").click | ||
109 | + | ||
110 | + within(".note-edit-form") do | ||
111 | + fill_in "note[note]", with: "Some new content" | ||
112 | + find(".btn-save").click | ||
113 | + end | ||
114 | + | ||
115 | + within("#note_#{note.id}") do | ||
116 | + should have_css(".note-last-update small") | ||
117 | + find(".note-last-update small").text.should match(/Edited just now/) | ||
118 | + end | ||
119 | + end | ||
120 | + end | ||
121 | + | ||
122 | + describe "deleting an attachment" do | ||
123 | + before do | ||
124 | + find('.note').hover | ||
125 | + find(".js-note-edit").click | ||
126 | + end | ||
127 | + | ||
128 | + it "shows the delete link" do | ||
129 | + within(".note-attachment") do | ||
130 | + should have_css(".js-note-attachment-delete") | ||
131 | + end | ||
132 | + end | ||
133 | + | ||
134 | + it "removes the attachment div and resets the edit form" do | ||
135 | + find(".js-note-attachment-delete").click | ||
136 | + should_not have_css(".note-attachment") | ||
137 | + find(".note-edit-form", visible: false).should_not be_visible | ||
138 | + end | ||
139 | + end | ||
140 | + end | ||
75 | end | 141 | end |
76 | 142 | ||
77 | describe "On a merge request diff", js: true, focus: true do | 143 | describe "On a merge request diff", js: true, focus: true do |
1.12 KB
spec/routing/project_routing_spec.rb
@@ -262,7 +262,7 @@ end | @@ -262,7 +262,7 @@ end | ||
262 | # project_snippet GET /:project_id/snippets/:id(.:format) snippets#show | 262 | # project_snippet GET /:project_id/snippets/:id(.:format) snippets#show |
263 | # PUT /:project_id/snippets/:id(.:format) snippets#update | 263 | # PUT /:project_id/snippets/:id(.:format) snippets#update |
264 | # DELETE /:project_id/snippets/:id(.:format) snippets#destroy | 264 | # DELETE /:project_id/snippets/:id(.:format) snippets#destroy |
265 | -describe Projects::SnippetsController, "routing" do | 265 | +describe SnippetsController, "routing" do |
266 | it "to #raw" do | 266 | it "to #raw" do |
267 | get("/gitlabhq/snippets/1/raw").should route_to('projects/snippets#raw', project_id: 'gitlabhq', id: '1') | 267 | get("/gitlabhq/snippets/1/raw").should route_to('projects/snippets#raw', project_id: 'gitlabhq', id: '1') |
268 | end | 268 | end |