Skip to content
Snippets Groups Projects
Commit c792263e authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 6f9edd1a
No related branches found
No related tags found
No related merge requests found
Showing
with 385 additions and 404 deletions
Loading
Loading
@@ -87,7 +87,7 @@ $ cat -- -l
hello
```
 
In the GitLab codebase, we avoid the option/argument ambiguity by _always_ using `--`.
In the GitLab codebase, we avoid the option/argument ambiguity by _always_ using `--` for commands that support it.
 
```ruby
# Wrong
Loading
Loading
Loading
Loading
@@ -64,8 +64,8 @@ The following quick actions are applicable to descriptions, discussions and thre
| `/create_merge_request <branch name>` | ✓ | | | Create a new merge request starting from the current issue |
| `/relate #issue1 #issue2` | ✓ | | | Mark issues as related **(STARTER)** |
| `/move <path/to/project>` | ✓ | | | Move this issue to another project |
| `/zoom <Zoom URL>` | ✓ | | | Add Zoom meeting to this issue. ([Introduced in GitLab 12.3](https://gitlab.com/gitlab-org/gitlab/merge_requests/16609) enabled by feature flag `issue_zoom_integration`) |
| `/remove_zoom` | ✓ | | | Remove Zoom meeting from this issue. ([Introduced in GitLab 12.3](https://gitlab.com/gitlab-org/gitlab/merge_requests/16609) enabled by feature flag `issue_zoom_integration`) |
| `/zoom <Zoom URL>` | ✓ | | | Add Zoom meeting to this issue. ([Introduced in GitLab 12.3](https://gitlab.com/gitlab-org/gitlab/merge_requests/16609). Must be enabled by feature flag `issue_zoom_integration` for self-hosted. Feature flag to be removed and available by default in 12.4.) |
| `/remove_zoom` | ✓ | | | Remove Zoom meeting from this issue. ([Introduced in GitLab 12.3](https://gitlab.com/gitlab-org/gitlab/merge_requests/16609). Must be enabled by feature flag `issue_zoom_integration` for self-hosted. Feature flag to be removed and available by default in 12.4.) |
| `/target_branch <local branch name>` | | ✓ | | Set target branch |
| `/wip` | | ✓ | | Toggle the Work In Progress status |
| `/approve` | | ✓ | | Approve the merge request |
Loading
Loading
Loading
Loading
@@ -1276,7 +1276,7 @@ module API
 
class Release < Grape::Entity
expose :name
expose :tag, as: :tag_name, if: lambda { |_, _| can_download_code? }
expose :tag, as: :tag_name, if: ->(_, _) { can_download_code? }
expose :description
expose :description_html do |entity|
MarkupHelper.markdown_field(entity, :description)
Loading
Loading
@@ -1284,16 +1284,17 @@ module API
expose :created_at
expose :released_at
expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? }
expose :commit, using: Entities::Commit, if: lambda { |_, _| can_download_code? }
expose :commit, using: Entities::Commit, if: ->(_, _) { can_download_code? }
expose :upcoming_release?, as: :upcoming_release
expose :milestones, using: Entities::Milestone, if: -> (release, _) { release.milestones.present? }
expose :commit_path, if: ->(_, _) { can_download_code? }
expose :tag_path, if: ->(_, _) { can_download_code? }
expose :assets do
expose :assets_count, as: :count do |release, _|
assets_to_exclude = can_download_code? ? [] : [:sources]
release.assets_count(except: assets_to_exclude)
end
expose :sources, using: Entities::Releases::Source, if: lambda { |_, _| can_download_code? }
expose :sources, using: Entities::Releases::Source, if: ->(_, _) { can_download_code? }
expose :links, using: Entities::Releases::Link do |release, options|
release.links.sorted
end
Loading
Loading
@@ -1304,6 +1305,16 @@ module API
def can_download_code?
Ability.allowed?(options[:current_user], :download_code, object.project)
end
def commit_path
return unless object.commit
Gitlab::Routing.url_helpers.project_commit_path(object.project, object.commit.id)
end
def tag_path
Gitlab::Routing.url_helpers.project_tag_path(object.project, object.tag)
end
end
 
class Tag < Grape::Entity
Loading
Loading
Loading
Loading
@@ -127,7 +127,7 @@ module Backup
end
 
tar_file = if ENV['BACKUP'].present?
"#{ENV['BACKUP']}#{FILE_NAME_SUFFIX}"
File.basename(ENV['BACKUP']) + FILE_NAME_SUFFIX
else
backup_file_list.first
end
Loading
Loading
@@ -235,8 +235,8 @@ module Backup
end
 
def tar_file
@tar_file ||= if ENV['BACKUP']
ENV['BACKUP'] + "#{FILE_NAME_SUFFIX}"
@tar_file ||= if ENV['BACKUP'].present?
File.basename(ENV['BACKUP']) + FILE_NAME_SUFFIX
else
"#{backup_information[:backup_created_at].strftime('%s_%Y_%m_%d_')}#{backup_information[:gitlab_version]}#{FILE_NAME_SUFFIX}"
end
Loading
Loading
Loading
Loading
@@ -19,13 +19,13 @@ module Banzai
 
def query
@query ||= begin
src_query = UploaderHelper::VIDEO_EXT.map do |ext|
src_query = UploaderHelper::SAFE_VIDEO_EXT.map do |ext|
"'.#{ext}' = substring(@src, string-length(@src) - #{ext.size})"
end
 
if context[:asset_proxy_enabled].present?
src_query.concat(
UploaderHelper::VIDEO_EXT.map do |ext|
UploaderHelper::SAFE_VIDEO_EXT.map do |ext|
"'.#{ext}' = substring(@data-canonical-src, string-length(@data-canonical-src) - #{ext.size})"
end
)
Loading
Loading
Loading
Loading
@@ -118,8 +118,14 @@ module Gitlab
path: file_path
}
 
# Takes action when creating diff notes (multiple calls are
# submitted to this method).
Gitlab::SafeRequestStore.fetch(key) { find_diff_file(repository) }
end
# We need to unfold diff lines according to the position in order
# to correctly calculate the line code and trace position changes.
@diff_file&.tap { |file| file.unfold_diff_lines(self) }
end
 
def diff_options
Loading
Loading
@@ -152,13 +158,7 @@ module Gitlab
return unless diff_refs.complete?
return unless comparison = diff_refs.compare_in(repository.project)
 
file = comparison.diffs(diff_options).diff_files.first
# We need to unfold diff lines according to the position in order
# to correctly calculate the line code and trace position changes.
file&.unfold_diff_lines(self)
file
comparison.diffs(diff_options).diff_files.first
end
 
def get_formatter_class(type)
Loading
Loading
Loading
Loading
@@ -10,7 +10,7 @@ module Gitlab
return unless name = markdown_name
 
markdown = "[#{name.gsub(']', '\\]')}](#{secure_url})"
markdown = "!#{markdown}" if image_or_video? || dangerous?
markdown = "!#{markdown}" if image_or_video? || dangerous_image_or_video?
markdown
end
 
Loading
Loading
# frozen_string_literal: true
 
# File helpers methods.
# It needs the method filename to be defined.
# The method `filename` must be defined in classes that use this module.
#
# This module is intended to be used as a helper and not a security gate
# to validate that a file is safe, as it identifies files only by the
# file extension and not its actual contents.
#
# An example useage of this module is in `FileMarkdownLinkBuilder` that
# renders markdown depending on a file name.
#
# We use Workhorse to detect the real extension when we serve files with
# the `SendsBlob` helper methods, and ask Workhorse to set the content
# type when it serves the file:
# https://gitlab.com/gitlab-org/gitlab-ce/blob/33e5955/app/helpers/workhorse_helper.rb#L48.
#
# Because Workhorse has access to the content when it is downloaded, if
# the type/extension doesn't match the real type, we adjust the
# `Content-Type` and `Content-Disposition` to the one we get from the detection.
module Gitlab
module FileTypeDetection
IMAGE_EXT = %w[png jpg jpeg gif bmp tiff ico].freeze
SAFE_IMAGE_EXT = %w[png jpg jpeg gif bmp tiff ico].freeze
# We recommend using the .mp4 format over .mov. Videos in .mov format can
# still be used but you really need to make sure they are served with the
# proper MIME type video/mp4 and not video/quicktime or your videos won't play
# on IE >= 9.
# http://archive.sublimevideo.info/20150912/docs.sublimevideo.net/troubleshooting.html
VIDEO_EXT = %w[mp4 m4v mov webm ogv].freeze
SAFE_VIDEO_EXT = %w[mp4 m4v mov webm ogv].freeze
# These extension types can contain dangerous code and should only be embedded inline with
# proper filtering. They should always be tagged as "Content-Disposition: attachment", not "inline".
DANGEROUS_EXT = %w[svg].freeze
DANGEROUS_IMAGE_EXT = %w[svg].freeze
DANGEROUS_VIDEO_EXT = [].freeze # None, yet
 
def image?
extension_match?(IMAGE_EXT)
extension_match?(SAFE_IMAGE_EXT)
end
 
def video?
extension_match?(VIDEO_EXT)
extension_match?(SAFE_VIDEO_EXT)
end
 
def image_or_video?
image? || video?
end
 
def dangerous?
extension_match?(DANGEROUS_EXT)
def dangerous_image?
extension_match?(DANGEROUS_IMAGE_EXT)
end
def dangerous_video?
extension_match?(DANGEROUS_VIDEO_EXT)
end
def dangerous_image_or_video?
dangerous_image? || dangerous_video?
end
 
private
Loading
Loading
Loading
Loading
@@ -17,7 +17,6 @@ module Gitlab
.merge(features_usage_data)
.merge(components_usage_data)
.merge(cycle_analytics_usage_data)
.merge(usage_counters)
end
 
def to_json(force_refresh: false)
Loading
Loading
@@ -99,6 +98,7 @@ module Gitlab
web_hooks: count(WebHook)
}.merge(services_usage)
.merge(approximate_counts)
.merge(usage_counters)
}.tap do |data|
data[:counts][:user_preferences] = user_preferences_usage
end
Loading
Loading
Loading
Loading
@@ -15647,6 +15647,9 @@ msgstr ""
msgid "There are no labels yet"
msgstr ""
 
msgid "There are no matching files"
msgstr ""
msgid "There are no open issues"
msgstr ""
 
Loading
Loading
@@ -16569,6 +16572,9 @@ msgstr ""
msgid "Try to fork again"
msgstr ""
 
msgid "Try using a different search term to find the file you are looking for."
msgstr ""
msgid "Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now."
msgstr ""
 
Loading
Loading
Loading
Loading
@@ -14,6 +14,10 @@ describe 'User comments on a diff', :js do
expect(suggested_content).to eq(expected_suggested_content)
end
 
def expect_appliable_suggestions(amount)
expect(all('button', text: 'Apply suggestion').size).to eq(amount)
end
let(:project) { create(:project, :repository) }
let(:merge_request) do
create(:merge_request_with_diffs, source_project: project, target_project: project, source_branch: 'merge-test')
Loading
Loading
@@ -89,6 +93,60 @@ describe 'User comments on a diff', :js do
end
end
 
context 'multiple suggestions in expanded lines' do
it 'suggestions are appliable' do
diff_file = merge_request.diffs(paths: ['files/ruby/popen.rb']).diff_files.first
hash = Digest::SHA1.hexdigest(diff_file.file_path)
expanded_changes = [
{
line_code: "#{hash}_1_1",
file_path: diff_file.file_path
},
{
line_code: "#{hash}_5_5",
file_path: diff_file.file_path
}
]
changes = sample_compare(expanded_changes).changes.last(expanded_changes.size)
page.within("[id='#{hash}']") do
find("button[data-original-title='Show full file']").click
wait_for_requests
click_diff_line(find("[id='#{changes.first[:line_code]}']"))
page.within('.js-discussion-note-form') do
fill_in('note_note', with: "```suggestion\n# change to a comment\n```")
click_button('Comment')
wait_for_requests
end
click_diff_line(find("[id='#{changes.last[:line_code]}']"))
page.within('.js-discussion-note-form') do
fill_in('note_note', with: "```suggestion\n# 2nd change to a comment\n```")
click_button('Comment')
wait_for_requests
end
expect_appliable_suggestions(2)
end
# Making sure it's not a Front-end cache.
visit(diffs_project_merge_request_path(project, merge_request))
expect_appliable_suggestions(2)
page.within("[id='#{hash}']") do
all('button', text: 'Apply suggestion').last.click
wait_for_requests
expect(page).to have_content('Applied')
end
end
end
context 'multiple suggestions in a single note' do
it 'suggestions are presented' do
click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']"))
Loading
Loading
Loading
Loading
@@ -19,6 +19,9 @@
"type": "array",
"items": { "$ref": "milestone.json" }
},
"commit_path": { "type": "string" },
"tag_path": { "type": "string" },
"name": { "type": "string" },
"assets": {
"required": ["count", "links", "sources"],
"properties": {
Loading
Loading
Loading
Loading
@@ -8,6 +8,12 @@
"created_at": { "type": "date" },
"released_at": { "type": "date" },
"upcoming_release": { "type": "boolean" },
"milestones": {
"type": "array",
"items": { "$ref": "../milestone.json" }
},
"commit_path": { "type": "string" },
"tag_path": { "type": "string" },
"author": {
"oneOf": [{ "type": "null" }, { "$ref": "../user/basic.json" }]
},
Loading
Loading
import Vuex from 'vuex';
import $ from 'jquery';
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Dropdown from '~/ide/components/file_templates/dropdown.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('IDE file templates dropdown component', () => {
let wrapper;
let element;
let fetchTemplateTypesMock;
const defaultProps = {
label: 'label',
};
const findItemButtons = () => wrapper.findAll('button');
const findSearch = () => wrapper.find('input[type="search"]');
const triggerDropdown = () => $(element).trigger('show.bs.dropdown');
const createComponent = ({ props, state } = {}) => {
fetchTemplateTypesMock = jest.fn();
const fakeStore = new Vuex.Store({
modules: {
fileTemplates: {
namespaced: true,
state: {
templates: [],
isLoading: false,
...state,
},
actions: {
fetchTemplateTypes: fetchTemplateTypesMock,
},
},
},
});
wrapper = shallowMount(Dropdown, {
propsData: {
...defaultProps,
...props,
},
store: fakeStore,
localVue,
sync: false,
});
({ element } = wrapper);
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('calls clickItem on click', () => {
const itemData = { name: 'test.yml ' };
createComponent({ props: { data: [itemData] } });
const item = findItemButtons().at(0);
item.trigger('click');
expect(wrapper.emitted().click[0][0]).toBe(itemData);
});
it('renders dropdown title', () => {
const title = 'Test title';
createComponent({ props: { title } });
expect(wrapper.find('.dropdown-title').text()).toContain(title);
});
describe('in async mode', () => {
const defaultAsyncProps = { ...defaultProps, isAsyncData: true };
it('calls `fetchTemplateTypes` on dropdown event', () => {
createComponent({ props: defaultAsyncProps });
triggerDropdown();
expect(fetchTemplateTypesMock).toHaveBeenCalled();
});
it('does not call `fetchTemplateTypes` on dropdown event if destroyed', () => {
createComponent({ props: defaultAsyncProps });
wrapper.destroy();
triggerDropdown();
expect(fetchTemplateTypesMock).not.toHaveBeenCalled();
});
it('shows loader when isLoading is true', () => {
createComponent({ props: defaultAsyncProps, state: { isLoading: true } });
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
it('renders templates', () => {
const templates = [{ name: 'file-1' }, { name: 'file-2' }];
createComponent({
props: { ...defaultAsyncProps, data: [{ name: 'should-never-appear ' }] },
state: {
templates,
},
});
const items = findItemButtons();
expect(items.wrappers.map(x => x.text())).toEqual(templates.map(x => x.name));
});
it('searches template data', () => {
const templates = [{ name: 'match 1' }, { name: 'other' }, { name: 'match 2' }];
const matches = ['match 1', 'match 2'];
createComponent({
props: { ...defaultAsyncProps, data: matches, searchable: true },
state: { templates },
});
findSearch().setValue('match');
return wrapper.vm.$nextTick().then(() => {
const items = findItemButtons();
expect(items.length).toBe(matches.length);
expect(items.wrappers.map(x => x.text())).toEqual(matches);
});
});
it('does not render input when `searchable` is true & `showLoading` is true', () => {
createComponent({
props: { ...defaultAsyncProps, searchable: true },
state: { isLoading: true },
});
expect(findSearch().exists()).toBe(false);
});
});
describe('in sync mode', () => {
it('renders props data', () => {
const data = [{ name: 'file-1' }, { name: 'file-2' }];
createComponent({
props: { data },
state: {
templates: [{ name: 'should-never-appear ' }],
},
});
const items = findItemButtons();
expect(items.length).toBe(data.length);
expect(items.wrappers.map(x => x.text())).toEqual(data.map(x => x.name));
});
it('renders input when `searchable` is true', () => {
createComponent({ props: { searchable: true } });
expect(findSearch().exists()).toBe(true);
});
it('searches data', () => {
const data = [{ name: 'match 1' }, { name: 'other' }, { name: 'match 2' }];
const matches = ['match 1', 'match 2'];
createComponent({ props: { searchable: true, data } });
findSearch().setValue('match');
return wrapper.vm.$nextTick().then(() => {
const items = findItemButtons();
expect(items.length).toBe(matches.length);
expect(items.wrappers.map(x => x.text())).toEqual(matches);
});
});
});
});
Loading
Loading
@@ -12,7 +12,6 @@ describe('Release block', () => {
propsData: {
release: releaseProp,
},
sync: false,
});
};
 
Loading
Loading
@@ -37,10 +36,16 @@ describe('Release block', () => {
 
it('renders commit sha', () => {
expect(wrapper.text()).toContain(release.commit.short_id);
wrapper.setProps({ release: { ...release, commit_path: '/commit/example' } });
expect(wrapper.find('a[href="/commit/example"]').exists()).toBe(true);
});
 
it('renders tag name', () => {
expect(wrapper.text()).toContain(release.tag_name);
wrapper.setProps({ release: { ...release, tag_path: '/tag/example' } });
expect(wrapper.find('a[href="/tag/example"]').exists()).toBe(true);
});
 
it('renders release date', () => {
Loading
Loading
import $ from 'jquery';
import Vue from 'vue';
import { createStore } from '~/ide/stores';
import Dropdown from '~/ide/components/file_templates/dropdown.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { resetStore } from '../../helpers';
describe('IDE file templates dropdown component', () => {
let Component;
let vm;
beforeAll(() => {
Component = Vue.extend(Dropdown);
});
beforeEach(() => {
const store = createStore();
vm = createComponentWithStore(Component, store, {
label: 'Test',
}).$mount();
});
afterEach(() => {
vm.$destroy();
resetStore(vm.$store);
});
describe('async', () => {
beforeEach(() => {
vm.isAsyncData = true;
});
it('calls async store method on Bootstrap dropdown event', () => {
spyOn(vm, 'fetchTemplateTypes').and.stub();
$(vm.$el).trigger('show.bs.dropdown');
expect(vm.fetchTemplateTypes).toHaveBeenCalled();
});
it('renders templates when async', done => {
vm.$store.state.fileTemplates.templates = [
{
name: 'test',
},
];
vm.$nextTick(() => {
expect(vm.$el.querySelector('.dropdown-content').textContent).toContain('test');
done();
});
});
it('renders loading icon when isLoading is true', done => {
vm.$store.state.fileTemplates.isLoading = true;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
done();
});
});
it('searches template data', () => {
vm.$store.state.fileTemplates.templates = [
{
name: 'test',
},
];
vm.searchable = true;
vm.search = 'hello';
expect(vm.outputData).toEqual([]);
});
it('does not filter data is searchable is false', () => {
vm.$store.state.fileTemplates.templates = [
{
name: 'test',
},
];
vm.search = 'hello';
expect(vm.outputData).toEqual([
{
name: 'test',
},
]);
});
it('calls clickItem on click', done => {
spyOn(vm, 'clickItem').and.stub();
vm.$store.state.fileTemplates.templates = [
{
name: 'test',
},
];
vm.$nextTick(() => {
vm.$el.querySelector('.dropdown-content button').click();
expect(vm.clickItem).toHaveBeenCalledWith({
name: 'test',
});
done();
});
});
it('renders input when searchable is true', done => {
vm.searchable = true;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.dropdown-input')).not.toBe(null);
done();
});
});
it('does not render input when searchable is true & showLoading is true', done => {
vm.searchable = true;
vm.$store.state.fileTemplates.isLoading = true;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.dropdown-input')).toBe(null);
done();
});
});
});
describe('sync', () => {
beforeEach(done => {
vm.data = [
{
name: 'test sync',
},
];
vm.$nextTick(done);
});
it('renders props data', () => {
expect(vm.$el.querySelector('.dropdown-content').textContent).toContain('test sync');
});
it('renders input when searchable is true', done => {
vm.searchable = true;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.dropdown-input')).not.toBe(null);
done();
});
});
it('calls clickItem on click', done => {
spyOn(vm, 'clickItem').and.stub();
vm.$nextTick(() => {
vm.$el.querySelector('.dropdown-content button').click();
expect(vm.clickItem).toHaveBeenCalledWith({
name: 'test sync',
});
done();
});
});
it('searches template data', () => {
vm.searchable = true;
vm.search = 'hello';
expect(vm.outputData).toEqual([]);
});
it('does not filter data is searchable is false', () => {
vm.search = 'hello';
expect(vm.outputData).toEqual([
{
name: 'test sync',
},
]);
});
it('renders dropdown title', done => {
vm.title = 'Test title';
vm.$nextTick(() => {
expect(vm.$el.querySelector('.dropdown-title').textContent).toContain('Test title');
done();
});
});
});
});
import Vue from 'vue';
import component from '~/releases/components/release_block.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Release block', () => {
const Component = Vue.extend(component);
const release = {
name: 'Bionic Beaver',
tag_name: '18.04',
description: '## changelog\n\n* line 1\n* line2',
description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>',
author_name: 'Release bot',
author_email: 'release-bot@example.com',
released_at: '2012-05-28T05:00:00-07:00',
author: {
avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png',
id: 482476,
name: 'John Doe',
path: '/johndoe',
state: 'active',
status_tooltip_html: null,
username: 'johndoe',
web_url: 'https://gitlab.com/johndoe',
},
commit: {
id: '2695effb5807a22ff3d138d593fd856244e155e7',
short_id: '2695effb',
title: 'Initial commit',
created_at: '2017-07-26T11:08:53.000+02:00',
parent_ids: ['2a4b78934375d7f53875269ffd4f45fd83a84ebe'],
message: 'Initial commit',
author_name: 'John Smith',
author_email: 'john@example.com',
authored_date: '2012-05-28T04:42:42-07:00',
committer_name: 'Jack Smith',
committer_email: 'jack@example.com',
committed_date: '2012-05-28T04:42:42-07:00',
},
assets: {
count: 6,
sources: [
{
format: 'zip',
url:
'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.zip',
},
{
format: 'tar.gz',
url:
'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.gz',
},
{
format: 'tar.bz2',
url:
'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.bz2',
},
{
format: 'tar',
url:
'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar',
},
],
links: [
{
name: 'release-18.04.dmg',
url: 'https://my-external-hosting.example.com/scrambled-url/',
external: true,
},
{
name: 'binary-linux-amd64',
url:
'https://gitlab.com/gitlab-org/gitlab-foss/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50',
external: false,
},
],
},
};
let vm;
const factory = props => mountComponent(Component, { release: props });
beforeEach(() => {
vm = factory(release);
});
afterEach(() => {
vm.$destroy();
});
it("renders the block with an id equal to the release's tag name", () => {
expect(vm.$el.id).toBe('18.04');
});
it('renders release name', () => {
expect(vm.$el.textContent).toContain(release.name);
});
it('renders commit sha', () => {
expect(vm.$el.textContent).toContain(release.commit.short_id);
});
it('renders tag name', () => {
expect(vm.$el.textContent).toContain(release.tag_name);
});
it('renders release date', () => {
expect(vm.$el.textContent).toContain(timeagoMixin.methods.timeFormated(release.released_at));
});
it('renders number of assets provided', () => {
expect(vm.$el.querySelector('.js-assets-count').textContent).toContain(release.assets.count);
});
it('renders dropdown with the sources', () => {
expect(vm.$el.querySelectorAll('.js-sources-dropdown li').length).toEqual(
release.assets.sources.length,
);
expect(vm.$el.querySelector('.js-sources-dropdown li a').getAttribute('href')).toEqual(
release.assets.sources[0].url,
);
expect(vm.$el.querySelector('.js-sources-dropdown li a').textContent).toContain(
release.assets.sources[0].format,
);
});
it('renders list with the links provided', () => {
expect(vm.$el.querySelectorAll('.js-assets-list li').length).toEqual(
release.assets.links.length,
);
expect(vm.$el.querySelector('.js-assets-list li a').getAttribute('href')).toEqual(
release.assets.links[0].url,
);
expect(vm.$el.querySelector('.js-assets-list li a').textContent).toContain(
release.assets.links[0].name,
);
});
it('renders author avatar', () => {
expect(vm.$el.querySelector('.user-avatar-link')).not.toBeNull();
});
describe('external label', () => {
it('renders external label when link is external', () => {
expect(vm.$el.querySelector('.js-assets-list li a').textContent).toContain('external source');
});
it('does not render external label when link is not external', () => {
expect(vm.$el.querySelector('.js-assets-list li:nth-child(2) a').textContent).not.toContain(
'external source',
);
});
});
describe('with upcoming_release flag', () => {
beforeEach(() => {
vm = factory(Object.assign({}, release, { upcoming_release: true }));
});
it('renders upcoming release badge', () => {
expect(vm.$el.textContent).toContain('Upcoming Release');
});
});
});
Loading
Loading
@@ -21,6 +21,49 @@ describe Backup::Manager do
$progress = @old_progress # rubocop:disable Style/GlobalVars
end
 
describe '#pack' do
let(:backup_contents) { ['backup_contents'] }
let(:tar_system_options) { { out: [tar_file, 'w', Gitlab.config.backup.archive_permissions] } }
let(:tar_cmdline) { ['tar', '-cf', '-', *backup_contents, tar_system_options] }
let(:backup_information) do
{
backup_created_at: Time.zone.parse('2019-01-01'),
gitlab_version: '12.3'
}
end
before do
allow(ActiveRecord::Base.connection).to receive(:reconnect!)
allow(Kernel).to receive(:system).and_return(true)
allow(subject).to receive(:backup_contents).and_return(backup_contents)
allow(subject).to receive(:backup_information).and_return(backup_information)
allow(subject).to receive(:upload)
end
context 'when BACKUP is not set' do
let(:tar_file) { '1546300800_2019_01_01_12.3_gitlab_backup.tar' }
it 'uses the default tar file name' do
subject.pack
expect(Kernel).to have_received(:system).with(*tar_cmdline)
end
end
context 'when BACKUP is set' do
let(:tar_file) { 'custom_gitlab_backup.tar' }
it 'uses the given value as tar file name' do
stub_env('BACKUP', '/ignored/path/custom')
subject.pack
expect(Kernel).to have_received(:system).with(*tar_cmdline)
end
end
end
describe '#remove_old' do
let(:files) do
[
Loading
Loading
@@ -238,7 +281,7 @@ describe Backup::Manager do
allow(Kernel).to receive(:system).and_return(true)
allow(YAML).to receive(:load_file).and_return(gitlab_version: Gitlab::VERSION)
 
stub_env('BACKUP', '1451606400_2016_01_01_1.2.3')
stub_env('BACKUP', '/ignored/path/1451606400_2016_01_01_1.2.3')
end
 
it 'unpacks the file' do
Loading
Loading
Loading
Loading
@@ -18,7 +18,7 @@ describe Banzai::Filter::VideoLinkFilter do
let(:project) { create(:project, :repository) }
 
context 'when the element src has a video extension' do
UploaderHelper::VIDEO_EXT.each do |ext|
UploaderHelper::SAFE_VIDEO_EXT.each do |ext|
it "replaces the image tag 'path/video.#{ext}' with a video tag" do
container = filter(link_to_image("/path/video.#{ext}")).children.first
 
Loading
Loading
Loading
Loading
@@ -130,6 +130,26 @@ describe Gitlab::Diff::Position do
expect(diff_file.new_path).to eq(subject.new_path)
expect(diff_file.diff_refs).to eq(subject.diff_refs)
end
context 'different folded positions in the same diff file' do
def diff_file(args = {})
described_class
.new(args_for_text.merge(args))
.diff_file(project.repository)
end
it 'expands the diff file', :request_store do
expect_any_instance_of(Gitlab::Diff::File)
.to receive(:unfold_diff_lines).and_call_original
diff_file(old_line: 1, new_line: 1, diff_refs: commit.diff_refs)
expect_any_instance_of(Gitlab::Diff::File)
.to receive(:unfold_diff_lines).and_call_original
diff_file(old_line: 5, new_line: 5, diff_refs: commit.diff_refs)
end
end
end
 
describe "#diff_line" do
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment