Commit 6862e788 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into add-svg-loader

* master:
  Fix migration without DOWNTIME clause specified
  Fix CSS classes
  fix missing @ symbol
  Update CHANGELOG.md for 8.16.7
  Update CHANGELOG.md for 8.16.7
  Update CHANGELOG.md for 8.16.7
  Make RuboCop happy
  Add development fixtures for nested groups
  Align last column buttons with new environment button
  Removed jQuery UI draggable
  ensure webpack dev server proxy connects regardless of request headers
  Keep consistent in handling indexOf results
  Replace setInterval with setTimeout to prevent highly frequent requests
  Add feature specs for three types of user uploads
  Minor refactoring of Uploaders
  Fix #27840 - Improve the search bar experience on mobile
  Fix false positive caused by non-interpolated string use
  Fix inline comment images by removing wrapper #20890
parents 61f65992 7733f285
......@@ -182,6 +182,12 @@ entry.
- Remove deprecated GitlabCiService.
- Requeue pending deletion projects.
## 8.16.7 (2017-02-27)
- No changes.
- No changes.
- Fix MR changes tab size count when there are over 100 files in the diff.
## 8.16.6 (2017-02-17)
- API: Fix file downloading. !0 (8267)
......
......@@ -7,8 +7,6 @@
/* global Aside */
window.$ = window.jQuery = require('jquery');
require('jquery-ui/ui/draggable');
require('jquery-ui/ui/sortable');
require('jquery-ujs');
require('vendor/jquery.endless-scroll');
require('vendor/jquery.highlight');
......
......@@ -7,7 +7,7 @@
var DOWN_BUILD_TRACE = '#down-build-trace';
this.Build = (function() {
Build.interval = null;
Build.timeout = null;
Build.state = null;
......@@ -31,7 +31,7 @@
this.$scrollBottomBtn = $('#scroll-bottom');
this.$buildRefreshAnimation = $('.js-build-refresh');
clearInterval(Build.interval);
clearTimeout(Build.timeout);
// Init breakpoint checker
this.bp = Breakpoints.get();
......@@ -52,17 +52,7 @@
this.getInitialBuildTrace();
this.initScrollButtonAffix();
}
if (this.buildStatus === "running" || this.buildStatus === "pending") {
Build.interval = setInterval((function(_this) {
// Check for new build output if user still watching build page
// Only valid for runnig build when output changes during time
return function() {
if (_this.location() === _this.pageUrl) {
return _this.getBuildTrace();
}
};
})(this), 4000);
}
this.invokeBuildTrace();
}
Build.prototype.initSidebar = function() {
......@@ -75,6 +65,22 @@
return window.location.href.split("#")[0];
};
Build.prototype.invokeBuildTrace = function() {
var continueRefreshStatuses = ['running', 'pending'];
// Continue to update build trace when build is running or pending
if (continueRefreshStatuses.indexOf(this.buildStatus) !== -1) {
// Check for new build output if user still watching build page
// Only valid for runnig build when output changes during time
Build.timeout = setTimeout((function(_this) {
return function() {
if (_this.location() === _this.pageUrl) {
return _this.getBuildTrace();
}
};
})(this), 4000);
}
};
Build.prototype.getInitialBuildTrace = function() {
var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped'];
......@@ -86,7 +92,7 @@
if (window.location.hash === DOWN_BUILD_TRACE) {
$("html,body").scrollTop(this.$buildTrace.height());
}
if (removeRefreshStatuses.indexOf(buildData.status) >= 0) {
if (removeRefreshStatuses.indexOf(buildData.status) !== -1) {
this.$buildRefreshAnimation.remove();
return this.initScrollMonitor();
}
......@@ -105,6 +111,7 @@
if (log.state) {
_this.state = log.state;
}
_this.invokeBuildTrace();
if (log.status === "running") {
if (log.append) {
$('.js-build-output').append(log.html);
......
......@@ -52,6 +52,30 @@
return this.views[viewMode].call(this);
};
ImageFile.prototype.initDraggable = function($el, padding, callback) {
var dragging = false;
var $body = $('body');
var $offsetEl = $el.parent();
$el.off('mousedown').on('mousedown', function() {
dragging = true;
$body.css('user-select', 'none');
});
$body.off('mouseup').off('mousemove').on('mouseup', function() {
dragging = false;
$body.css('user-select', '');
})
.on('mousemove', function(e) {
var left;
if (!dragging) return;
left = e.pageX - ($offsetEl.offset().left + padding);
callback(e, left);
});
};
prepareFrames = function(view) {
var maxHeight, maxWidth;
maxWidth = 0;
......@@ -96,26 +120,30 @@
maxHeight = 0;
return $('.swipe.view', this.file).each((function(_this) {
return function(index, view) {
var ref;
var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref;
ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
$('.swipe-frame', view).css({
$swipeFrame = $('.swipe-frame', view);
$swipeWrap = $('.swipe-wrap', view);
$swipeBar = $('.swipe-bar', view);
$swipeFrame.css({
width: maxWidth + 16,
height: maxHeight + 28
});
$('.swipe-wrap', view).css({
$swipeWrap.css({
width: maxWidth + 1,
height: maxHeight + 2
});
return $('.swipe-bar', view).css({
$swipeBar.css({
left: 0
}).draggable({
axis: 'x',
containment: 'parent',
drag: function(event) {
return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left);
},
stop: function(event) {
return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left);
});
wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10);
_this.initDraggable($swipeBar, wrapPadding, function(e, left) {
if (left > 0 && left < $swipeFrame.width() - (wrapPadding * 2)) {
$swipeWrap.width((maxWidth + 1) - left);
$swipeBar.css('left', left);
}
});
};
......@@ -128,9 +156,14 @@
dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width();
return $('.onion-skin.view', this.file).each((function(_this) {
return function(index, view) {
var ref;
var $frame, $track, $dragger, $frameAdded, framePadding, ref, dragging = false;
ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
$('.onion-skin-frame', view).css({
$frame = $('.onion-skin-frame', view);
$frameAdded = $('.frame.added', view);
$track = $('.drag-track', view);
$dragger = $('.dragger', $track);
$frame.css({
width: maxWidth + 16,
height: maxHeight + 28
});
......@@ -138,16 +171,18 @@
width: maxWidth + 1,
height: maxHeight + 2
});
return $('.dragger', view).css({
$dragger.css({
left: dragTrackWidth
}).draggable({
axis: 'x',
containment: 'parent',
drag: function(event) {
return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth);
},
stop: function(event) {
return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth);
});
framePadding = parseInt($frameAdded.css('right').replace('px', ''), 10);
_this.initDraggable($dragger, framePadding, function(e, left) {
var opacity = left / dragTrackWidth;
if (opacity >= 0 && opacity <= 1) {
$dragger.css('left', left);
$frameAdded.css('opacity', opacity);
}
});
};
......
......@@ -486,25 +486,23 @@ module.exports = Vue.component('environment-item', {
</span>
</td>
<td class="hidden-xs">
<div v-if="!model.isFolder">
<div class="btn-group" role="group">
<actions-component v-if="hasManualActions && canCreateDeployment"
:actions="manualActions"/>
<td class="hidden-xs environments-actions">
<div v-if="!model.isFolder" class="btn-group" role="group">
<actions-component v-if="hasManualActions && canCreateDeployment"
:actions="manualActions"/>
<external-url-component v-if="externalURL && canReadEnvironment"
:external-url="externalURL"/>
<external-url-component v-if="externalURL && canReadEnvironment"
:external-url="externalURL"/>
<stop-component v-if="hasStopAction && canCreateDeployment"
:stop-url="model.stop_path"/>
<stop-component v-if="hasStopAction && canCreateDeployment"
:stop-url="model.stop_path"/>
<terminal-button-component v-if="model && model.terminal_path"
:terminal-path="model.terminal_path"/>
<terminal-button-component v-if="model && model.terminal_path"
:terminal-path="model.terminal_path"/>
<rollback-component v-if="canRetry && canCreateDeployment"
:is-last-deployment="isLastDeployment"
:retry-url="retryUrl"/>
</div>
<rollback-component v-if="canRetry && canCreateDeployment"
:is-last-deployment="isLastDeployment"
:retry-url="retryUrl"/>
</div>
</td>
</tr>
......
......@@ -48,7 +48,11 @@
}
setOffset(offset = 0) {
this.dropdown.style.left = `${offset}px`;
if (window.innerWidth > 480) {
this.dropdown.style.left = `${offset}px`;
} else {
this.dropdown.style.left = '0px';
}
}
renderContent(forceShowList = false) {
......
......@@ -63,7 +63,7 @@
}
GitLabDropdownFilter.prototype.shouldBlur = function(keyCode) {
return BLUR_KEYCODES.indexOf(keyCode) >= 0;
return BLUR_KEYCODES.indexOf(keyCode) !== -1;
};
GitLabDropdownFilter.prototype.filter = function(search_text) {
......@@ -605,7 +605,7 @@
var occurrences;
occurrences = fuzzaldrinPlus.match(text, term);
return text.split('').map(function(character, i) {
if (indexOf.call(occurrences, i) >= 0) {
if (indexOf.call(occurrences, i) !== -1) {
return "<b>" + character + "</b>";
} else {
return character;
......@@ -748,7 +748,7 @@
return function(e) {
var $listItems, PREV_INDEX, currentKeyCode;
currentKeyCode = e.which;
if (ARROW_KEY_CODES.indexOf(currentKeyCode) >= 0) {
if (ARROW_KEY_CODES.indexOf(currentKeyCode) !== -1) {
e.preventDefault();
e.stopImmediatePropagation();
PREV_INDEX = currentIndex;
......
......@@ -116,7 +116,7 @@
formData = $.param(formData);
formAction = form.attr('action');
issuesUrl = formAction;
issuesUrl += "" + (formAction.indexOf('?') < 0 ? '?' : '&');
issuesUrl += "" + (formAction.indexOf('?') === -1 ? '?' : '&');
issuesUrl += formData;
return gl.utils.visitUrl(issuesUrl);
};
......
......@@ -83,7 +83,7 @@ require('./smart_interval');
return function() {
var page;
page = $('body').data('page').split(':').last();
if (allowedPages.indexOf(page) < 0) {
if (allowedPages.indexOf(page) === -1) {
return _this.clearEventListeners();
}
};
......@@ -233,7 +233,7 @@ require('./smart_interval');
}
$('.ci_widget').hide();
allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"];
if (indexOf.call(allowed_states, state) >= 0) {
if (indexOf.call(allowed_states, state) !== -1) {
$('.ci_widget.ci-' + state).show();
switch (state) {
case "failed":
......
......@@ -81,7 +81,7 @@
var errorMessage, errors, formatter, unique, validator;
this.branchNameError.empty();
unique = function(values, value) {
if (indexOf.call(values, value) < 0) {
if (indexOf.call(values, value) === -1) {
values.push(value);
}
return values;
......
......@@ -116,7 +116,7 @@
if ($('input[name="ref"]').length) {
var $form = $dropdown.closest('form');
var action = $form.attr('action');
var divider = action.indexOf('?') < 0 ? '?' : '&';
var divider = action.indexOf('?') === -1 ? '?' : '&';
gl.utils.visitUrl(action + '' + divider + '' + $form.serialize());
}
}
......
......@@ -26,6 +26,11 @@
.filtered-search-container {
display: -webkit-flex;
display: flex;
@media (max-width: $screen-xs-min) {
-webkit-flex-direction: column;
flex-direction: column;
}
}
.filtered-search-input-container {
......@@ -34,6 +39,20 @@
position: relative;
width: 100%;
@media (max-width: $screen-xs-min) {
-webkit-flex: 1 1 100%;
flex: 1 1 100%;
margin-bottom: 10px;
.dropdown-menu {
width: auto;
left: 0;
right: 0;
max-width: none;
min-width: 100%;
}
}
.form-control {
padding-left: 25px;
padding-right: 25px;
......@@ -79,6 +98,31 @@
overflow: auto;
}
@media (max-width: $screen-xs-min) {
.issues-details-filters {
padding: 0 0 10px;
background-color: $white-light;
border-top: 0;
}
.filter-dropdown-container {
.dropdown-toggle,
.dropdown {
width: 100%;
}
.dropdown {
margin-left: 0;
}
.fa-chevron-down {
position: absolute;
right: 10px;
top: 10px;
}
}
}
%filter-dropdown-item-btn-hover {
background-color: $dropdown-hover-color;
color: $white-light;
......@@ -148,4 +192,4 @@
.filter-dropdown-loading {
padding: 8px 16px;
}
}
\ No newline at end of file
......@@ -128,6 +128,10 @@
padding: 10px 8px;
}
td.environments-actions {
padding-right: 0;
}
td.stage-cell {
padding: 10px 0;
}
......
......@@ -15,4 +15,11 @@ module BuildsHelper
log_state: @build.trace_with_state[:state].to_s
}
end
def build_failed_issue_options
{
title: "Build Failed ##{@build.id}",
description: namespace_project_build_url(@project.namespace, @project, @build)
}
end
end
......@@ -27,10 +27,6 @@ class ArtifactUploader < GitlabUploader
File.join(self.class.artifacts_cache_path, @build.artifacts_path)
end
def file_storage?
self.class.storage == CarrierWave::Storage::File
end
def filename
file.try(:filename)
end
......
......@@ -4,6 +4,6 @@ class AttachmentUploader < GitlabUploader
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
"#{base_dir}/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
......@@ -4,7 +4,7 @@ class AvatarUploader < GitlabUploader
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
"#{base_dir}/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def exists?
......
......@@ -4,15 +4,12 @@ class FileUploader < GitlabUploader
storage :file
attr_accessor :project, :secret
attr_accessor :project
attr_reader :secret
def initialize(project, secret = nil)
@project = project
@secret = secret || self.class.generate_secret
end
def base_dir
"uploads"
@secret = secret || generate_secret
end
def store_dir
......@@ -23,10 +20,6 @@ class FileUploader < GitlabUploader
File.join(base_dir, 'tmp', @project.path_with_namespace, @secret)
end
def secure_url
File.join("/uploads", @secret, file.filename)
end
def to_markdown
to_h[:markdown]
end
......@@ -35,17 +28,23 @@ class FileUploader < GitlabUploader
filename = image_or_video? ? self.file.basename : self.file.filename
escaped_filename = filename.gsub("]", "\\]")
markdown = "[#{escaped_filename}](#{self.secure_url})"
markdown = "[#{escaped_filename}](#{secure_url})"
markdown.prepend("!") if image_or_video? || dangerous?
{
alt: filename,
url: self.secure_url,
url: secure_url,
markdown: markdown
}
end
def self.generate_secret
private
def generate_secret
SecureRandom.hex
end
def secure_url
File.join('/uploads', @secret, file.filename)
end
end
class GitlabUploader < CarrierWave::Uploader::Base
def self.base_dir
'uploads'
end
delegate :base_dir, to: :class
def file_storage?
self.class.storage == CarrierWave::Storage::File
end
# Reduce disk IO
def move_to_cache
true
......
......@@ -27,6 +27,8 @@ module UploaderHelper
extension_match?(DANGEROUS_EXT)
end
private
def extension_match?(extensions)
return false unless file
......@@ -40,8 +42,4 @@ module UploaderHelper
extensions.include?(extension.downcase)
end
def file_storage?
self.class.storage == CarrierWave::Storage::File
end
end
.content-block.build-header
.content-block.build-header.top-area
.header-content
= render 'ci/status/badge', status: @build.detailed_status(current_user), link: false
Job
......@@ -16,7 +16,10 @@
- if @build.user
= render "user"
= time_ago_with_tooltip(@build.created_at)
- if can?(current_user, :update_build, @build) && @build.retryable?
= link_to "Retry job", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary pull-right', method: :post
.nav-controls
- if can?(current_user, :create_issue, @project) && @build.failed?
= link_to "New issue", new_namespace_project_issue_path(@project.namespace, @project, issue: build_failed_issue_options), class: 'btn btn-new btn-inverted'
- if can?(current_user, :update_build, @build) && @build.retryable?
= link_to "Retry job", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary', method: :post
%button.btn.btn-default.pull-right.visible-xs-block.visible-sm-block.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
......@@ -82,7 +82,7 @@
%span.dropdown-label-box{ style: 'background: {{color}}' }
%span.label-title.js-data-value
{{title}}
.pull-right
.pull-right.filter-dropdown-container
= render 'shared/sort_dropdown'
- if @bulk_edit
......
---
title: Add button to create issue for failing build
merge_request: 9391
author: Alex Sanford
---
title: Enhanced filter issues layout for better mobile experiance
merge_request: 9280
author: Pratik Borsadiya
---
title: Replace setInterval with setTimeout to prevent highly frequent requests
merge_request: 9271
author: Takuya Noguchi
---
title: Keep consistent in handling indexOf results
merge_request: 9531
author: Takuya Noguchi
---
title: Fix MR changes tab size count when there are over 100 files in the diff
merge_request:
author:
require './spec/support/sidekiq'
def create_group_with_parents(user, full_path)
parent_path = nil
group = nil
until full_path.blank?
path, _, full_path = full_path.partition('/')
if parent_path
parent = Group.find_by_full_path(parent_path)
parent_path += '/'
parent_path += path
group = Groups::CreateService.new(user, path: path, parent_id: parent.id).execute
else
parent_path = path
group = Group.find_by_full_path(parent_path) ||
Groups::CreateService.new(user, path: path).execute
end
end
group
end
Sidekiq::Testing.inline! do
Gitlab::Seeder.quiet do
project_urls = [
'https://android.googlesource.com/platform/hardware/broadcom/libbt.git',
'https://android.googlesource.com/platform/hardware/broadcom/wlan.git',
'https://android.googlesource.com/platform/hardware/bsp/bootloader/intel/edison-u-boot.git',
'https://android.googlesource.com/platform/hardware/bsp/broadcom.git',
'https://android.googlesource.com/platform/hardware/bsp/freescale.git',
'https://android.googlesource.com/platform/hardware/bsp/imagination.git',
'https://android.googlesource.com/platform/hardware/bsp/intel.git',
'https://android.googlesource.com/platform/hardware/bsp/kernel/common/v4.1.git',
'https://android.googlesource.com/platform/hardware/bsp/kernel/common/v4.4.git'
]
user = User.admins.first
project_urls.each_with_index do |url, i|
full_path = url.sub('https://android.googlesource.com/', '')
full_path = full_path.sub(/\.git\z/, '')
full_path, _, project_path = full_path.rpartition('/')
group = Group.find_by_full_path(full_path) || create_group_with_parents(user, full_path)
params = {
import_url: url,
namespace_id: group.id,
path: project_path,
name: project_path,
description: FFaker::Lorem.sentence,
visibility_level: Gitlab::VisibilityLevel.values.sample
}
project = Projects::CreateService.new(user, params).execute
project.send(:_run_after_commit_queue)
if project.valid?
print '.'
else
print 'F'
end
end
end
end
class MigrateUsersNotificationLevel < ActiveRecord::Migration
DOWNTIME = false
# Migrates only users who changed their default notification level :participating
# creating a new record on notification settings table
......
......@@ -8,11 +8,6 @@ module Banzai
# of the anchor, and then replace the img with the link-wrapped version.
def call
doc.xpath('descendant-or-self::img[not(ancestor::a)]').each do |img|
div = doc.document.create_element(
'div',
class: 'image-container'
)
link = doc.document.create_element(
'a',
class: 'no-attachment-icon',
......@@ -22,9 +17,7 @@ module Banzai
link.children = img.clone
div.children = link
img.replace(div)
img.replace(link)
end
doc
......
......@@ -8,16 +8,16 @@ module Gitlab
@proxy_host = opts.fetch(:proxy_host, 'localhost')
@proxy_port = opts.fetch(:proxy_port, 3808)
@proxy_path = opts[:proxy_path] if opts[:proxy_path]
super(app, opts)
super(app, backend: "http://#{@proxy_host}:#{@proxy_port}", **opts)
end
def perform_request(env)
unless @proxy_path && env['PATH_INFO'].start_with?("/#{@proxy_path}")
return @app.call(env)
if @proxy_path && env['PATH_INFO'].start_with?("/#{@proxy_path}")
super(env)
else
@app.call(env)
end
env['HTTP_HOST'] = "#{@proxy_host}:#{@proxy_port}"
super(env)
end
end
end
......
......@@ -61,7 +61,7 @@ describe "User Feed", feature: true do
end
it 'has XHTML summaries in merge request descriptions' do
expect(body).to match /Here is the fix: <\/p><div[^>]*><a[^>]*><img[^>]*\/><\/a><\/div>/
expect(body).to match /Here is the fix: <a[^>]*><img[^>]*\/><\/a>/
end
end
end
......
require 'spec_helper'
describe 'Issues', feature: true do
include DropzoneHelper
include IssueHelpers
include SortingHelper
include WaitForAjax
......@@ -570,19 +571,13 @@ describe 'Issues', feature: true do
end
it 'uploads file when dragging into textarea' do
drop_in_dropzone test_image_file
# Wait for the file to upload
sleep 1
dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
expect(page.find_field("issue_description").value).to have_content 'banana_sample'
end
it 'adds double newline to end of attachment markdown' do
drop_in_dropzone test_image_file
# Wait for the file to upload
sleep 1
dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
expect(page.find_field("issue_description").value).to match /\n\n$/
end
......@@ -665,25 +660,4 @@ describe 'Issues', feature: true do
end
end
end
def drop_in_dropzone(file_path)
# Generate a fake input selector
page.execute_script <<-JS
var fakeFileInput = window.$('<input/>').attr(
{id: 'fakeFileInput', type: 'file'}
).appendTo('body');
JS
# Attach the file to the fake input selector with Capybara
attach_file("fakeFileInput", file_path)
# Add the file to a fileList array and trigger the fake drop event
page.execute_script <<-JS
var fileList = [$('#fakeFileInput')[0].files[0]];
var e = jQuery.Event('drop', { dataTransfer : { files : fileList } });
$('.div-dropzone')[0].dropzone.listeners[0].events.drop(e);
JS
end
def test_image_file
File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
end
end
require 'rails_helper'
feature 'User uploads avatar to group', feature: true do
scenario 'they see the new avatar' do
user = create(:user)
group = create(:group)
group.add_owner(user)
login_as(user)
visit edit_group_path(group)
attach_file(
'group_avatar',
Rails.root.join('spec', 'fixtures', 'dk.png'),
visible: false
)
click_button 'Save group'
visit group_path(group)
expect(page).to have_selector(%Q(img[src$="/uploads/group/avatar/#{group.id}/dk.png"]))
# Cheating here to verify something that isn't user-facing, but is important
expect(group.reload.avatar.file).to exist
end
end
require 'rails_helper'
feature 'User uploads avatar to profile', feature: true do
scenario 'they see their new avatar' do
user = create(:user)
login_as(user)
visit profile_path
attach_file(
'user_avatar',
Rails.root.join('spec', 'fixtures', 'dk.png'),
visible: false
)
click_button 'Update profile settings'
visit user_path(user)
expect(page).to have_selector(%Q(img[src$="/uploads/user/avatar/#{user.id}/dk.png"]))
# Cheating here to verify something that isn't user-facing, but is important
expect(user.reload.avatar.file).to exist
end
end
require 'rails_helper'
feature 'User uploads file to note', feature: true do
include DropzoneHelper
let(:user) { create(:user) }
let(:project) { create(:empty_project, creator: user, namespace: user.namespace) }
scenario 'they see the attached file', js: true do
issue = create(:issue, project: project, author: user)
login_as(user)
visit namespace_project_issue_path(project.namespace, project, issue)
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
......@@ -13,8 +13,8 @@ describe Banzai::Filter::ImageLinkFilter, lib: true do
end
it 'does not wrap a duplicate link' do
exp = act = %q(<a href="/whatever">#{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')}</a>)
expect(filter(act).to_html).to eq exp
doc = filter(%Q(<a href="/whatever">#{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')}</a>))
expect(doc.to_html).to match /^<a href="\/whatever"><img[^>]*><\/a>$/
end
it 'works with external images' do
......@@ -22,8 +22,8 @@ describe Banzai::Filter::ImageLinkFilter, lib: true do
expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href']
end
it 'wraps the image with a link and a div' do
doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
expect(doc.to_html).to include('<div class="image-container">')
it 'works with inline images' do
doc = filter(%Q(<p>test #{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')} inline</p>))
expect(doc.to_html).to match /^<p>test <a[^>]*><img[^>]*><\/a> inline<\/p>$/
end
end
module DropzoneHelper
# Provides a way to perform `attach_file` for a Dropzone-based file input
#
# This is accomplished by creating a standard HTML file input on the page,
# performing `attach_file` on that field, and then triggering the appropriate
# Dropzone events to perform the actual upload.
#
# This method waits for the upload to complete before returning.
def dropzone_file(file_path)
# Generate a fake file input that Capybara can attach to
page.execute_script <<-JS.strip_heredoc
var fakeFileInput = window.$('<input/>').attr(
{id: 'fakeFileInput', type: 'file'}
).appendTo('body');
window._dropzoneComplete = false;
JS
# Attach the file to the fake input selector with Capybara
attach_file('fakeFileInput', file_path)
# 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.on('queuecomplete', function() {
window._dropzoneComplete = true;
});
dropzone.listeners[0].events.drop(e);
JS
# Wait until Dropzone's fired `queuecomplete`
loop until page.evaluate_script('window._dropzoneComplete === true')
end
end
......@@ -33,7 +33,7 @@ shared_examples 'update invalid issuable' do |klass|
end
it 'renders json error message when format is json' do
params.merge!(format: "json")
params[:format] = "json"
put :update, params
......
require 'spec_helper'
describe AttachmentUploader do
let(:issue) { build(:issue) }
subject { described_class.new(issue) }
let(:uploader) { described_class.new(build_stubbed(:user)) }
describe '#move_to_cache' do
it 'is true' do
expect(subject.move_to_cache).to eq(true)
expect(uploader.move_to_cache).to eq(true)
end
end
describe '#move_to_store' do
it 'is true' do
expect(subject.move_to_store).to eq(true)
expect(uploader.move_to_store).to eq(true)
end
end
end
require 'spec_helper'
describe AvatarUploader do
let(:user) { build(:user) }
subject { described_class.new(user) }
let(:uploader) { described_class.new(build_stubbed(:user)) }
describe '#move_to_cache' do
it 'is false' do
expect(subject.move_to_cache).to eq(false)
expect(uploader.move_to_cache).to eq(false)
end
end
describe '#move_to_store' do
it 'is false' do
expect(subject.move_to_store).to eq(false)
expect(uploader.move_to_store).to eq(false)
end
end
end
require 'spec_helper'
describe FileUploader do
let(:project) { create(:project) }
let(:uploader) { described_class.new(build_stubbed(:project)) }
before do
@previous_enable_processing = FileUploader.enable_processing
FileUploader.enable_processing = false
@uploader = FileUploader.new(project)
end
after do
FileUploader.enable_processing = @previous_enable_processing
@uploader.remove!
end
describe 'initialize' do
it 'generates a secret if none is provided' do
expect(SecureRandom).to receive(:hex).and_return('secret')
describe '#image_or_video?' do
context 'given an image file' do
before do
@uploader.store!(fixture_file_upload(Rails.root.join('spec', 'fixtures', 'rails_sample.jpg')))
end
uploader = described_class.new(double)
it 'detects an image based on file extension' do
expect(@uploader.image_or_video?).to be true
end
expect(uploader.secret).to eq 'secret'
end
context 'given an video file' do
before do
video_file = fixture_file_upload(Rails.root.join('spec', 'fixtures', 'video_sample.mp4'))
@uploader.store!(video_file)
end
it 'detects a video based on file extension' do
expect(@uploader.image_or_video?).to be true
end
end
it 'accepts a secret parameter' do
expect(SecureRandom).not_to receive(:hex)
it 'does not return image_or_video? for other types' do
@uploader.store!(fixture_file_upload(Rails.root.join('spec', 'fixtures', 'doc_sample.txt')))
uploader = described_class.new(double, 'secret')
expect(@uploader.image_or_video?).to be false
expect(uploader.secret).to eq 'secret'
end
end
describe '#move_to_cache' do
it 'is true' do
expect(@uploader.move_to_cache).to eq(true)
expect(uploader.move_to_cache).to eq(true)
end
end
describe '#move_to_store' do
it 'is true' do
expect(@uploader.move_to_store).to eq(true)
expect(uploader.move_to_store).to eq(true)
end
end
end
require 'rails_helper'
describe UploaderHelper do
class ExampleUploader < CarrierWave::Uploader::Base
include UploaderHelper
storage :file
end
def upload_fixture(filename)
fixture_file_upload(Rails.root.join('spec', 'fixtures', filename))
end
describe '#image_or_video?' do
let(:uploader) { ExampleUploader.new }
it 'returns true for an image file' do
uploader.store!(upload_fixture('dk.png'))
expect(uploader).to be_image_or_video
end
it 'it returns true for a video file' do
uploader.store!(upload_fixture('video_sample.mp4'))
expect(uploader).to be_image_or_video
end
it 'returns false for other extensions' do
uploader.store!(upload_fixture('doc_sample.txt'))
expect(uploader).not_to be_image_or_video
end
end
end
......@@ -209,6 +209,10 @@ describe 'projects/builds/show', :view do
it 'does not show retry button' do
expect(rendered).not_to have_link('Retry')
end
it 'does not show New issue button' do
expect(rendered).not_to have_link('New issue')
end
end
context 'when job is not running' do
......@@ -220,6 +224,23 @@ describe 'projects/builds/show', :view do
it 'shows retry button' do
expect(rendered).to have_link('Retry')
end
context 'if build passed' do
it 'does not show New issue button' do
expect(rendered).not_to have_link('New issue')
end
end
context 'if build failed' do
before do
build.status = 'failed'
render
end
it 'shows New issue button' do
expect(rendered).to have_link('New issue')
end
end
end
describe 'commit title in sidebar' do
......@@ -248,4 +269,25 @@ describe 'projects/builds/show', :view do
expect(rendered).to have_css('.js-build-value', visible: false, text: 'TRIGGER_VALUE_2')
end
end
describe 'New issue button' do
before do
build.status = 'failed'
render
end
it 'links to issues/new with the title and description filled in' do
title = "Build Failed ##{build.id}"
build_url = namespace_project_build_url(project.namespace, project, build)
href = new_namespace_project_issue_path(
project.namespace,
project,
issue: {
title: title,
description: build_url
}
)
expect(rendered).to have_link('New issue', href: href)
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment