Commit 6c21b502 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into qa-add-more-key-tests

* upstream/master: (36 commits)
  Change language to be more inclusive of those with accessibility requirements
  Show group id in group settings
  Cleanup after adding MR diff's commit_count (try 2)
  Mock Pager in Karma tests
  Backport dev env check for storage settings deprecation
  Do not preload settings
  Update import on actions file to fix conflict when rewire plugin was added
  Broken link fix
  Make /copy_metadata only handle the first issuable passed
  Allow admins to push to empty repos
  Only show push-to-master authorized users
  Add documentation about resetting the runner registration token
  Document externally hosted LFS objects
  Align project avatar on small viewports
  fix CI BABEL_ENV variable
  document the spyOnDependency method
  disable sourcemaps when generating coverage report to avoid out-of-memory errors
  only apply rewire plugin when running karma tests
  fix illegal references to "this" in module context
  add default exports to prevent rewire plugin from breaking vuex
  ...
parents 185d278b a5ffb012
{ {
"presets": [["latest", { "es2015": { "modules": false } }], "stage-2"], "presets": [["latest", { "es2015": { "modules": false } }], "stage-2"],
"env": { "env": {
"karma": {
"plugins": ["rewire"]
},
"coverage": { "coverage": {
"plugins": [ "plugins": [
[ [
...@@ -14,7 +17,8 @@ ...@@ -14,7 +17,8 @@
{ {
"process.env.BABEL_ENV": "coverage" "process.env.BABEL_ENV": "coverage"
} }
] ],
"rewire"
] ]
} }
} }
......
...@@ -144,3 +144,6 @@ export * from './actions/tree'; ...@@ -144,3 +144,6 @@ export * from './actions/tree';
export * from './actions/file'; export * from './actions/file';
export * from './actions/project'; export * from './actions/project';
export * from './actions/merge_request'; export * from './actions/merge_request';
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
...@@ -56,3 +56,6 @@ export const allBlobs = state => ...@@ -56,3 +56,6 @@ export const allBlobs = state =>
.sort((a, b) => b.lastOpenedAt - a.lastOpenedAt); .sort((a, b) => b.lastOpenedAt - a.lastOpenedAt);
export const getStagedFile = state => path => state.stagedFiles.find(f => f.path === path); export const getStagedFile = state => path => state.stagedFiles.find(f => f.path === path);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
...@@ -185,3 +185,6 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState }) = ...@@ -185,3 +185,6 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState }) =
commit(types.UPDATE_LOADING, false); commit(types.UPDATE_LOADING, false);
}); });
}; };
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
...@@ -27,3 +27,6 @@ export const branchName = (state, getters, rootState) => { ...@@ -27,3 +27,6 @@ export const branchName = (state, getters, rootState) => {
return rootState.currentBranchId; return rootState.currentBranchId;
}; };
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
...@@ -315,3 +315,6 @@ export const scrollToNoteIfNeeded = (context, el) => { ...@@ -315,3 +315,6 @@ export const scrollToNoteIfNeeded = (context, el) => {
scrollToElement(el); scrollToElement(el);
} }
}; };
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
...@@ -68,3 +68,6 @@ export const resolvedDiscussionCount = (state, getters) => { ...@@ -68,3 +68,6 @@ export const resolvedDiscussionCount = (state, getters) => {
return Object.keys(resolvedMap).length; return Object.keys(resolvedMap).length;
}; };
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
...@@ -35,3 +35,6 @@ export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.destr ...@@ -35,3 +35,6 @@ export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.destr
export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data); export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data);
export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING); export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
export const isLoading = state => state.isLoading; export const isLoading = state => state.isLoading;
export const repos = state => state.repos; export const repos = state => state.repos;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
...@@ -40,10 +40,6 @@ ...@@ -40,10 +40,6 @@
.project-home-panel { .project-home-panel {
padding-left: 0 !important; padding-left: 0 !important;
.project-avatar {
display: block;
}
.project-repo-buttons, .project-repo-buttons,
.git-clone-holder { .git-clone-holder {
display: none; display: none;
......
...@@ -31,7 +31,7 @@ module ProtectedRef ...@@ -31,7 +31,7 @@ module ProtectedRef
end end
end end
def protected_ref_accessible_to?(ref, user, action:, protected_refs: nil) def protected_ref_accessible_to?(ref, user, project:, action:, protected_refs: nil)
access_levels_for_ref(ref, action: action, protected_refs: protected_refs).any? do |access_level| access_levels_for_ref(ref, action: action, protected_refs: protected_refs).any? do |access_level|
access_level.check_access(user) access_level.check_access(user)
end end
......
...@@ -197,10 +197,6 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -197,10 +197,6 @@ class MergeRequestDiff < ActiveRecord::Base
CompareService.new(project, head_commit_sha).execute(project, sha, straight: true) CompareService.new(project, head_commit_sha).execute(project, sha, straight: true)
end end
def commits_count
super || merge_request_diff_commits.size
end
private private
def create_merge_request_diff_files(diffs) def create_merge_request_diff_files(diffs)
......
...@@ -1047,13 +1047,6 @@ class Project < ActiveRecord::Base ...@@ -1047,13 +1047,6 @@ class Project < ActiveRecord::Base
"#{web_url}.git" "#{web_url}.git"
end end
def user_can_push_to_empty_repo?(user)
return false unless empty_repo?
return false unless Ability.allowed?(user, :push_code, self)
!ProtectedBranch.default_branch_protected? || team.max_member_access(user.id) > Gitlab::Access::DEVELOPER
end
def forked? def forked?
return true if fork_network && fork_network.root_project != self return true if fork_network && fork_network.root_project != self
...@@ -2014,10 +2007,11 @@ class Project < ActiveRecord::Base ...@@ -2014,10 +2007,11 @@ class Project < ActiveRecord::Base
def fetch_branch_allows_maintainer_push?(user, branch_name) def fetch_branch_allows_maintainer_push?(user, branch_name)
check_access = -> do check_access = -> do
next false if empty_repo?
merge_request = source_of_merge_requests.opened merge_request = source_of_merge_requests.opened
.where(allow_maintainer_to_push: true) .where(allow_maintainer_to_push: true)
.find_by(source_branch: branch_name) .find_by(source_branch: branch_name)
merge_request&.can_be_merged_by?(user) merge_request&.can_be_merged_by?(user)
end end
......
...@@ -4,6 +4,15 @@ class ProtectedBranch < ActiveRecord::Base ...@@ -4,6 +4,15 @@ class ProtectedBranch < ActiveRecord::Base
protected_ref_access_levels :merge, :push protected_ref_access_levels :merge, :push
def self.protected_ref_accessible_to?(ref, user, project:, action:, protected_refs: nil)
# Masters, owners and admins are allowed to create the default branch
if default_branch_protected? && project.empty_repo?
return true if user.admin? || project.team.max_member_access(user.id) > Gitlab::Access::DEVELOPER
end
super
end
# Check if branch name is marked as protected in the system # Check if branch name is marked as protected in the system
def self.protected?(project, ref_name) def self.protected?(project, ref_name)
return true if project.empty_repo? && default_branch_protected? return true if project.empty_repo? && default_branch_protected?
......
...@@ -4,6 +4,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -4,6 +4,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
include GitlabRoutingHelper include GitlabRoutingHelper
include StorageHelper include StorageHelper
include TreeHelper include TreeHelper
include ChecksCollaboration
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
presents :project presents :project
...@@ -170,9 +171,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -170,9 +171,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end end
def can_current_user_push_to_branch?(branch) def can_current_user_push_to_branch?(branch)
return false unless repository.branch_exists?(branch) user_access(project).can_push_to_branch?(branch)
end
::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(branch) def can_current_user_push_to_default_branch?
can_current_user_push_to_branch?(default_branch)
end end
def files_anchor_data def files_anchor_data
...@@ -200,7 +203,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -200,7 +203,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end end
def new_file_anchor_data def new_file_anchor_data
if current_user && can_current_user_push_code? if current_user && can_current_user_push_to_default_branch?
OpenStruct.new(enabled: false, OpenStruct.new(enabled: false,
label: _('New file'), label: _('New file'),
link: project_new_blob_path(project, default_branch || 'master'), link: project_new_blob_path(project, default_branch || 'master'),
...@@ -209,7 +212,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -209,7 +212,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end end
def readme_anchor_data def readme_anchor_data
if current_user && can_current_user_push_code? && repository.readme.blank? if current_user && can_current_user_push_to_default_branch? && repository.readme.blank?
OpenStruct.new(enabled: false, OpenStruct.new(enabled: false,
label: _('Add Readme'), label: _('Add Readme'),
link: add_readme_path) link: add_readme_path)
...@@ -221,7 +224,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -221,7 +224,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end end
def changelog_anchor_data def changelog_anchor_data
if current_user && can_current_user_push_code? && repository.changelog.blank? if current_user && can_current_user_push_to_default_branch? && repository.changelog.blank?
OpenStruct.new(enabled: false, OpenStruct.new(enabled: false,
label: _('Add Changelog'), label: _('Add Changelog'),
link: add_changelog_path) link: add_changelog_path)
...@@ -233,7 +236,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -233,7 +236,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end end
def license_anchor_data def license_anchor_data
if current_user && can_current_user_push_code? && repository.license_blob.blank? if current_user && can_current_user_push_to_default_branch? && repository.license_blob.blank?
OpenStruct.new(enabled: false, OpenStruct.new(enabled: false,
label: _('Add License'), label: _('Add License'),
link: add_license_path) link: add_license_path)
...@@ -245,7 +248,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -245,7 +248,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end end
def contribution_guide_anchor_data def contribution_guide_anchor_data
if current_user && can_current_user_push_code? && repository.contribution_guide.blank? if current_user && can_current_user_push_to_default_branch? && repository.contribution_guide.blank?
OpenStruct.new(enabled: false, OpenStruct.new(enabled: false,
label: _('Add Contribution guide'), label: _('Add Contribution guide'),
link: add_contribution_guide_path) link: add_contribution_guide_path)
......
...@@ -270,6 +270,26 @@ module QuickActions ...@@ -270,6 +270,26 @@ module QuickActions
end end
end end
desc 'Copy labels and milestone from other issue or merge request'
explanation do |source_issuable|
"Copy labels and milestone from #{source_issuable.to_reference}."
end
params '#issue | !merge_request'
condition do
issuable.persisted? &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end
parse_params do |issuable_param|
extract_references(issuable_param, :issue).first ||
extract_references(issuable_param, :merge_request).first
end
command :copy_metadata do |source_issuable|
if source_issuable.present? && source_issuable.project.id == issuable.project.id
@updates[:add_label_ids] = source_issuable.labels.map(&:id)
@updates[:milestone_id] = source_issuable.milestone.id if source_issuable.milestone
end
end
desc 'Add a todo' desc 'Add a todo'
explanation 'Adds a todo.' explanation 'Adds a todo.'
condition do condition do
......
- breadcrumb_title "General Settings" - breadcrumb_title "General Settings"
- @content_class = "limit-container-width" unless fluid_layout
.panel.panel-default.prepend-top-default .panel.panel-default.prepend-top-default
.panel-heading .panel-heading
Group settings Group settings
......
...@@ -58,7 +58,9 @@ ...@@ -58,7 +58,9 @@
touch README.md touch README.md
git add README.md git add README.md
git commit -m "add README" git commit -m "add README"
git push -u origin master - if @project.can_current_user_push_to_default_branch?
%span><
git push -u origin master
%fieldset %fieldset
%h5 Existing folder %h5 Existing folder
...@@ -69,7 +71,9 @@ ...@@ -69,7 +71,9 @@
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')} git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
git add . git add .
git commit -m "Initial commit" git commit -m "Initial commit"
git push -u origin master - if @project.can_current_user_push_to_default_branch?
%span><
git push -u origin master
%fieldset %fieldset
%h5 Existing Git repository %h5 Existing Git repository
...@@ -78,8 +82,10 @@ ...@@ -78,8 +82,10 @@
cd existing_repo cd existing_repo
git remote rename origin old-origin git remote rename origin old-origin
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')} git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
git push -u origin --all - if @project.can_current_user_push_to_default_branch?
git push -u origin --tags %span><
git push -u origin --all
git push -u origin --tags
- if can? current_user, :remove_project, @project - if can? current_user, :remove_project, @project
.prepend-top-20 .prepend-top-20
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
- if @namespaces.present? - if @namespaces.present?
.fork-thumbnail-container.js-fork-content .fork-thumbnail-container.js-fork-content
%h5.prepend-top-0.append-bottom-0.prepend-left-default.append-right-default %h5.prepend-top-0.append-bottom-0.prepend-left-default.append-right-default
Click to fork the project = _("Select a namespace to fork the project")
- @namespaces.each do |namespace| - @namespaces.each do |namespace|
= render 'fork_button', namespace: namespace = render 'fork_button', namespace: namespace
- else - else
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
docker login #{Gitlab.config.registry.host_port} docker login #{Gitlab.config.registry.host_port}
%br %br
%p %p
- deploy_token = link_to(_('deploy token'), help_page_path('user/projects/deploy_tokens/index', anchor: 'read-container-registry-images'), target: '_blank') - deploy_token = link_to(_('deploy token'), help_page_path('user/project/deploy_tokens/index', anchor: 'read-container-registry-images'), target: '_blank')
= s_('ContainerRegistry|You can also %{deploy_token} for read-only access to the registry images.').html_safe % { deploy_token: deploy_token } = s_('ContainerRegistry|You can also %{deploy_token} for read-only access to the registry images.').html_safe % { deploy_token: deploy_token }
%br %br
%p %p
......
...@@ -32,6 +32,13 @@ ...@@ -32,6 +32,13 @@
required: true, required: true,
title: 'You can choose a descriptive name different from the path.' title: 'You can choose a descriptive name different from the path.'
- if @group.persisted?
.form-group.group-name-holder
= f.label :id, class: 'control-label' do
= _("Group ID")
.col-sm-10
= f.text_field :id, class: 'form-control', readonly: true
.form-group.group-description-holder .form-group.group-description-holder
= f.label :description, class: 'control-label' = f.label :description, class: 'control-label'
.col-sm-10 .col-sm-10
......
---
title: Replace "Click" with "Select" to be more inclusive of people with accessibility
requirements
merge_request: 18386
author: Mark Lapierre
type: other
---
title: Add Copy metadata quick action
merge_request: 16473
author: Mateusz Bajorski
type: added
---
title: Align project avatar on small viewports
merge_request: 18513
author: George Tsiolis
type: changed
---
title: Fix errors on pushing to an empty repository
merge_request: 18462
author:
type: fixed
---
title: Show group id in group settings
merge_request: 18482
author: George Tsiolis
type: added
require_dependency File.expand_path('../../lib/gitlab', __dir__) # Load Gitlab as soon as possible require_relative '../settings'
# Default settings # Default settings
Settings['ldap'] ||= Settingslogic.new({}) Settings['ldap'] ||= Settingslogic.new({})
......
require_relative '../../lib/gitlab'
deprecator = ActiveSupport::Deprecation.new('11.0', 'GitLab') deprecator = ActiveSupport::Deprecation.new('11.0', 'GitLab')
if Gitlab.com? || Rails.env.development? if Gitlab.dev_env_or_com?
ActiveSupport::Deprecation.deprecate_methods(Gitlab::GitalyClient::StorageSettings, :legacy_disk_path, deprecator: deprecator) ActiveSupport::Deprecation.deprecate_methods(Gitlab::GitalyClient::StorageSettings, :legacy_disk_path, deprecator: deprecator)
end end
...@@ -33,7 +33,7 @@ webpackConfig.plugins.push( ...@@ -33,7 +33,7 @@ webpackConfig.plugins.push(
}) })
); );
webpackConfig.devtool = 'cheap-inline-source-map'; webpackConfig.devtool = process.env.BABEL_ENV !== 'coverage' && 'cheap-inline-source-map';
// Karma configuration // Karma configuration
module.exports = function(config) { module.exports = function(config) {
......
class AssureCommitsCountForMergeRequestDiff < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
class MergeRequestDiff < ActiveRecord::Base
self.table_name = 'merge_request_diffs'
include ::EachBatch
end
def up
Gitlab::BackgroundMigration.steal('AddMergeRequestDiffCommitsCount')
MergeRequestDiff.where(commits_count: nil).each_batch(of: 50) do |batch|
range = batch.pluck('MIN(id)', 'MAX(id)').first
Gitlab::BackgroundMigration::AddMergeRequestDiffCommitsCount.new.perform(*range)
end
end
def down
# noop
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180418053107) do ActiveRecord::Schema.define(version: 20180425131009) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
......
...@@ -298,6 +298,28 @@ Mentioned briefly earlier, but the following things of Runners can be exploited. ...@@ -298,6 +298,28 @@ Mentioned briefly earlier, but the following things of Runners can be exploited.
We're always looking for contributions that can mitigate these We're always looking for contributions that can mitigate these
[Security Considerations](https://docs.gitlab.com/runner/security/). [Security Considerations](https://docs.gitlab.com/runner/security/).
### Resetting the registration token for a Project
If you think that registration token for a Project was revealed, you should
reset them. It's recommended because such token can be used to register another
Runner to thi Project. It may be next used to obtain the values of secret
variables or clone the project code, that normally may be unavailable for the
attacker.
To reset the token:
1. Go to **Settings > CI/CD** for a specified Project
1. Expand the **General pipelines settings** section
1. Find the **Runner token** form field and click the **Reveal value** button
1. Delete the value and save the form
1. After the page is refreshed, expand the **Runners settings** section
and check the registration token - it should be changed
From now on the old token is not valid anymore and will not allow to register
a new Runner to the project. If you are using any tools to provision and
register new Runners, you should now update the token that is used to the
new value.
## Determining the IP address of a Runner ## Determining the IP address of a Runner
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17286) in GitLab 10.6. > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17286) in GitLab 10.6.
......
...@@ -126,13 +126,51 @@ it('tests a promise rejection', (done) => { ...@@ -126,13 +126,51 @@ it('tests a promise rejection', (done) => {
}); });
``` ```
#### Stubbing #### Stubbing and Mocking
For unit tests, you should stub methods that are unrelated to the current unit you are testing. Jasmine provides useful helpers `spyOn`, `spyOnProperty`, `jasmine.createSpy`,
If you need to use a prototype method, instantiate an instance of the class and call it there instead of mocking the instance completely. and `jasmine.createSpyObject` to facilitate replacing methods with dummy
placeholders, and recalling when they are called and the arguments that are
passed to them. These tools should be used liberally, to test for expected
behavior, to mock responses, and to block unwanted side effects (such as a
method that would generate a network request or alter `window.location`). The
documentation for these methods can be found in the [jasmine introduction page](https://jasmine.github.io/2.0/introduction.html#section-Spies).
For integration tests, you should stub methods that will effect the stability of the test if they Sometimes you may need to spy on a method that is directly imported by another
execute their original behaviour. i.e. Network requests. module. GitLab has a custom `spyOnDependency` method which utilizes
[babel-plugin-rewire](https://github.com/speedskater/babel-plugin-rewire) to
achieve this. It can be used like so:
```javascript
// my_module.js
import { visitUrl } from '~/lib/utils/url_utility';
export default function doSomething() {
visitUrl('/foo/bar');
}
// my_module_spec.js
import doSomething from '~/my_module';
describe('my_module', () => {
it('does something', () => {
const visitUrl = spyOnDependency(doSomething, 'visitUrl');
doSomething();
expect(visitUrl).toHaveBeenCalledWith('/foo/bar');
});
});
```
Unlike `spyOn`, `spyOnDependency` expects its first parameter to be the default
export of a module who's import you want to stub, rather than an object which
contains a method you wish to stub (if the module does not have a default
export, one is be generated by the babel plugin). The second parameter is the
name of the import you wish to change. The result of the function is a Spy
object which can be treated like any other jasmine spy object.
Further documentation on the babel rewire pluign API can be found on
[its repository Readme doc](https://github.com/speedskater/babel-plugin-rewire#babel-plugin-rewire).
### Vue.js unit tests ### Vue.js unit tests
......
...@@ -38,6 +38,7 @@ do. ...@@ -38,6 +38,7 @@ do.
| `/award :emoji:` | Toggle award for :emoji: | | `/award :emoji:` | Toggle award for :emoji: |
| `/board_move ~column` | Move issue to column on the board | | `/board_move ~column` | Move issue to column on the board |
| `/duplicate #issue` | Closes this issue and marks it as a duplicate of another issue | | `/duplicate #issue` | Closes this issue and marks it as a duplicate of another issue |
| `/move path/to/project` | Moves issue to another project | | `/move path/to/project` | Moves issue to another project |
| `/tableflip` | Append the comment with `(╯°□°)╯︵ ┻━┻` | | `/tableflip` | Append the comment with `(╯°□°)╯︵ ┻━┻` |
| `/shrug` | Append the comment with `¯\_(ツ)_/¯` | | `/shrug` | Append the comment with `¯\_(ツ)_/¯` |
\ No newline at end of file | <code>/copy_metadata #issue &#124; !merge_request</code> | Copy labels and milestone from other issue or merge request |
...@@ -31,7 +31,8 @@ with all their related data and be moved into a new GitLab instance. ...@@ -31,7 +31,8 @@ with all their related data and be moved into a new GitLab instance.
| GitLab version | Import/Export version | | GitLab version | Import/Export version |
| ---------------- | --------------------- | | ---------------- | --------------------- |
| 10.4 to current | 0.2.2 | | 10.8 to current | 0.2.3 |
| 10.4 | 0.2.2 |
| 10.3 | 0.2.1 | | 10.3 | 0.2.1 |
| 10.0 | 0.2.0 | | 10.0 | 0.2.0 |
| 9.4.0 | 0.1.8 | | 9.4.0 | 0.1.8 |
......
...@@ -244,3 +244,20 @@ GitLab checks files to detect LFS pointers on push. If LFS pointers are detected ...@@ -244,3 +244,20 @@ GitLab checks files to detect LFS pointers on push. If LFS pointers are detected
Verify that LFS in installed locally and consider a manual push with `git lfs push --all`. Verify that LFS in installed locally and consider a manual push with `git lfs push --all`.
If you are storing LFS files outside of GitLab you can disable LFS on the project by settting `lfs_enabled: false` with the [projects api](../../api/projects.md#edit-project). If you are storing LFS files outside of GitLab you can disable LFS on the project by settting `lfs_enabled: false` with the [projects api](../../api/projects.md#edit-project).
### Hosting LFS objects externally
It is possible to host LFS objects externally by setting a custom LFS url with `git config -f .lfsconfig lfs.url https://example.com/<project>.git/info/lfs`.
Because GitLab verifies the existence of objects referenced by LFS pointers, push will fail when LFS is enabled for the project.
LFS can be disabled for a project by Owners and Masters using the [Project API](../../api/projects.md#edit-project).
```bash
curl --request PUT \
--url https://example.com/api/v4/projects/<PROJECT_ID> \
--header 'Private-Token: <YOUR_PRIVATE_TOKEN>' \
--data 'lfs_enabled=false'
```
Note, `<PROJECT_ID>` can also be substituted with a [namespaced path](../../api/README.md#namespaced-path-encoding).
require_dependency 'settings'
require_dependency 'gitlab/popen' require_dependency 'gitlab/popen'
module Gitlab module Gitlab
...@@ -30,6 +29,6 @@ module Gitlab ...@@ -30,6 +29,6 @@ module Gitlab
end end
def self.dev_env_or_com? def self.dev_env_or_com?
Rails.env.test? || Rails.env.development? || org? || com? Rails.env.development? || org? || com?
end end
end end
...@@ -3,7 +3,7 @@ module Gitlab ...@@ -3,7 +3,7 @@ module Gitlab
extend self extend self
# For every version update, the version history in import_export.md has to be kept up to date. # For every version update, the version history in import_export.md has to be kept up to date.
VERSION = '0.2.2'.freeze VERSION = '0.2.3'.freeze
FILENAME_LIMIT = 50 FILENAME_LIMIT = 50
def export_path(relative_path:) def export_path(relative_path:)
......
...@@ -63,10 +63,12 @@ module Gitlab ...@@ -63,10 +63,12 @@ module Gitlab
request_cache def can_push_to_branch?(ref) request_cache def can_push_to_branch?(ref)
return false unless can_access_git? return false unless can_access_git?
return false unless user.can?(:push_code, project) || project.branch_allows_maintainer_push?(user, ref) return false unless project
return false if !user.can?(:push_code, project) && !project.branch_allows_maintainer_push?(user, ref)
if protected?(ProtectedBranch, project, ref) if protected?(ProtectedBranch, project, ref)
project.user_can_push_to_empty_repo?(user) || protected_branch_accessible_to?(ref, action: :push) protected_branch_accessible_to?(ref, action: :push)
else else
true true
end end
...@@ -101,6 +103,7 @@ module Gitlab ...@@ -101,6 +103,7 @@ module Gitlab
def protected_branch_accessible_to?(ref, action:) def protected_branch_accessible_to?(ref, action:)
ProtectedBranch.protected_ref_accessible_to?( ProtectedBranch.protected_ref_accessible_to?(
ref, user, ref, user,
project: project,
action: action, action: action,
protected_refs: project.protected_branches) protected_refs: project.protected_branches)
end end
...@@ -108,6 +111,7 @@ module Gitlab ...@@ -108,6 +111,7 @@ module Gitlab
def protected_tag_accessible_to?(ref, action:) def protected_tag_accessible_to?(ref, action:)
ProtectedTag.protected_ref_accessible_to?( ProtectedTag.protected_ref_accessible_to?(
ref, user, ref, user,
project: project,
action: action, action: action,
protected_refs: project.protected_tags) protected_refs: project.protected_tags)
end end
......
...@@ -8,8 +8,8 @@ msgid "" ...@@ -8,8 +8,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gitlab 1.0.0\n" "Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-04-17 11:44+0200\n" "POT-Creation-Date: 2018-04-24 13:19+0000\n"
"PO-Revision-Date: 2018-04-17 11:44+0200\n" "PO-Revision-Date: 2018-04-24 13:19+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
...@@ -3136,6 +3136,9 @@ msgstr "" ...@@ -3136,6 +3136,9 @@ msgstr ""
msgid "Select Archive Format" msgid "Select Archive Format"
msgstr "" msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
msgid "Select a timezone" msgid "Select a timezone"
msgstr "" msgstr ""
......
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
"eslint": "eslint --max-warnings 0 --ext .js,.vue .", "eslint": "eslint --max-warnings 0 --ext .js,.vue .",
"eslint-fix": "eslint --max-warnings 0 --ext .js,.vue --fix .", "eslint-fix": "eslint --max-warnings 0 --ext .js,.vue --fix .",
"eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html .", "eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html .",
"karma": "karma start --single-run true config/karma.config.js", "karma": "BABEL_ENV=${BABEL_ENV:=karma} karma start --single-run true config/karma.config.js",
"karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js", "karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js",
"karma-start": "karma start config/karma.config.js", "karma-start": "BABEL_ENV=karma karma start config/karma.config.js",
"prettier-staged": "node ./scripts/frontend/prettier.js", "prettier-staged": "node ./scripts/frontend/prettier.js",
"prettier-staged-save": "node ./scripts/frontend/prettier.js save", "prettier-staged-save": "node ./scripts/frontend/prettier.js save",
"prettier-all": "node ./scripts/frontend/prettier.js check-all", "prettier-all": "node ./scripts/frontend/prettier.js check-all",
...@@ -99,6 +99,9 @@ ...@@ -99,6 +99,9 @@
"axios-mock-adapter": "^1.10.0", "axios-mock-adapter": "^1.10.0",
"babel-eslint": "^8.0.2", "babel-eslint": "^8.0.2",
"babel-plugin-istanbul": "^4.1.5", "babel-plugin-istanbul": "^4.1.5",
"babel-plugin-rewire": "^1.1.0",
"babel-template": "^6.26.0",
"babel-types": "^6.26.0",
"commander": "^2.15.1", "commander": "^2.15.1",
"eslint": "^3.18.0", "eslint": "^3.18.0",
"eslint-config-airbnb-base": "^10.0.1", "eslint-config-airbnb-base": "^10.0.1",
......
...@@ -151,6 +151,13 @@ FactoryBot.define do ...@@ -151,6 +151,13 @@ FactoryBot.define do
end end
end end
trait :stubbed_repository do
after(:build) do |project|
allow(project).to receive(:empty_repo?).and_return(false)
allow(project.repository).to receive(:empty?).and_return(false)
end
end
trait :wiki_repo do trait :wiki_repo do
after(:create) do |project| after(:create) do |project|
raise 'Failed to create wiki repository!' unless project.create_wiki raise 'Failed to create wiki repository!' unless project.create_wiki
......
...@@ -9,7 +9,8 @@ unless Object.respond_to?(:require_dependency) ...@@ -9,7 +9,8 @@ unless Object.respond_to?(:require_dependency)
end end
end end
# Defines Gitlab and Gitlab.config which are at the center of the app # Defines Settings and Gitlab.config which are at the center of the app
require_relative '../config/settings'
require_relative '../lib/gitlab' unless defined?(Gitlab.config) require_relative '../lib/gitlab' unless defined?(Gitlab.config)
require_relative 'support/rspec' require_relative 'support/rspec'
require 'spec_helper'
feature 'User creates blob in new project', :js do
let(:user) { create(:user) }
let(:project) { create(:project, :empty_repo) }
shared_examples 'creating a file' do
before do
sign_in(user)
visit project_path(project)
end
it 'allows the user to add a new file' do
click_link 'New file'
find('#editor')
execute_script('ace.edit("editor").setValue("Hello world")')
fill_in(:file_name, with: 'dummy-file')
click_button('Commit changes')
expect(page).to have_content('The file has been successfully created')
end
end
describe 'as a master' do
before do
project.add_master(user)
end
it_behaves_like 'creating a file'
end
describe 'as an admin' do
let(:user) { create(:user, :admin) }
it_behaves_like 'creating a file'
end
describe 'as a developer' do
before do
project.add_developer(user)
sign_in(user)
visit project_path(project)
end
it 'does not allow pushing to the default branch' do
expect(page).not_to have_content('New file')
end
end
end
require 'spec_helper'
describe 'User views an empty project' do
let(:project) { create(:project, :empty_repo) }
let(:user) { create(:user) }
shared_examples 'allowing push to default branch' do
before do
sign_in(user)
visit project_path(project)
end
it 'shows push-to-master instructions' do
expect(page).to have_content('git push -u origin master')
end
end
describe 'as a master' do
before do
project.add_master(user)
end
it_behaves_like 'allowing push to default branch'
end
describe 'as an admin' do
let(:user) { create(:user, :admin) }
it_behaves_like 'allowing push to default branch'
end
describe 'as a developer' do
before do
project.add_developer(user)
sign_in(user)
visit project_path(project)
end
it 'does not show push-to-master instructions' do
expect(page).not_to have_content('git push -u origin master')
end
end
end
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
"sandbox": false, "sandbox": false,
"setFixtures": false, "setFixtures": false,
"setStyleFixtures": false, "setStyleFixtures": false,
"spyOnDependency": false,
"spyOnEvent": false, "spyOnEvent": false,
"ClassSpecHelper": false "ClassSpecHelper": false
}, },
......
...@@ -3,24 +3,30 @@ ...@@ -3,24 +3,30 @@
import $ from 'jquery'; import $ from 'jquery';
import 'vendor/jquery.endless-scroll'; import 'vendor/jquery.endless-scroll';
import Activities from '~/activities'; import Activities from '~/activities';
import Pager from '~/pager';
(() => { describe('Activities', () => {
window.gon || (window.gon = {}); window.gon || (window.gon = {});
const fixtureTemplate = 'static/event_filter.html.raw'; const fixtureTemplate = 'static/event_filter.html.raw';
const filters = [ const filters = [
{ {
id: 'all', id: 'all',
}, { },
{
id: 'push', id: 'push',
name: 'push events', name: 'push events',
}, { },
{
id: 'merged', id: 'merged',
name: 'merge events', name: 'merge events',
}, { },
{
id: 'comments', id: 'comments',
}, { },
{
id: 'team', id: 'team',
}]; },
];
function getEventName(index) { function getEventName(index) {
const filter = filters[index]; const filter = filters[index];
...@@ -32,31 +38,34 @@ import Activities from '~/activities'; ...@@ -32,31 +38,34 @@ import Activities from '~/activities';
return `#${filter.id}_event_filter`; return `#${filter.id}_event_filter`;
} }
describe('Activities', () => { beforeEach(() => {
beforeEach(() => { loadFixtures(fixtureTemplate);
loadFixtures(fixtureTemplate); spyOn(Pager, 'init').and.stub();
new Activities(); new Activities();
});
for (let i = 0; i < filters.length; i += 1) {
((i) => {
describe(`when selecting ${getEventName(i)}`, () => {
beforeEach(() => {
$(getSelector(i)).click();
});
for (let x = 0; x < filters.length; x += 1) {
((x) => {
const shouldHighlight = i === x;
const testName = shouldHighlight ? 'should highlight' : 'should not highlight';
it(`${testName} ${getEventName(x)}`, () => {
expect($(getSelector(x)).parent().hasClass('active')).toEqual(shouldHighlight);
});
})(x);
}
});
})(i);
}
}); });
})();
for (let i = 0; i < filters.length; i += 1) {
(i => {
describe(`when selecting ${getEventName(i)}`, () => {
beforeEach(() => {
$(getSelector(i)).click();
});
for (let x = 0; x < filters.length; x += 1) {
(x => {
const shouldHighlight = i === x;
const testName = shouldHighlight ? 'should highlight' : 'should not highlight';
it(`${testName} ${getEventName(x)}`, () => {
expect(
$(getSelector(x))
.parent()
.hasClass('active'),
).toEqual(shouldHighlight);
});
})(x);
}
});
})(i);
}
});
import $ from 'jquery'; import $ from 'jquery';
import '~/behaviors/quick_submit'; import '~/behaviors/quick_submit';
describe('Quick Submit behavior', () => { describe('Quick Submit behavior', function () {
const keydownEvent = (options = { keyCode: 13, metaKey: true }) => $.Event('keydown', options); const keydownEvent = (options = { keyCode: 13, metaKey: true }) => $.Event('keydown', options);
preloadFixtures('merge_requests/merge_request_with_task_list.html.raw'); preloadFixtures('merge_requests/merge_request_with_task_list.html.raw');
......
import $ from 'jquery'; import $ from 'jquery';
import BlobFileDropzone from '~/blob/blob_file_dropzone'; import BlobFileDropzone from '~/blob/blob_file_dropzone';
describe('BlobFileDropzone', () => { describe('BlobFileDropzone', function () {
preloadFixtures('blob/show.html.raw'); preloadFixtures('blob/show.html.raw');
beforeEach(() => { beforeEach(() => {
......
import CommentTypeToggle from '~/comment_type_toggle'; import CommentTypeToggle from '~/comment_type_toggle';
import * as dropLabSrc from '~/droplab/drop_lab';
import InputSetter from '~/droplab/plugins/input_setter'; import InputSetter from '~/droplab/plugins/input_setter';
describe('CommentTypeToggle', function () { describe('CommentTypeToggle', function () {
...@@ -59,14 +58,14 @@ describe('CommentTypeToggle', function () { ...@@ -59,14 +58,14 @@ describe('CommentTypeToggle', function () {
this.droplab = jasmine.createSpyObj('droplab', ['init']); this.droplab = jasmine.createSpyObj('droplab', ['init']);
spyOn(dropLabSrc, 'default').and.returnValue(this.droplab); this.droplabConstructor = spyOnDependency(CommentTypeToggle, 'DropLab').and.returnValue(this.droplab);
spyOn(this.commentTypeToggle, 'setConfig').and.returnValue(this.config); spyOn(this.commentTypeToggle, 'setConfig').and.returnValue(this.config);
CommentTypeToggle.prototype.initDroplab.call(this.commentTypeToggle); CommentTypeToggle.prototype.initDroplab.call(this.commentTypeToggle);
}); });
it('should instantiate a DropLab instance', function () { it('should instantiate a DropLab instance', function () {
expect(dropLabSrc.default).toHaveBeenCalled(); expect(this.droplabConstructor).toHaveBeenCalled();
}); });
it('should set .droplab', function () { it('should set .droplab', function () {
......
...@@ -4,7 +4,7 @@ import axios from '~/lib/utils/axios_utils'; ...@@ -4,7 +4,7 @@ import axios from '~/lib/utils/axios_utils';
import pipelinesTable from '~/commit/pipelines/pipelines_table.vue'; import pipelinesTable from '~/commit/pipelines/pipelines_table.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Pipelines table in Commits and Merge requests', () => { describe('Pipelines table in Commits and Merge requests', function () {
const jsonFixtureName = 'pipelines/pipelines.json'; const jsonFixtureName = 'pipelines/pipelines.json';
let pipeline; let pipeline;
let PipelinesTable; let PipelinesTable;
......
...@@ -3,6 +3,7 @@ import 'vendor/jquery.endless-scroll'; ...@@ -3,6 +3,7 @@ import 'vendor/jquery.endless-scroll';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import CommitsList from '~/commits'; import CommitsList from '~/commits';
import Pager from '~/pager';
describe('Commits List', () => { describe('Commits List', () => {
let commitsList; let commitsList;
...@@ -14,6 +15,7 @@ describe('Commits List', () => { ...@@ -14,6 +15,7 @@ describe('Commits List', () => {
</form> </form>
<ol id="commits-list"></ol> <ol id="commits-list"></ol>
`); `);
spyOn(Pager, 'init').and.stub();
commitsList = new CommitsList(25); commitsList = new CommitsList(25);
}); });
...@@ -68,9 +70,10 @@ describe('Commits List', () => { ...@@ -68,9 +70,10 @@ describe('Commits List', () => {
mock.restore(); mock.restore();
}); });
it('should save the last search string', (done) => { it('should save the last search string', done => {
commitsList.searchField.val('GitLab'); commitsList.searchField.val('GitLab');
commitsList.filterResults() commitsList
.filterResults()
.then(() => { .then(() => {
expect(ajaxSpy).toHaveBeenCalled(); expect(ajaxSpy).toHaveBeenCalled();
expect(commitsList.lastSearch).toEqual('GitLab'); expect(commitsList.lastSearch).toEqual('GitLab');
...@@ -80,8 +83,9 @@ describe('Commits List', () => { ...@@ -80,8 +83,9 @@ describe('Commits List', () => {
.catch(done.fail); .catch(done.fail);
}); });
it('should not make ajax call if the input does not change', (done) => { it('should not make ajax call if the input does not change', done => {
commitsList.filterResults() commitsList
.filterResults()
.then(() => { .then(() => {
expect(ajaxSpy).not.toHaveBeenCalled(); expect(ajaxSpy).not.toHaveBeenCalled();
expect(commitsList.lastSearch).toEqual(''); expect(commitsList.lastSearch).toEqual('');
......
import Hook from '~/droplab/hook'; import Hook from '~/droplab/hook';
import * as dropdownSrc from '~/droplab/drop_down';
describe('Hook', function () { describe('Hook', function () {
describe('class constructor', function () { describe('class constructor', function () {
...@@ -10,7 +9,7 @@ describe('Hook', function () { ...@@ -10,7 +9,7 @@ describe('Hook', function () {
this.config = {}; this.config = {};
this.dropdown = {}; this.dropdown = {};
spyOn(dropdownSrc, 'default').and.returnValue(this.dropdown); this.dropdownConstructor = spyOnDependency(Hook, 'DropDown').and.returnValue(this.dropdown);
this.hook = new Hook(this.trigger, this.list, this.plugins, this.config); this.hook = new Hook(this.trigger, this.list, this.plugins, this.config);
}); });
...@@ -24,7 +23,7 @@ describe('Hook', function () { ...@@ -24,7 +23,7 @@ describe('Hook', function () {
}); });
it('should call DropDown constructor', function () { it('should call DropDown constructor', function () {
expect(dropdownSrc.default).toHaveBeenCalledWith(this.list, this.config); expect(this.dropdownConstructor).toHaveBeenCalledWith(this.list, this.config);
}); });
it('should set .type', function () { it('should set .type', function () {
......
import * as urlUtils from '~/lib/utils/url_utility';
import * as recentSearchesStoreSrc from '~/filtered_search/stores/recent_searches_store';
import RecentSearchesService from '~/filtered_search/services/recent_searches_service'; import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error'; import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error';
import RecentSearchesRoot from '~/filtered_search/recent_searches_root'; import RecentSearchesRoot from '~/filtered_search/recent_searches_root';
...@@ -11,7 +9,7 @@ import FilteredSearchDropdownManager from '~/filtered_search/filtered_search_dro ...@@ -11,7 +9,7 @@ import FilteredSearchDropdownManager from '~/filtered_search/filtered_search_dro
import FilteredSearchManager from '~/filtered_search/filtered_search_manager'; import FilteredSearchManager from '~/filtered_search/filtered_search_manager';
import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper'; import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper';
describe('Filtered Search Manager', () => { describe('Filtered Search Manager', function () {
let input; let input;
let manager; let manager;
let tokensContainer; let tokensContainer;
...@@ -74,18 +72,19 @@ describe('Filtered Search Manager', () => { ...@@ -74,18 +72,19 @@ describe('Filtered Search Manager', () => {
describe('class constructor', () => { describe('class constructor', () => {
const isLocalStorageAvailable = 'isLocalStorageAvailable'; const isLocalStorageAvailable = 'isLocalStorageAvailable';
let RecentSearchesStoreSpy;
beforeEach(() => { beforeEach(() => {
spyOn(RecentSearchesService, 'isAvailable').and.returnValue(isLocalStorageAvailable); spyOn(RecentSearchesService, 'isAvailable').and.returnValue(isLocalStorageAvailable);
spyOn(recentSearchesStoreSrc, 'default');
spyOn(RecentSearchesRoot.prototype, 'render'); spyOn(RecentSearchesRoot.prototype, 'render');
RecentSearchesStoreSpy = spyOnDependency(FilteredSearchManager, 'RecentSearchesStore');
}); });
it('should instantiate RecentSearchesStore with isLocalStorageAvailable', () => { it('should instantiate RecentSearchesStore with isLocalStorageAvailable', () => {
manager = new FilteredSearchManager({ page }); manager = new FilteredSearchManager({ page });
expect(RecentSearchesService.isAvailable).toHaveBeenCalled(); expect(RecentSearchesService.isAvailable).toHaveBeenCalled();
expect(recentSearchesStoreSrc.default).toHaveBeenCalledWith({ expect(RecentSearchesStoreSpy).toHaveBeenCalledWith({
isLocalStorageAvailable, isLocalStorageAvailable,
allowedKeys: FilteredSearchTokenKeys.getKeys(), allowedKeys: FilteredSearchTokenKeys.getKeys(),
}); });
...@@ -164,7 +163,7 @@ describe('Filtered Search Manager', () => { ...@@ -164,7 +163,7 @@ describe('Filtered Search Manager', () => {
it('should search with a single word', (done) => { it('should search with a single word', (done) => {
input.value = 'searchTerm'; input.value = 'searchTerm';
spyOn(urlUtils, 'visitUrl').and.callFake((url) => { spyOnDependency(FilteredSearchManager, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&search=searchTerm`); expect(url).toEqual(`${defaultParams}&search=searchTerm`);
done(); done();
}); });
...@@ -175,7 +174,7 @@ describe('Filtered Search Manager', () => { ...@@ -175,7 +174,7 @@ describe('Filtered Search Manager', () => {
it('should search with multiple words', (done) => { it('should search with multiple words', (done) => {
input.value = 'awesome search terms'; input.value = 'awesome search terms';
spyOn(urlUtils, 'visitUrl').and.callFake((url) => { spyOnDependency(FilteredSearchManager, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&search=awesome+search+terms`); expect(url).toEqual(`${defaultParams}&search=awesome+search+terms`);
done(); done();
}); });
...@@ -186,7 +185,7 @@ describe('Filtered Search Manager', () => { ...@@ -186,7 +185,7 @@ describe('Filtered Search Manager', () => {
it('should search with special characters', (done) => { it('should search with special characters', (done) => {
input.value = '~!@#$%^&*()_+{}:<>,.?/'; input.value = '~!@#$%^&*()_+{}:<>,.?/';
spyOn(urlUtils, 'visitUrl').and.callFake((url) => { spyOnDependency(FilteredSearchManager, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&search=~!%40%23%24%25%5E%26*()_%2B%7B%7D%3A%3C%3E%2C.%3F%2F`); expect(url).toEqual(`${defaultParams}&search=~!%40%23%24%25%5E%26*()_%2B%7B%7D%3A%3C%3E%2C.%3F%2F`);
done(); done();
}); });
...@@ -200,7 +199,7 @@ describe('Filtered Search Manager', () => { ...@@ -200,7 +199,7 @@ describe('Filtered Search Manager', () => {
${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')} ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')}
`); `);
spyOn(urlUtils, 'visitUrl').and.callFake((url) => { spyOnDependency(FilteredSearchManager, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(`${defaultParams}&label_name[]=bug`); expect(url).toEqual(`${defaultParams}&label_name[]=bug`);
done(); done();
}); });
......
import RecentSearchesRoot from '~/filtered_search/recent_searches_root'; import RecentSearchesRoot from '~/filtered_search/recent_searches_root';
import * as vueSrc from 'vue';
describe('RecentSearchesRoot', () => { describe('RecentSearchesRoot', () => {
describe('render', () => { describe('render', () => {
let recentSearchesRoot; let recentSearchesRoot;
let data; let data;
let template; let template;
let VueSpy;
beforeEach(() => { beforeEach(() => {
recentSearchesRoot = { recentSearchesRoot = {
...@@ -14,7 +14,7 @@ describe('RecentSearchesRoot', () => { ...@@ -14,7 +14,7 @@ describe('RecentSearchesRoot', () => {
}, },
}; };
spyOn(vueSrc, 'default').and.callFake((options) => { VueSpy = spyOnDependency(RecentSearchesRoot, 'Vue').and.callFake((options) => {
data = options.data; data = options.data;
template = options.template; template = options.template;
}); });
...@@ -23,7 +23,7 @@ describe('RecentSearchesRoot', () => { ...@@ -23,7 +23,7 @@ describe('RecentSearchesRoot', () => {
}); });
it('should instantiate Vue', () => { it('should instantiate Vue', () => {
expect(vueSrc.default).toHaveBeenCalled(); expect(VueSpy).toHaveBeenCalled();
expect(data()).toBe(recentSearchesRoot.store.state); expect(data()).toBe(recentSearchesRoot.store.state);
expect(template).toContain(':is-local-storage-available="isLocalStorageAvailable"'); expect(template).toContain(':is-local-storage-available="isLocalStorageAvailable"');
}); });
......
/* eslint-disable comma-dangle, no-param-reassign, no-unused-expressions, max-len */ /* eslint-disable comma-dangle, no-param-reassign, no-unused-expressions, max-len */
import $ from 'jquery'; import $ from 'jquery';
import '~/gl_dropdown'; import GLDropdown from '~/gl_dropdown';
import '~/lib/utils/common_utils'; import '~/lib/utils/common_utils';
import * as urlUtils from '~/lib/utils/url_utility';
describe('glDropdown', function describeDropdown() { describe('glDropdown', function describeDropdown() {
preloadFixtures('static/gl_dropdown.html.raw'); preloadFixtures('static/gl_dropdown.html.raw');
...@@ -138,13 +137,13 @@ describe('glDropdown', function describeDropdown() { ...@@ -138,13 +137,13 @@ describe('glDropdown', function describeDropdown() {
expect(this.dropdownContainerElement).toHaveClass('open'); expect(this.dropdownContainerElement).toHaveClass('open');
const randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0; const randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0;
navigateWithKeys('down', randomIndex, () => { navigateWithKeys('down', randomIndex, () => {
spyOn(urlUtils, 'visitUrl').and.stub(); const visitUrl = spyOnDependency(GLDropdown, 'visitUrl').and.stub();
navigateWithKeys('enter', null, () => { navigateWithKeys('enter', null, () => {
expect(this.dropdownContainerElement).not.toHaveClass('open'); expect(this.dropdownContainerElement).not.toHaveClass('open');
const link = $(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement); const link = $(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement);
expect(link).toHaveClass('is-active'); expect(link).toHaveClass('is-active');
const linkedLocation = link.attr('href'); const linkedLocation = link.attr('href');
if (linkedLocation && linkedLocation !== '#') expect(urlUtils.visitUrl).toHaveBeenCalledWith(linkedLocation); if (linkedLocation && linkedLocation !== '#') expect(visitUrl).toHaveBeenCalledWith(linkedLocation);
}); });
}); });
}); });
......
import $ from 'jquery'; import $ from 'jquery';
import Vue from 'vue'; import Vue from 'vue';
import * as utils from '~/lib/utils/url_utility';
import appComponent from '~/groups/components/app.vue'; import appComponent from '~/groups/components/app.vue';
import groupFolderComponent from '~/groups/components/group_folder.vue'; import groupFolderComponent from '~/groups/components/group_folder.vue';
import groupItemComponent from '~/groups/components/group_item.vue'; import groupItemComponent from '~/groups/components/group_item.vue';
...@@ -177,7 +176,7 @@ describe('AppComponent', () => { ...@@ -177,7 +176,7 @@ describe('AppComponent', () => {
it('should fetch groups for provided page details and update window state', (done) => { it('should fetch groups for provided page details and update window state', (done) => {
spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise(mockGroups)); spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise(mockGroups));
spyOn(vm, 'updateGroups').and.callThrough(); spyOn(vm, 'updateGroups').and.callThrough();
spyOn(utils, 'mergeUrlParams').and.callThrough(); const mergeUrlParams = spyOnDependency(appComponent, 'mergeUrlParams').and.callThrough();
spyOn(window.history, 'replaceState'); spyOn(window.history, 'replaceState');
spyOn($, 'scrollTo'); spyOn($, 'scrollTo');
...@@ -193,7 +192,7 @@ describe('AppComponent', () => { ...@@ -193,7 +192,7 @@ describe('AppComponent', () => {
setTimeout(() => { setTimeout(() => {
expect(vm.isLoading).toBe(false); expect(vm.isLoading).toBe(false);
expect($.scrollTo).toHaveBeenCalledWith(0); expect($.scrollTo).toHaveBeenCalledWith(0);
expect(utils.mergeUrlParams).toHaveBeenCalledWith({ page: 2 }, jasmine.any(String)); expect(mergeUrlParams).toHaveBeenCalledWith({ page: 2 }, jasmine.any(String));
expect(window.history.replaceState).toHaveBeenCalledWith({ expect(window.history.replaceState).toHaveBeenCalledWith({
page: jasmine.any(String), page: jasmine.any(String),
}, jasmine.any(String), jasmine.any(String)); }, jasmine.any(String), jasmine.any(String));
......
import Vue from 'vue'; import Vue from 'vue';
import * as urlUtils from '~/lib/utils/url_utility';
import groupItemComponent from '~/groups/components/group_item.vue'; import groupItemComponent from '~/groups/components/group_item.vue';
import groupFolderComponent from '~/groups/components/group_folder.vue'; import groupFolderComponent from '~/groups/components/group_folder.vue';
import eventHub from '~/groups/event_hub'; import eventHub from '~/groups/event_hub';
...@@ -135,13 +134,13 @@ describe('GroupItemComponent', () => { ...@@ -135,13 +134,13 @@ describe('GroupItemComponent', () => {
const group = Object.assign({}, mockParentGroupItem); const group = Object.assign({}, mockParentGroupItem);
group.childrenCount = 0; group.childrenCount = 0;
const newVm = createComponent(group); const newVm = createComponent(group);
spyOn(urlUtils, 'visitUrl').and.stub(); const visitUrl = spyOnDependency(groupItemComponent, 'visitUrl').and.stub();
spyOn(eventHub, '$emit'); spyOn(eventHub, '$emit');
newVm.onClickRowGroup(event); newVm.onClickRowGroup(event);
setTimeout(() => { setTimeout(() => {
expect(eventHub.$emit).not.toHaveBeenCalled(); expect(eventHub.$emit).not.toHaveBeenCalled();
expect(urlUtils.visitUrl).toHaveBeenCalledWith(newVm.group.relativePath); expect(visitUrl).toHaveBeenCalledWith(newVm.group.relativePath);
done(); done();
}, 0); }, 0);
}); });
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import './class_spec_helper'; import './class_spec_helper';
describe('ClassSpecHelper', () => { describe('ClassSpecHelper', function () {
describe('itShouldBeAStaticMethod', () => { describe('itShouldBeAStaticMethod', () => {
beforeEach(() => { beforeEach(() => {
class TestClass { class TestClass {
......
import * as urlUtils from '~/lib/utils/url_utility'; import actions, { stageAllChanges, unstageAllChanges, toggleFileFinder } from '~/ide/stores/actions';
import store from '~/ide/stores'; import store from '~/ide/stores';
import * as actions from '~/ide/stores/actions';
import * as types from '~/ide/stores/mutation_types'; import * as types from '~/ide/stores/mutation_types';
import router from '~/ide/ide_router'; import router from '~/ide/ide_router';
import { resetStore, file } from '../helpers'; import { resetStore, file } from '../helpers';
...@@ -17,12 +16,12 @@ describe('Multi-file store actions', () => { ...@@ -17,12 +16,12 @@ describe('Multi-file store actions', () => {
describe('redirectToUrl', () => { describe('redirectToUrl', () => {
it('calls visitUrl', done => { it('calls visitUrl', done => {
spyOn(urlUtils, 'visitUrl'); const visitUrl = spyOnDependency(actions, 'visitUrl');
store store
.dispatch('redirectToUrl', 'test') .dispatch('redirectToUrl', 'test')
.then(() => { .then(() => {
expect(urlUtils.visitUrl).toHaveBeenCalledWith('test'); expect(visitUrl).toHaveBeenCalledWith('test');
done(); done();
}) })
...@@ -298,7 +297,7 @@ describe('Multi-file store actions', () => { ...@@ -298,7 +297,7 @@ describe('Multi-file store actions', () => {
store.state.changedFiles.push(file(), file('new')); store.state.changedFiles.push(file(), file('new'));
testAction( testAction(
actions.stageAllChanges, stageAllChanges,
null, null,
store.state, store.state,
[ [
...@@ -316,7 +315,7 @@ describe('Multi-file store actions', () => { ...@@ -316,7 +315,7 @@ describe('Multi-file store actions', () => {
store.state.stagedFiles.push(file(), file('new')); store.state.stagedFiles.push(file(), file('new'));
testAction( testAction(
actions.unstageAllChanges, unstageAllChanges,
null, null,
store.state, store.state,
[ [
...@@ -344,7 +343,7 @@ describe('Multi-file store actions', () => { ...@@ -344,7 +343,7 @@ describe('Multi-file store actions', () => {
describe('toggleFileFinder', () => { describe('toggleFileFinder', () => {
it('commits TOGGLE_FILE_FINDER', done => { it('commits TOGGLE_FILE_FINDER', done => {
testAction( testAction(
actions.toggleFileFinder, toggleFileFinder,
true, true,
null, null,
[{ type: 'TOGGLE_FILE_FINDER', payload: true }], [{ type: 'TOGGLE_FILE_FINDER', payload: true }],
......
import actions from '~/ide/stores/actions';
import store from '~/ide/stores'; import store from '~/ide/stores';
import service from '~/ide/services'; import service from '~/ide/services';
import router from '~/ide/ide_router'; import router from '~/ide/ide_router';
import * as urlUtils from '~/lib/utils/url_utility';
import eventHub from '~/ide/eventhub'; import eventHub from '~/ide/eventhub';
import * as consts from '~/ide/stores/modules/commit/constants'; import * as consts from '~/ide/stores/modules/commit/constants';
import { resetStore, file } from 'spec/ide/helpers'; import { resetStore, file } from 'spec/ide/helpers';
...@@ -307,8 +307,10 @@ describe('IDE commit module actions', () => { ...@@ -307,8 +307,10 @@ describe('IDE commit module actions', () => {
}); });
describe('commitChanges', () => { describe('commitChanges', () => {
let visitUrl;
beforeEach(() => { beforeEach(() => {
spyOn(urlUtils, 'visitUrl'); visitUrl = spyOnDependency(actions, 'visitUrl');
document.body.innerHTML += '<div class="flash-container"></div>'; document.body.innerHTML += '<div class="flash-container"></div>';
...@@ -461,7 +463,7 @@ describe('IDE commit module actions', () => { ...@@ -461,7 +463,7 @@ describe('IDE commit module actions', () => {
store store
.dispatch('commit/commitChanges') .dispatch('commit/commitChanges')
.then(() => { .then(() => {
expect(urlUtils.visitUrl).toHaveBeenCalledWith( expect(visitUrl).toHaveBeenCalledWith(
`webUrl/merge_requests/new?merge_request[source_branch]=${ `webUrl/merge_requests/new?merge_request[source_branch]=${
store.getters['commit/newBranchName'] store.getters['commit/newBranchName']
}&merge_request[target_branch]=master`, }&merge_request[target_branch]=master`,
......
...@@ -2,7 +2,6 @@ import Vue from 'vue'; ...@@ -2,7 +2,6 @@ import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import '~/behaviors/markdown/render_gfm'; import '~/behaviors/markdown/render_gfm';
import * as urlUtils from '~/lib/utils/url_utility';
import issuableApp from '~/issue_show/components/app.vue'; import issuableApp from '~/issue_show/components/app.vue';
import eventHub from '~/issue_show/event_hub'; import eventHub from '~/issue_show/event_hub';
import setTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; import setTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
...@@ -174,7 +173,7 @@ describe('Issuable output', () => { ...@@ -174,7 +173,7 @@ describe('Issuable output', () => {
}); });
it('does not redirect if issue has not moved', (done) => { it('does not redirect if issue has not moved', (done) => {
spyOn(urlUtils, 'visitUrl'); const visitUrl = spyOnDependency(issuableApp, 'visitUrl');
spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => { spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
resolve({ resolve({
data: { data: {
...@@ -187,16 +186,13 @@ describe('Issuable output', () => { ...@@ -187,16 +186,13 @@ describe('Issuable output', () => {
vm.updateIssuable(); vm.updateIssuable();
setTimeout(() => { setTimeout(() => {
expect( expect(visitUrl).not.toHaveBeenCalled();
urlUtils.visitUrl,
).not.toHaveBeenCalled();
done(); done();
}); });
}); });
it('redirects if returned web_url has changed', (done) => { it('redirects if returned web_url has changed', (done) => {
spyOn(urlUtils, 'visitUrl'); const visitUrl = spyOnDependency(issuableApp, 'visitUrl');
spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => { spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
resolve({ resolve({
data: { data: {
...@@ -209,10 +205,7 @@ describe('Issuable output', () => { ...@@ -209,10 +205,7 @@ describe('Issuable output', () => {
vm.updateIssuable(); vm.updateIssuable();
setTimeout(() => { setTimeout(() => {
expect( expect(visitUrl).toHaveBeenCalledWith('/testing-issue-move');
urlUtils.visitUrl,
).toHaveBeenCalledWith('/testing-issue-move');
done(); done();
}); });
}); });
...@@ -340,7 +333,7 @@ describe('Issuable output', () => { ...@@ -340,7 +333,7 @@ describe('Issuable output', () => {
describe('deleteIssuable', () => { describe('deleteIssuable', () => {
it('changes URL when deleted', (done) => { it('changes URL when deleted', (done) => {
spyOn(urlUtils, 'visitUrl'); const visitUrl = spyOnDependency(issuableApp, 'visitUrl');
spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve) => { spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve) => {
resolve({ resolve({
data: { data: {
...@@ -352,16 +345,13 @@ describe('Issuable output', () => { ...@@ -352,16 +345,13 @@ describe('Issuable output', () => {
vm.deleteIssuable(); vm.deleteIssuable();
setTimeout(() => { setTimeout(() => {
expect( expect(visitUrl).toHaveBeenCalledWith('/test');
urlUtils.visitUrl,
).toHaveBeenCalledWith('/test');
done(); done();
}); });
}); });
it('stops polling when deleting', (done) => { it('stops polling when deleting', (done) => {
spyOn(urlUtils, 'visitUrl'); spyOnDependency(issuableApp, 'visitUrl');
spyOn(vm.poll, 'stop').and.callThrough(); spyOn(vm.poll, 'stop').and.callThrough();
spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve) => { spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve) => {
resolve({ resolve({
...@@ -377,7 +367,6 @@ describe('Issuable output', () => { ...@@ -377,7 +367,6 @@ describe('Issuable output', () => {
expect( expect(
vm.poll.stop, vm.poll.stop,
).toHaveBeenCalledWith(); ).toHaveBeenCalledWith();
done(); done();
}); });
}); });
......
import $ from 'jquery'; import $ from 'jquery';
import Vue from 'vue'; import Vue from 'vue';
import descriptionComponent from '~/issue_show/components/description.vue'; import Description from '~/issue_show/components/description.vue';
import * as taskList from '~/task_list';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Description component', () => { describe('Description component', () => {
...@@ -17,7 +16,7 @@ describe('Description component', () => { ...@@ -17,7 +16,7 @@ describe('Description component', () => {
}; };
beforeEach(() => { beforeEach(() => {
DescriptionComponent = Vue.extend(descriptionComponent); DescriptionComponent = Vue.extend(Description);
if (!document.querySelector('.issuable-meta')) { if (!document.querySelector('.issuable-meta')) {
const metaData = document.createElement('div'); const metaData = document.createElement('div');
...@@ -82,18 +81,20 @@ describe('Description component', () => { ...@@ -82,18 +81,20 @@ describe('Description component', () => {
}); });
describe('TaskList', () => { describe('TaskList', () => {
let TaskList;
beforeEach(() => { beforeEach(() => {
vm = mountComponent(DescriptionComponent, Object.assign({}, props, { vm = mountComponent(DescriptionComponent, Object.assign({}, props, {
issuableType: 'issuableType', issuableType: 'issuableType',
})); }));
spyOn(taskList, 'default'); TaskList = spyOnDependency(Description, 'TaskList');
}); });
it('re-inits the TaskList when description changed', (done) => { it('re-inits the TaskList when description changed', (done) => {
vm.descriptionHtml = 'changed'; vm.descriptionHtml = 'changed';
setTimeout(() => { setTimeout(() => {
expect(taskList.default).toHaveBeenCalled(); expect(TaskList).toHaveBeenCalled();
done(); done();
}); });
}); });
...@@ -103,7 +104,7 @@ describe('Description component', () => { ...@@ -103,7 +104,7 @@ describe('Description component', () => {
vm.descriptionHtml = 'changed'; vm.descriptionHtml = 'changed';
setTimeout(() => { setTimeout(() => {
expect(taskList.default).not.toHaveBeenCalled(); expect(TaskList).not.toHaveBeenCalled();
done(); done();
}); });
}); });
...@@ -112,7 +113,7 @@ describe('Description component', () => { ...@@ -112,7 +113,7 @@ describe('Description component', () => {
vm.descriptionHtml = 'changed'; vm.descriptionHtml = 'changed';
setTimeout(() => { setTimeout(() => {
expect(taskList.default).toHaveBeenCalledWith({ expect(TaskList).toHaveBeenCalledWith({
dataType: 'issuableType', dataType: 'issuableType',
fieldName: 'description', fieldName: 'description',
selector: '.detail-page-description', selector: '.detail-page-description',
......
...@@ -2,7 +2,6 @@ import $ from 'jquery'; ...@@ -2,7 +2,6 @@ import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { numberToHumanSize } from '~/lib/utils/number_utils'; import { numberToHumanSize } from '~/lib/utils/number_utils';
import * as urlUtils from '~/lib/utils/url_utility';
import '~/lib/utils/datetime_utility'; import '~/lib/utils/datetime_utility';
import Job from '~/job'; import Job from '~/job';
import '~/breakpoints'; import '~/breakpoints';
...@@ -22,7 +21,7 @@ describe('Job', () => { ...@@ -22,7 +21,7 @@ describe('Job', () => {
beforeEach(() => { beforeEach(() => {
loadFixtures('builds/build-with-artifacts.html.raw'); loadFixtures('builds/build-with-artifacts.html.raw');
spyOn(urlUtils, 'visitUrl'); spyOnDependency(Job, 'visitUrl');
response = {}; response = {};
......
import csrf from '~/lib/utils/csrf'; import csrf from '~/lib/utils/csrf';
describe('csrf', () => { describe('csrf', function () {
beforeEach(() => { beforeEach(() => {
this.tokenKey = 'X-CSRF-Token'; this.tokenKey = 'X-CSRF-Token';
this.token = 'pH1cvjnP9grx2oKlhWEDvUZnJ8x2eXsIs1qzyHkF3DugSG5yTxR76CWeEZRhML2D1IeVB7NEW0t5l/axE4iJpQ=='; this.token = 'pH1cvjnP9grx2oKlhWEDvUZnJ8x2eXsIs1qzyHkF3DugSG5yTxR76CWeEZRhML2D1IeVB7NEW0t5l/axE4iJpQ==';
......
import * as imageUtility from '~/lib/utils/image_utility'; import { isImageLoaded } from '~/lib/utils/image_utility';
describe('imageUtility', () => { describe('imageUtility', () => {
describe('isImageLoaded', () => { describe('isImageLoaded', () => {
...@@ -8,7 +8,7 @@ describe('imageUtility', () => { ...@@ -8,7 +8,7 @@ describe('imageUtility', () => {
naturalHeight: 100, naturalHeight: 100,
}; };
expect(imageUtility.isImageLoaded(element)).toEqual(false); expect(isImageLoaded(element)).toEqual(false);
}); });
it('should return false when naturalHeight = 0', () => { it('should return false when naturalHeight = 0', () => {
...@@ -17,7 +17,7 @@ describe('imageUtility', () => { ...@@ -17,7 +17,7 @@ describe('imageUtility', () => {
naturalHeight: 0, naturalHeight: 0,
}; };
expect(imageUtility.isImageLoaded(element)).toEqual(false); expect(isImageLoaded(element)).toEqual(false);
}); });
it('should return true when image.complete and naturalHeight != 0', () => { it('should return true when image.complete and naturalHeight != 0', () => {
...@@ -26,7 +26,7 @@ describe('imageUtility', () => { ...@@ -26,7 +26,7 @@ describe('imageUtility', () => {
naturalHeight: 100, naturalHeight: 100,
}; };
expect(imageUtility.isImageLoaded(element)).toEqual(true); expect(isImageLoaded(element)).toEqual(true);
}); });
}); });
}); });
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
import $ from 'jquery'; import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import * as urlUtils from '~/lib/utils/url_utility';
import MergeRequestTabs from '~/merge_request_tabs'; import MergeRequestTabs from '~/merge_request_tabs';
import '~/commit/pipelines/pipelines_bundle'; import '~/commit/pipelines/pipelines_bundle';
import '~/breakpoints'; import '~/breakpoints';
...@@ -356,7 +355,7 @@ import 'vendor/jquery.scrollTo'; ...@@ -356,7 +355,7 @@ import 'vendor/jquery.scrollTo';
describe('with note fragment hash', () => { describe('with note fragment hash', () => {
it('should expand and scroll to linked fragment hash #note_xxx', function (done) { it('should expand and scroll to linked fragment hash #note_xxx', function (done) {
spyOn(urlUtils, 'getLocationHash').and.returnValue(noteId); spyOnDependency(MergeRequestTabs, 'getLocationHash').and.returnValue(noteId);
this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
setTimeout(() => { setTimeout(() => {
...@@ -372,7 +371,7 @@ import 'vendor/jquery.scrollTo'; ...@@ -372,7 +371,7 @@ import 'vendor/jquery.scrollTo';
}); });
it('should gracefully ignore non-existant fragment hash', function (done) { it('should gracefully ignore non-existant fragment hash', function (done) {
spyOn(urlUtils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist'); spyOnDependency(MergeRequestTabs, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
setTimeout(() => { setTimeout(() => {
...@@ -385,7 +384,7 @@ import 'vendor/jquery.scrollTo'; ...@@ -385,7 +384,7 @@ import 'vendor/jquery.scrollTo';
describe('with line number fragment hash', () => { describe('with line number fragment hash', () => {
it('should gracefully ignore line number fragment hash', function () { it('should gracefully ignore line number fragment hash', function () {
spyOn(urlUtils, 'getLocationHash').and.returnValue(noteLineNumId); spyOnDependency(MergeRequestTabs, 'getLocationHash').and.returnValue(noteLineNumId);
this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(noteLineNumId.length).toBeGreaterThan(0); expect(noteLineNumId.length).toBeGreaterThan(0);
...@@ -422,7 +421,7 @@ import 'vendor/jquery.scrollTo'; ...@@ -422,7 +421,7 @@ import 'vendor/jquery.scrollTo';
describe('with note fragment hash', () => { describe('with note fragment hash', () => {
it('should expand and scroll to linked fragment hash #note_xxx', function (done) { it('should expand and scroll to linked fragment hash #note_xxx', function (done) {
spyOn(urlUtils, 'getLocationHash').and.returnValue(noteId); spyOnDependency(MergeRequestTabs, 'getLocationHash').and.returnValue(noteId);
this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
...@@ -439,7 +438,7 @@ import 'vendor/jquery.scrollTo'; ...@@ -439,7 +438,7 @@ import 'vendor/jquery.scrollTo';
}); });
it('should gracefully ignore non-existant fragment hash', function (done) { it('should gracefully ignore non-existant fragment hash', function (done) {
spyOn(urlUtils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist'); spyOnDependency(MergeRequestTabs, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
setTimeout(() => { setTimeout(() => {
...@@ -451,7 +450,7 @@ import 'vendor/jquery.scrollTo'; ...@@ -451,7 +450,7 @@ import 'vendor/jquery.scrollTo';
describe('with line number fragment hash', () => { describe('with line number fragment hash', () => {
it('should gracefully ignore line number fragment hash', function () { it('should gracefully ignore line number fragment hash', function () {
spyOn(urlUtils, 'getLocationHash').and.returnValue(noteLineNumId); spyOnDependency(MergeRequestTabs, 'getLocationHash').and.returnValue(noteLineNumId);
this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
expect(noteLineNumId.length).toBeGreaterThan(0); expect(noteLineNumId.length).toBeGreaterThan(0);
......
import MonitoringStore from '~/monitoring/stores/monitoring_store'; import MonitoringStore from '~/monitoring/stores/monitoring_store';
import MonitoringMock, { deploymentData } from './mock_data'; import MonitoringMock, { deploymentData } from './mock_data';
describe('MonitoringStore', () => { describe('MonitoringStore', function () {
this.store = new MonitoringStore(); this.store = new MonitoringStore();
this.store.storeMetrics(MonitoringMock.data); this.store.storeMetrics(MonitoringMock.data);
......
...@@ -3,7 +3,6 @@ import $ from 'jquery'; ...@@ -3,7 +3,6 @@ import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import * as urlUtils from '~/lib/utils/url_utility';
import 'autosize'; import 'autosize';
import '~/gl_form'; import '~/gl_form';
import '~/lib/utils/text_utility'; import '~/lib/utils/text_utility';
...@@ -222,7 +221,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; ...@@ -222,7 +221,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
}); });
it('sets target when hash matches', () => { it('sets target when hash matches', () => {
spyOn(urlUtils, 'getLocationHash').and.returnValue(hash); spyOnDependency(Notes, 'getLocationHash').and.returnValue(hash);
Notes.updateNoteTargetSelector($note); Notes.updateNoteTargetSelector($note);
...@@ -231,7 +230,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; ...@@ -231,7 +230,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
}); });
it('unsets target when hash does not match', () => { it('unsets target when hash does not match', () => {
spyOn(urlUtils, 'getLocationHash').and.returnValue('note_doesnotexist'); spyOnDependency(Notes, 'getLocationHash').and.returnValue('note_doesnotexist');
Notes.updateNoteTargetSelector($note); Notes.updateNoteTargetSelector($note);
...@@ -239,7 +238,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; ...@@ -239,7 +238,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
}); });
it('unsets target when there is not a hash fragment anymore', () => { it('unsets target when there is not a hash fragment anymore', () => {
spyOn(urlUtils, 'getLocationHash').and.returnValue(null); spyOnDependency(Notes, 'getLocationHash').and.returnValue(null);
Notes.updateNoteTargetSelector($note); Notes.updateNoteTargetSelector($note);
......
/* global fixture */ import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import * as utils from '~/lib/utils/url_utility';
import Pager from '~/pager'; import Pager from '~/pager';
describe('pager', () => { describe('pager', () => {
let axiosMock;
beforeEach(() => {
axiosMock = new MockAdapter(axios);
});
afterEach(() => {
axiosMock.restore();
});
describe('init', () => { describe('init', () => {
const originalHref = window.location.href; const originalHref = window.location.href;
beforeEach(() => { beforeEach(() => {
setFixtures('<div class="content_list"></div><div class="loading"></div>'); setFixtures('<div class="content_list"></div><div class="loading"></div>');
spyOn($.fn, 'endlessScroll').and.stub();
}); });
afterEach(() => { afterEach(() => {
...@@ -25,7 +35,7 @@ describe('pager', () => { ...@@ -25,7 +35,7 @@ describe('pager', () => {
it('should use current url if data-href attribute not provided', () => { it('should use current url if data-href attribute not provided', () => {
const href = `${gl.TEST_HOST}/some_list`; const href = `${gl.TEST_HOST}/some_list`;
spyOn(utils, 'removeParams').and.returnValue(href); spyOnDependency(Pager, 'removeParams').and.returnValue(href);
Pager.init(); Pager.init();
expect(Pager.url).toBe(href); expect(Pager.url).toBe(href);
}); });
...@@ -39,42 +49,37 @@ describe('pager', () => { ...@@ -39,42 +49,37 @@ describe('pager', () => {
it('keeps extra query parameters from url', () => { it('keeps extra query parameters from url', () => {
window.history.replaceState({}, null, '?filter=test&offset=100'); window.history.replaceState({}, null, '?filter=test&offset=100');
const href = `${gl.TEST_HOST}/some_list?filter=test`; const href = `${gl.TEST_HOST}/some_list?filter=test`;
spyOn(utils, 'removeParams').and.returnValue(href); const removeParams = spyOnDependency(Pager, 'removeParams').and.returnValue(href);
Pager.init(); Pager.init();
expect(utils.removeParams).toHaveBeenCalledWith(['limit', 'offset']); expect(removeParams).toHaveBeenCalledWith(['limit', 'offset']);
expect(Pager.url).toEqual(href); expect(Pager.url).toEqual(href);
}); });
}); });
describe('getOld', () => { describe('getOld', () => {
const urlRegex = /(.*)some_list(.*)$/; const urlRegex = /(.*)some_list(.*)$/;
let mock;
function mockSuccess() { function mockSuccess() {
mock.onGet(urlRegex).reply(200, { axiosMock.onGet(urlRegex).reply(200, {
count: 0, count: 0,
html: '', html: '',
}); });
} }
function mockError() { function mockError() {
mock.onGet(urlRegex).networkError(); axiosMock.onGet(urlRegex).networkError();
} }
beforeEach(() => { beforeEach(() => {
setFixtures('<div class="content_list" data-href="/some_list"></div><div class="loading"></div>'); setFixtures(
'<div class="content_list" data-href="/some_list"></div><div class="loading"></div>',
);
spyOn(axios, 'get').and.callThrough(); spyOn(axios, 'get').and.callThrough();
mock = new MockAdapter(axios);
Pager.init(); Pager.init();
}); });
afterEach(() => { it('shows loader while loading next page', done => {
mock.restore();
});
it('shows loader while loading next page', (done) => {
mockSuccess(); mockSuccess();
spyOn(Pager.loading, 'show'); spyOn(Pager.loading, 'show');
...@@ -87,7 +92,7 @@ describe('pager', () => { ...@@ -87,7 +92,7 @@ describe('pager', () => {
}); });
}); });
it('hides loader on success', (done) => { it('hides loader on success', done => {
mockSuccess(); mockSuccess();
spyOn(Pager.loading, 'hide'); spyOn(Pager.loading, 'hide');
...@@ -100,7 +105,7 @@ describe('pager', () => { ...@@ -100,7 +105,7 @@ describe('pager', () => {
}); });
}); });
it('hides loader on error', (done) => { it('hides loader on error', done => {
mockError(); mockError();
spyOn(Pager.loading, 'hide'); spyOn(Pager.loading, 'hide');
...@@ -113,7 +118,7 @@ describe('pager', () => { ...@@ -113,7 +118,7 @@ describe('pager', () => {
}); });
}); });
it('sends request to url with offset and limit params', (done) => { it('sends request to url with offset and limit params', done => {
Pager.offset = 100; Pager.offset = 100;
Pager.limit = 20; Pager.limit = 20;
Pager.getOld(); Pager.getOld();
......
...@@ -2,7 +2,6 @@ import Vue from 'vue'; ...@@ -2,7 +2,6 @@ import Vue from 'vue';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import stopJobsModal from '~/pages/admin/jobs/index/components/stop_jobs_modal.vue'; import stopJobsModal from '~/pages/admin/jobs/index/components/stop_jobs_modal.vue';
import * as urlUtility from '~/lib/utils/url_utility';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
...@@ -24,7 +23,7 @@ describe('stop_jobs_modal.vue', () => { ...@@ -24,7 +23,7 @@ describe('stop_jobs_modal.vue', () => {
describe('onSubmit', () => { describe('onSubmit', () => {
it('stops jobs and redirects to overview page', (done) => { it('stops jobs and redirects to overview page', (done) => {
const responseURL = `${gl.TEST_HOST}/stop_jobs_modal.vue/jobs`; const responseURL = `${gl.TEST_HOST}/stop_jobs_modal.vue/jobs`;
const redirectSpy = spyOn(urlUtility, 'redirectTo'); const redirectSpy = spyOnDependency(stopJobsModal, 'redirectTo');
spyOn(axios, 'post').and.callFake((url) => { spyOn(axios, 'post').and.callFake((url) => {
expect(url).toBe(props.url); expect(url).toBe(props.url);
return Promise.resolve({ return Promise.resolve({
...@@ -44,7 +43,7 @@ describe('stop_jobs_modal.vue', () => { ...@@ -44,7 +43,7 @@ describe('stop_jobs_modal.vue', () => {
it('displays error if stopping jobs failed', (done) => { it('displays error if stopping jobs failed', (done) => {
const dummyError = new Error('stopping jobs failed'); const dummyError = new Error('stopping jobs failed');
const redirectSpy = spyOn(urlUtility, 'redirectTo'); const redirectSpy = spyOnDependency(stopJobsModal, 'redirectTo');
spyOn(axios, 'post').and.callFake((url) => { spyOn(axios, 'post').and.callFake((url) => {
expect(url).toBe(props.url); expect(url).toBe(props.url);
return Promise.reject(dummyError); return Promise.reject(dummyError);
......
...@@ -3,7 +3,6 @@ import Vue from 'vue'; ...@@ -3,7 +3,6 @@ import Vue from 'vue';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import deleteMilestoneModal from '~/pages/milestones/shared/components/delete_milestone_modal.vue'; import deleteMilestoneModal from '~/pages/milestones/shared/components/delete_milestone_modal.vue';
import eventHub from '~/pages/milestones/shared/event_hub'; import eventHub from '~/pages/milestones/shared/event_hub';
import * as urlUtility from '~/lib/utils/url_utility';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
...@@ -40,7 +39,7 @@ describe('delete_milestone_modal.vue', () => { ...@@ -40,7 +39,7 @@ describe('delete_milestone_modal.vue', () => {
}, },
}); });
}); });
const redirectSpy = spyOn(urlUtility, 'redirectTo'); const redirectSpy = spyOnDependency(deleteMilestoneModal, 'redirectTo');
vm.onSubmit() vm.onSubmit()
.then(() => { .then(() => {
...@@ -60,7 +59,7 @@ describe('delete_milestone_modal.vue', () => { ...@@ -60,7 +59,7 @@ describe('delete_milestone_modal.vue', () => {
eventHub.$emit.calls.reset(); eventHub.$emit.calls.reset();
return Promise.reject(dummyError); return Promise.reject(dummyError);
}); });
const redirectSpy = spyOn(urlUtility, 'redirectTo'); const redirectSpy = spyOnDependency(deleteMilestoneModal, 'redirectTo');
vm.onSubmit() vm.onSubmit()
.catch((error) => { .catch((error) => {
......
...@@ -6,7 +6,7 @@ const PipelineSchedulesCalloutComponent = Vue.extend(PipelineSchedulesCallout); ...@@ -6,7 +6,7 @@ const PipelineSchedulesCalloutComponent = Vue.extend(PipelineSchedulesCallout);
const cookieKey = 'pipeline_schedules_callout_dismissed'; const cookieKey = 'pipeline_schedules_callout_dismissed';
const docsUrl = 'help/ci/scheduled_pipelines'; const docsUrl = 'help/ci/scheduled_pipelines';
describe('Pipeline Schedule Callout', () => { describe('Pipeline Schedule Callout', function () {
beforeEach(() => { beforeEach(() => {
setFixtures(` setFixtures(`
<div id='pipeline-schedules-callout' data-docs-url=${docsUrl}></div> <div id='pipeline-schedules-callout' data-docs-url=${docsUrl}></div>
......
...@@ -9,8 +9,6 @@ import Sidebar from '~/right_sidebar'; ...@@ -9,8 +9,6 @@ import Sidebar from '~/right_sidebar';
(function() { (function() {
var $aside, $icon, $labelsIcon, $page, $toggle, assertSidebarState; var $aside, $icon, $labelsIcon, $page, $toggle, assertSidebarState;
this.sidebar = null;
$aside = null; $aside = null;
$toggle = null; $toggle = null;
...@@ -43,7 +41,7 @@ import Sidebar from '~/right_sidebar'; ...@@ -43,7 +41,7 @@ import Sidebar from '~/right_sidebar';
beforeEach(function() { beforeEach(function() {
loadFixtures(fixtureName); loadFixtures(fixtureName);
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
this.sidebar = new Sidebar(); new Sidebar(); // eslint-disable-line no-new
$aside = $('.right-sidebar'); $aside = $('.right-sidebar');
$page = $('.layout-page'); $page = $('.layout-page');
$icon = $aside.find('i'); $icon = $aside.find('i');
......
...@@ -4,7 +4,6 @@ import $ from 'jquery'; ...@@ -4,7 +4,6 @@ import $ from 'jquery';
import '~/gl_dropdown'; import '~/gl_dropdown';
import SearchAutocomplete from '~/search_autocomplete'; import SearchAutocomplete from '~/search_autocomplete';
import '~/lib/utils/common_utils'; import '~/lib/utils/common_utils';
import * as urlUtils from '~/lib/utils/url_utility';
describe('Search autocomplete dropdown', () => { describe('Search autocomplete dropdown', () => {
var assertLinks, var assertLinks,
...@@ -129,9 +128,6 @@ describe('Search autocomplete dropdown', () => { ...@@ -129,9 +128,6 @@ describe('Search autocomplete dropdown', () => {
beforeEach(function() { beforeEach(function() {
loadFixtures('static/search_autocomplete.html.raw'); loadFixtures('static/search_autocomplete.html.raw');
// Prevent turbolinks from triggering within gl_dropdown
spyOn(urlUtils, 'visitUrl').and.returnValue(true);
window.gon = {}; window.gon = {};
window.gon.current_user_id = userId; window.gon.current_user_id = userId;
window.gon.current_username = userName; window.gon.current_username = userName;
......
import findAndFollowLink from '~/shortcuts_dashboard_navigation'; import findAndFollowLink from '~/shortcuts_dashboard_navigation';
import * as urlUtility from '~/lib/utils/url_utility';
describe('findAndFollowLink', () => { describe('findAndFollowLink', () => {
it('visits a link when the selector exists', () => { it('visits a link when the selector exists', () => {
const href = '/some/path'; const href = '/some/path';
const locationSpy = spyOn(urlUtility, 'visitUrl'); const visitUrl = spyOnDependency(findAndFollowLink, 'visitUrl');
setFixtures(`<a class="my-shortcut" href="${href}">link</a>`); setFixtures(`<a class="my-shortcut" href="${href}">link</a>`);
findAndFollowLink('.my-shortcut'); findAndFollowLink('.my-shortcut');
expect(locationSpy).toHaveBeenCalledWith(href); expect(visitUrl).toHaveBeenCalledWith(href);
}); });
it('does not throw an exception when the selector does not exist', () => { it('does not throw an exception when the selector does not exist', () => {
const locationSpy = spyOn(urlUtility, 'visitUrl'); const visitUrl = spyOnDependency(findAndFollowLink, 'visitUrl');
// this should not throw an exception // this should not throw an exception
findAndFollowLink('.this-selector-does-not-exist'); findAndFollowLink('.this-selector-does-not-exist');
expect(locationSpy).not.toHaveBeenCalled(); expect(visitUrl).not.toHaveBeenCalled();
}); });
}); });
...@@ -4,7 +4,7 @@ import ShortcutsIssuable from '~/shortcuts_issuable'; ...@@ -4,7 +4,7 @@ import ShortcutsIssuable from '~/shortcuts_issuable';
initCopyAsGFM(); initCopyAsGFM();
describe('ShortcutsIssuable', () => { describe('ShortcutsIssuable', function () {
const fixtureName = 'merge_requests/diff_comment.html.raw'; const fixtureName = 'merge_requests/diff_comment.html.raw';
preloadFixtures(fixtureName); preloadFixtures(fixtureName);
beforeEach(() => { beforeEach(() => {
......
import _ from 'underscore'; import _ from 'underscore';
import Vue from 'vue'; import Vue from 'vue';
import * as urlUtils from '~/lib/utils/url_utility';
import SidebarMediator from '~/sidebar/sidebar_mediator'; import SidebarMediator from '~/sidebar/sidebar_mediator';
import SidebarStore from '~/sidebar/stores/sidebar_store'; import SidebarStore from '~/sidebar/stores/sidebar_store';
import SidebarService from '~/sidebar/services/sidebar_service'; import SidebarService from '~/sidebar/services/sidebar_service';
import Mock from './mock_data'; import Mock from './mock_data';
describe('Sidebar mediator', () => { describe('Sidebar mediator', function() {
beforeEach(() => { beforeEach(() => {
Vue.http.interceptors.push(Mock.sidebarMockInterceptor); Vue.http.interceptors.push(Mock.sidebarMockInterceptor);
this.mediator = new SidebarMediator(Mock.mediator); this.mediator = new SidebarMediator(Mock.mediator);
...@@ -87,12 +86,12 @@ describe('Sidebar mediator', () => { ...@@ -87,12 +86,12 @@ describe('Sidebar mediator', () => {
const moveToProjectId = 7; const moveToProjectId = 7;
this.mediator.store.setMoveToProjectId(moveToProjectId); this.mediator.store.setMoveToProjectId(moveToProjectId);
spyOn(this.mediator.service, 'moveIssue').and.callThrough(); spyOn(this.mediator.service, 'moveIssue').and.callThrough();
spyOn(urlUtils, 'visitUrl'); const visitUrl = spyOnDependency(SidebarMediator, 'visitUrl');
this.mediator.moveIssue() this.mediator.moveIssue()
.then(() => { .then(() => {
expect(this.mediator.service.moveIssue).toHaveBeenCalledWith(moveToProjectId); expect(this.mediator.service.moveIssue).toHaveBeenCalledWith(moveToProjectId);
expect(urlUtils.visitUrl).toHaveBeenCalledWith('/root/some-project/issues/5'); expect(visitUrl).toHaveBeenCalledWith('/root/some-project/issues/5');
}) })
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
......
...@@ -7,7 +7,7 @@ import SidebarService from '~/sidebar/services/sidebar_service'; ...@@ -7,7 +7,7 @@ import SidebarService from '~/sidebar/services/sidebar_service';
import SidebarMoveIssue from '~/sidebar/lib/sidebar_move_issue'; import SidebarMoveIssue from '~/sidebar/lib/sidebar_move_issue';
import Mock from './mock_data'; import Mock from './mock_data';
describe('SidebarMoveIssue', () => { describe('SidebarMoveIssue', function () {
beforeEach(() => { beforeEach(() => {
Vue.http.interceptors.push(Mock.sidebarMockInterceptor); Vue.http.interceptors.push(Mock.sidebarMockInterceptor);
this.mediator = new SidebarMediator(Mock.mediator); this.mediator = new SidebarMediator(Mock.mediator);
......
...@@ -31,7 +31,7 @@ const PARTICIPANT_LIST = [ ...@@ -31,7 +31,7 @@ const PARTICIPANT_LIST = [
{ ...PARTICIPANT, id: 3 }, { ...PARTICIPANT, id: 3 },
]; ];
describe('Sidebar store', () => { describe('Sidebar store', function () {
beforeEach(() => { beforeEach(() => {
this.store = new SidebarStore({ this.store = new SidebarStore({
currentUser: { currentUser: {
......
/* eslint-disable jasmine/no-global-setup */ /* eslint-disable jasmine/no-global-setup, jasmine/no-unsafe-spy, no-underscore-dangle */
import $ from 'jquery'; import $ from 'jquery';
import 'vendor/jasmine-jquery'; import 'vendor/jasmine-jquery';
import '~/commons'; import '~/commons';
...@@ -55,6 +56,17 @@ window.addEventListener('unhandledrejection', event => { ...@@ -55,6 +56,17 @@ window.addEventListener('unhandledrejection', event => {
console.error(event.reason.stack || event.reason); console.error(event.reason.stack || event.reason);
}); });
// Add global function to spy on a module's dependencies via rewire
window.spyOnDependency = (module, name) => {
const dependency = module.__GetDependency__(name);
const spy = jasmine.createSpy(name, dependency);
module.__Rewire__(name, spy);
return spy;
};
// Reset any rewired modules after each test (see babel-plugin-rewire)
afterEach(__rewire_reset_all__); // eslint-disable-line
// HACK: Chrome 59 disconnects if there are too many synchronous tests in a row // HACK: Chrome 59 disconnects if there are too many synchronous tests in a row
// because it appears to lock up the thread that communicates to Karma's socket // because it appears to lock up the thread that communicates to Karma's socket
// This async beforeEach gets called on every spec and releases the JS thread long // This async beforeEach gets called on every spec and releases the JS thread long
......
import $ from 'jquery'; import $ from 'jquery';
import * as urlUtils from '~/lib/utils/url_utility';
import Todos from '~/pages/dashboard/todos/index/todos'; import Todos from '~/pages/dashboard/todos/index/todos';
import '~/lib/utils/common_utils'; import '~/lib/utils/common_utils';
...@@ -18,7 +17,7 @@ describe('Todos', () => { ...@@ -18,7 +17,7 @@ describe('Todos', () => {
it('opens the todo url', (done) => { it('opens the todo url', (done) => {
const todoLink = todoItem.dataset.url; const todoLink = todoItem.dataset.url;
spyOn(urlUtils, 'visitUrl').and.callFake((url) => { spyOnDependency(Todos, 'visitUrl').and.callFake((url) => {
expect(url).toEqual(todoLink); expect(url).toEqual(todoLink);
done(); done();
}); });
...@@ -33,7 +32,7 @@ describe('Todos', () => { ...@@ -33,7 +32,7 @@ describe('Todos', () => {
beforeEach(() => { beforeEach(() => {
metakeyEvent = $.Event('click', { keyCode: 91, ctrlKey: true }); metakeyEvent = $.Event('click', { keyCode: 91, ctrlKey: true });
visitUrlSpy = spyOn(urlUtils, 'visitUrl').and.callFake(() => {}); visitUrlSpy = spyOnDependency(Todos, 'visitUrl').and.callFake(() => {});
windowOpenSpy = spyOn(window, 'open').and.callFake(() => {}); windowOpenSpy = spyOn(window, 'open').and.callFake(() => {});
}); });
......
...@@ -3,7 +3,7 @@ import U2FAuthenticate from '~/u2f/authenticate'; ...@@ -3,7 +3,7 @@ import U2FAuthenticate from '~/u2f/authenticate';
import 'vendor/u2f'; import 'vendor/u2f';
import MockU2FDevice from './mock_u2f_device'; import MockU2FDevice from './mock_u2f_device';
describe('U2FAuthenticate', () => { describe('U2FAuthenticate', function () {
preloadFixtures('u2f/authenticate.html.raw'); preloadFixtures('u2f/authenticate.html.raw');
beforeEach((done) => { beforeEach((done) => {
......
...@@ -3,7 +3,7 @@ import U2FRegister from '~/u2f/register'; ...@@ -3,7 +3,7 @@ import U2FRegister from '~/u2f/register';
import 'vendor/u2f'; import 'vendor/u2f';
import MockU2FDevice from './mock_u2f_device'; import MockU2FDevice from './mock_u2f_device';
describe('U2FRegister', () => { describe('U2FRegister', function () {
preloadFixtures('u2f/register.html.raw'); preloadFixtures('u2f/register.html.raw');
beforeEach((done) => { beforeEach((done) => {
......
import Vue from 'vue'; import Vue from 'vue';
import * as urlUtils from '~/lib/utils/url_utility';
import deploymentComponent from '~/vue_merge_request_widget/components/deployment.vue'; import deploymentComponent from '~/vue_merge_request_widget/components/deployment.vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
import { getTimeago } from '~/lib/utils/datetime_utility'; import { getTimeago } from '~/lib/utils/datetime_utility';
...@@ -117,13 +116,13 @@ describe('Deployment component', () => { ...@@ -117,13 +116,13 @@ describe('Deployment component', () => {
it('should show a confirm dialog and call service.stopEnvironment when confirmed', (done) => { it('should show a confirm dialog and call service.stopEnvironment when confirmed', (done) => {
spyOn(window, 'confirm').and.returnValue(true); spyOn(window, 'confirm').and.returnValue(true);
spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true)); spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true));
spyOn(urlUtils, 'visitUrl').and.returnValue(true); const visitUrl = spyOnDependency(deploymentComponent, 'visitUrl').and.returnValue(true);
vm = mockStopEnvironment(); vm = mockStopEnvironment();
expect(window.confirm).toHaveBeenCalled(); expect(window.confirm).toHaveBeenCalled();
expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url); expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url);
setTimeout(() => { setTimeout(() => {
expect(urlUtils.visitUrl).toHaveBeenCalledWith(url); expect(visitUrl).toHaveBeenCalledWith(url);
done(); done();
}, 333); }, 333);
}); });
......
import Vue from 'vue'; import Vue from 'vue';
import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue'; import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue';
import eventHub from '~/vue_merge_request_widget/event_hub'; import eventHub from '~/vue_merge_request_widget/event_hub';
import * as simplePoll from '~/lib/utils/simple_poll';
const commitMessage = 'This is the commit message'; const commitMessage = 'This is the commit message';
const commitMessageWithDescription = 'This is the commit message description'; const commitMessageWithDescription = 'This is the commit message description';
...@@ -355,9 +354,9 @@ describe('ReadyToMerge', () => { ...@@ -355,9 +354,9 @@ describe('ReadyToMerge', () => {
describe('initiateMergePolling', () => { describe('initiateMergePolling', () => {
it('should call simplePoll', () => { it('should call simplePoll', () => {
spyOn(simplePoll, 'default'); const simplePoll = spyOnDependency(ReadyToMerge, 'simplePoll');
vm.initiateMergePolling(); vm.initiateMergePolling();
expect(simplePoll.default).toHaveBeenCalled(); expect(simplePoll).toHaveBeenCalled();
}); });
}); });
...@@ -457,11 +456,11 @@ describe('ReadyToMerge', () => { ...@@ -457,11 +456,11 @@ describe('ReadyToMerge', () => {
describe('initiateRemoveSourceBranchPolling', () => { describe('initiateRemoveSourceBranchPolling', () => {
it('should emit event and call simplePoll', () => { it('should emit event and call simplePoll', () => {
spyOn(eventHub, '$emit'); spyOn(eventHub, '$emit');
spyOn(simplePoll, 'default'); const simplePoll = spyOnDependency(ReadyToMerge, 'simplePoll');
vm.initiateRemoveSourceBranchPolling(); vm.initiateRemoveSourceBranchPolling();
expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [true]); expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [true]);
expect(simplePoll.default).toHaveBeenCalled(); expect(simplePoll).toHaveBeenCalled();
}); });
}); });
...@@ -524,18 +523,20 @@ describe('ReadyToMerge', () => { ...@@ -524,18 +523,20 @@ describe('ReadyToMerge', () => {
}); });
describe('when user can merge and can delete branch', () => { describe('when user can merge and can delete branch', () => {
let customVm;
beforeEach(() => { beforeEach(() => {
this.customVm = createComponent({ customVm = createComponent({
mr: { canRemoveSourceBranch: true }, mr: { canRemoveSourceBranch: true },
}); });
}); });
it('isRemoveSourceBranchButtonDisabled should be false', () => { it('isRemoveSourceBranchButtonDisabled should be false', () => {
expect(this.customVm.isRemoveSourceBranchButtonDisabled).toBe(false); expect(customVm.isRemoveSourceBranchButtonDisabled).toBe(false);
}); });
it('should be enabled in rendered output', () => { it('should be enabled in rendered output', () => {
const checkboxElement = this.customVm.$el.querySelector('#remove-source-branch-input'); const checkboxElement = customVm.$el.querySelector('#remove-source-branch-input');
expect(checkboxElement).not.toBeNull(); expect(checkboxElement).not.toBeNull();
}); });
}); });
......
...@@ -2,8 +2,8 @@ require 'spec_helper' ...@@ -2,8 +2,8 @@ require 'spec_helper'
describe Gitlab::Ci::Status::Build::Play do describe Gitlab::Ci::Status::Build::Play do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { build.project } let(:project) { create(:project, :stubbed_repository) }
let(:build) { create(:ci_build, :manual) } let(:build) { create(:ci_build, :manual, project: project) }
let(:status) { Gitlab::Ci::Status::Core.new(build, user) } let(:status) { Gitlab::Ci::Status::Core.new(build, user) }
subject { described_class.new(status) } subject { described_class.new(status) }
...@@ -46,6 +46,8 @@ describe Gitlab::Ci::Status::Build::Play do ...@@ -46,6 +46,8 @@ describe Gitlab::Ci::Status::Build::Play do
context 'when user can not push to the branch' do context 'when user can not push to the branch' do
before do before do
build.project.add_developer(user) build.project.add_developer(user)
create(:protected_branch, :masters_can_push,
name: build.ref, project: project)
end end
it { is_expected.not_to have_action } it { is_expected.not_to have_action }
......
...@@ -32,6 +32,12 @@ describe Gitlab::UserAccess do ...@@ -32,6 +32,12 @@ describe Gitlab::UserAccess do
let(:empty_project) { create(:project_empty_repo) } let(:empty_project) { create(:project_empty_repo) }
let(:project_access) { described_class.new(user, project: empty_project) } let(:project_access) { described_class.new(user, project: empty_project) }
it 'returns true for admins' do
user.update!(admin: true)
expect(access.can_push_to_branch?('master')).to be_truthy
end
it 'returns true if user is master' do it 'returns true if user is master' do
empty_project.add_master(user) empty_project.add_master(user)
...@@ -71,6 +77,12 @@ describe Gitlab::UserAccess do ...@@ -71,6 +77,12 @@ describe Gitlab::UserAccess do
let(:branch) { create :protected_branch, project: project, name: "test" } let(:branch) { create :protected_branch, project: project, name: "test" }
let(:not_existing_branch) { create :protected_branch, :developers_can_merge, project: project } let(:not_existing_branch) { create :protected_branch, :developers_can_merge, project: project }
it 'returns true for admins' do
user.update!(admin: true)
expect(access.can_push_to_branch?(branch.name)).to be_truthy
end
it 'returns true if user is a master' do it 'returns true if user is a master' do
project.add_master(user) project.add_master(user)
......
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20180425131009_assure_commits_count_for_merge_request_diff.rb')
describe AssureCommitsCountForMergeRequestDiff, :migration, :sidekiq, :redis do
let(:migration) { spy('migration') }
before do
allow(Gitlab::BackgroundMigration::AddMergeRequestDiffCommitsCount)
.to receive(:new).and_return(migration)
end
context 'when there are still unmigrated commit_counts afterwards' do
let(:namespaces) { table('namespaces') }
let(:projects) { table('projects') }
let(:merge_requests) { table('merge_requests') }
let(:diffs) { table('merge_request_diffs') }
before do
namespace = namespaces.create(name: 'foo', path: 'foo')
project = projects.create!(namespace_id: namespace.id)
merge_request = merge_requests.create!(source_branch: 'x', target_branch: 'y', target_project_id: project.id)
diffs.create!(commits_count: nil, merge_request_id: merge_request.id)
diffs.create!(commits_count: nil, merge_request_id: merge_request.id)
end
it 'migrates commit_counts sequentially in batches' do
migrate!
expect(migration).to have_received(:perform).once
end
end
end
require 'spec_helper' require 'spec_helper'
describe Environment do describe Environment do
let(:project) { create(:project) } let(:project) { create(:project, :stubbed_repository) }
subject(:environment) { create(:environment, project: project) } subject(:environment) { create(:environment, project: project) }
it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:project) }
...@@ -201,7 +201,7 @@ describe Environment do ...@@ -201,7 +201,7 @@ describe Environment do
end end
describe '#stop_with_action!' do describe '#stop_with_action!' do
let(:user) { create(:admin) } let(:user) { create(:user) }
subject { environment.stop_with_action!(user) } subject { environment.stop_with_action!(user) }
......
...@@ -1492,52 +1492,6 @@ describe Project do ...@@ -1492,52 +1492,6 @@ describe Project do
end end
end end
describe '#user_can_push_to_empty_repo?' do
let(:project) { create(:project) }
let(:user) { create(:user) }
it 'returns false when default_branch_protection is in full protection and user is developer' do
project.add_developer(user)
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_FULL)
expect(project.user_can_push_to_empty_repo?(user)).to be_falsey
end
it 'returns false when default_branch_protection only lets devs merge and user is dev' do
project.add_developer(user)
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
expect(project.user_can_push_to_empty_repo?(user)).to be_falsey
end
it 'returns true when default_branch_protection lets devs push and user is developer' do
project.add_developer(user)
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
expect(project.user_can_push_to_empty_repo?(user)).to be_truthy
end
it 'returns true when default_branch_protection is unprotected and user is developer' do
project.add_developer(user)
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE)
expect(project.user_can_push_to_empty_repo?(user)).to be_truthy
end
it 'returns true when user is master' do
project.add_master(user)
expect(project.user_can_push_to_empty_repo?(user)).to be_truthy
end
it 'returns false when the repo is not empty' do
project.add_master(user)
expect(project).to receive(:empty_repo?).and_return(false)
expect(project.user_can_push_to_empty_repo?(user)).to be_falsey
end
end
describe '#container_registry_url' do describe '#container_registry_url' do
let(:project) { create(:project) } let(:project) { create(:project) }
......
...@@ -208,6 +208,17 @@ describe ProjectPresenter do ...@@ -208,6 +208,17 @@ describe ProjectPresenter do
it 'returns nil if user cannot push' do it 'returns nil if user cannot push' do
expect(presenter.new_file_anchor_data).to be_nil expect(presenter.new_file_anchor_data).to be_nil
end end
context 'when the project is empty' do
let(:project) { create(:project, :empty_repo) }
# Since we protect the default branch for empty repos
it 'is empty for a developer' do
project.add_developer(user)
expect(presenter.new_file_anchor_data).to be_nil
end
end
end end
describe '#readme_anchor_data' do describe '#readme_anchor_data' do
......
...@@ -235,6 +235,8 @@ describe Ci::RetryPipelineService, '#execute' do ...@@ -235,6 +235,8 @@ describe Ci::RetryPipelineService, '#execute' do
context 'when user is not allowed to trigger manual action' do context 'when user is not allowed to trigger manual action' do
before do before do
project.add_developer(user) project.add_developer(user)
create(:protected_branch, :masters_can_push,
name: pipeline.ref, project: project)
end end
context 'when there is a failed manual action present' do context 'when there is a failed manual action present' do
......
...@@ -306,6 +306,23 @@ describe QuickActions::InterpretService do ...@@ -306,6 +306,23 @@ describe QuickActions::InterpretService do
end end
end end
shared_examples 'copy_metadata command' do
it 'fetches issue or merge request and copies labels and milestone if content contains /copy_metadata reference' do
source_issuable # populate the issue
todo_label # populate this label
inreview_label # populate this label
_, updates = service.execute(content, issuable)
expect(updates[:add_label_ids]).to match_array([inreview_label.id, todo_label.id])
if source_issuable.milestone
expect(updates[:milestone_id]).to eq(source_issuable.milestone.id)
else
expect(updates).not_to have_key(:milestone_id)
end
end
end
shared_examples 'shrug command' do shared_examples 'shrug command' do
it 'appends ¯\_(ツ)_/¯ to the comment' do it 'appends ¯\_(ツ)_/¯ to the comment' do
new_content, _ = service.execute(content, issuable) new_content, _ = service.execute(content, issuable)
...@@ -757,6 +774,65 @@ describe QuickActions::InterpretService do ...@@ -757,6 +774,65 @@ describe QuickActions::InterpretService do
let(:issuable) { issue } let(:issuable) { issue }
end end
context '/copy_metadata command' do
let(:todo_label) { create(:label, project: project, title: 'To Do') }
let(:inreview_label) { create(:label, project: project, title: 'In Review') }
it_behaves_like 'empty command' do
let(:content) { '/copy_metadata' }
let(:issuable) { issue }
end
it_behaves_like 'copy_metadata command' do
let(:source_issuable) { create(:labeled_issue, project: project, labels: [inreview_label, todo_label]) }
let(:content) { "/copy_metadata #{source_issuable.to_reference}" }
let(:issuable) { issue }
end
context 'when the parent issuable has a milestone' do
it_behaves_like 'copy_metadata command' do
let(:source_issuable) { create(:labeled_issue, project: project, labels: [todo_label, inreview_label], milestone: milestone) }
let(:content) { "/copy_metadata #{source_issuable.to_reference(project)}" }
let(:issuable) { issue }
end
end
context 'when more than one issuable is passed' do
it_behaves_like 'copy_metadata command' do
let(:source_issuable) { create(:labeled_issue, project: project, labels: [inreview_label, todo_label]) }
let(:other_label) { create(:label, project: project, title: 'Other') }
let(:other_source_issuable) { create(:labeled_issue, project: project, labels: [other_label]) }
let(:content) { "/copy_metadata #{source_issuable.to_reference} #{other_source_issuable.to_reference}" }
let(:issuable) { issue }
end
end
context 'cross project references' do
it_behaves_like 'empty command' do
let(:other_project) { create(:project, :public) }
let(:source_issuable) { create(:labeled_issue, project: other_project, labels: [todo_label, inreview_label]) }
let(:content) { "/copy_metadata #{source_issuable.to_reference(project)}" }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
let(:content) { "/copy_metadata imaginary#1234" }
let(:issuable) { issue }
end
it_behaves_like 'empty command' do
let(:other_project) { create(:project, :private) }
let(:source_issuable) { create(:issue, project: other_project) }
let(:content) { "/copy_metadata #{source_issuable.to_reference(project)}" }
let(:issuable) { issue }
end
end
end
context '/duplicate command' do context '/duplicate command' do
it_behaves_like 'duplicate command' do it_behaves_like 'duplicate command' do
let(:issue_duplicate) { create(:issue, project: project) } let(:issue_duplicate) { create(:issue, project: project) }
......
...@@ -670,6 +670,10 @@ babel-plugin-istanbul@^4.1.5: ...@@ -670,6 +670,10 @@ babel-plugin-istanbul@^4.1.5:
istanbul-lib-instrument "^1.7.5" istanbul-lib-instrument "^1.7.5"
test-exclude "^4.1.1" test-exclude "^4.1.1"
babel-plugin-rewire@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/babel-plugin-rewire/-/babel-plugin-rewire-1.1.0.tgz#a6b966d9d8c06c03d95dcda2eec4e2521519549b"
babel-plugin-syntax-async-functions@^6.8.0: babel-plugin-syntax-async-functions@^6.8.0:
version "6.13.0" version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
......
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