Commit cf08db62 authored by Marin Jankovski's avatar Marin Jankovski

Merge branch 'ce-to-ee-2018-10-18' into 'master'

CE upstream - 2018-10-18 11:28 UTC

Closes gitlab-ce#46588 and gitlab-org/quality/nightly#26

See merge request gitlab-org/gitlab-ee!7992
parents 35625fc5 ba725565
Add a description of your merge request here. Merge requests without an adequate ## What does this MR do?
description will not be reviewed until one is added.
<!--
Describe in detail what your merge request does, why it does that, etc. Merge
requests without an adequate description will not be reviewed until one is
added.
Please also keep this description up-to-date with any discussion that takes
place so that reviewers can understand your intent. This is especially
important if they didn't participate in the discussion.
Make sure to remove this comment when you are done.
-->
Add a description of your merge request here.
## Database checklist ## Database checklist
- [ ] Conforms to the [database guides](https://docs.gitlab.com/ee/development/README.html#databases-guides)
When adding migrations: When adding migrations:
- [ ] Updated `db/schema.rb` - [ ] Updated `db/schema.rb`
...@@ -35,16 +50,9 @@ When removing columns, tables, indexes or other structures: ...@@ -35,16 +50,9 @@ When removing columns, tables, indexes or other structures:
- [ ] [Changelog entry](https://docs.gitlab.com/ee/development/changelog.html) added, if necessary - [ ] [Changelog entry](https://docs.gitlab.com/ee/development/changelog.html) added, if necessary
- [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/documentation/index.html#contributing-to-docs) - [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/documentation/index.html#contributing-to-docs)
- [ ] [API support added](https://docs.gitlab.com/ee/development/api_styleguide.html)
- [ ] [Tests added for this feature/bug](https://docs.gitlab.com/ee/development/testing_guide/index.html) - [ ] [Tests added for this feature/bug](https://docs.gitlab.com/ee/development/testing_guide/index.html)
- Conforms to the [code review guidelines](https://docs.gitlab.com/ee/development/code_review.html) - [ ] Conforms to the [code review guidelines](https://docs.gitlab.com/ee/development/code_review.html)
- [ ] Has been reviewed by a Backend [maintainer](https://about.gitlab.com/handbook/engineering/#maintainer)
- [ ] Has been reviewed by a Database [specialist](https://about.gitlab.com/team/structure/#specialist)
- [ ] Conforms to the [merge request performance guidelines](https://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html) - [ ] Conforms to the [merge request performance guidelines](https://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html)
- [ ] Conforms to the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides) - [ ] Conforms to the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides)
- [ ] If you have multiple commits, please combine them into a few logically organized commits by [squashing them](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
- [ ] [Internationalization required/considered](https://docs.gitlab.com/ee/development/i18n/index.html)
- [ ] For a paid feature, have we considered GitLab.com plans, how it works for groups, and is there a design for promoting it to users who aren't on the correct plan?
- [ ] [End-to-end tests](https://docs.gitlab.com/ee/development/testing_guide/end_to_end_tests.html#testing-code-in-merge-requests) pass (`package-and-qa` manual pipeline job)
/label ~database /label ~database
...@@ -170,7 +170,7 @@ ...@@ -170,7 +170,7 @@
<gl-loading-icon <gl-loading-icon
v-if="isLoading" v-if="isLoading"
:size="2" :size="2"
class="js-job-loading prepend-top-20" class="js-job-loading qa-loading-animation prepend-top-20"
/> />
<template v-else-if="shouldRenderContent"> <template v-else-if="shouldRenderContent">
......
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
}; };
</script> </script>
<template> <template>
<pre class="js-build-trace build-trace"> <pre class="js-build-trace build-trace qa-build-trace">
<code <code
class="bash" class="bash"
v-html="trace" v-html="trace"
......
...@@ -82,7 +82,7 @@ ...@@ -82,7 +82,7 @@
:loading-text="groupedSummaryText" :loading-text="groupedSummaryText"
:error-text="groupedSummaryText" :error-text="groupedSummaryText"
:has-issues="reports.length > 0" :has-issues="reports.length > 0"
class="mr-widget-border-top grouped-security-reports mr-report" class="mr-widget-section grouped-security-reports mr-report"
> >
<div <div
slot="body" slot="body"
......
...@@ -276,12 +276,13 @@ export default { ...@@ -276,12 +276,13 @@ export default {
:key="deployment.id" :key="deployment.id"
:deployment="deployment" :deployment="deployment"
/> />
<grouped-test-reports-app
v-if="mr.testResultsPath"
class="js-reports-container"
:endpoint="mr.testResultsPath"
/>
<div class="mr-section-container"> <div class="mr-section-container">
<grouped-test-reports-app
v-if="mr.testResultsPath"
class="js-reports-container"
:endpoint="mr.testResultsPath"
/>
<div class="mr-widget-section"> <div class="mr-widget-section">
<component <component
:is="componentName" :is="componentName"
......
...@@ -43,7 +43,7 @@ export default { ...@@ -43,7 +43,7 @@ export default {
computed: { computed: {
cssClass() { cssClass() {
const className = this.status.group; const className = this.status.group;
return className ? `ci-status ci-${className}` : 'ci-status'; return className ? `ci-status ci-${className} qa-status-badge` : 'ci-status qa-status-badge';
}, },
}, },
}; };
......
...@@ -22,7 +22,7 @@ class AutocompleteController < ApplicationController ...@@ -22,7 +22,7 @@ class AutocompleteController < ApplicationController
end end
def user def user
user = UserFinder.new(params).execute! user = UserFinder.new(params[:id]).find_by_id!
render json: UserSerializer.new.represent(user) render json: UserSerializer.new.represent(user)
end end
......
...@@ -22,7 +22,7 @@ class Import::GithubController < Import::BaseController ...@@ -22,7 +22,7 @@ class Import::GithubController < Import::BaseController
end end
def personal_access_token def personal_access_token
session[access_token_key] = params[:personal_access_token] session[access_token_key] = params[:personal_access_token]&.strip
redirect_to status_import_url redirect_to status_import_url
end end
......
...@@ -38,7 +38,7 @@ class Profiles::KeysController < Profiles::ApplicationController ...@@ -38,7 +38,7 @@ class Profiles::KeysController < Profiles::ApplicationController
def get_keys def get_keys
if params[:username].present? if params[:username].present?
begin begin
user = User.find_by_username(params[:username]) user = UserFinder.new(params[:username]).find_by_username
if user.present? if user.present?
render text: user.all_ssh_keys.join("\n"), content_type: "text/plain" render text: user.all_ssh_keys.join("\n"), content_type: "text/plain"
else else
......
...@@ -26,12 +26,9 @@ class SnippetsController < ApplicationController ...@@ -26,12 +26,9 @@ class SnippetsController < ApplicationController
layout 'snippets' layout 'snippets'
respond_to :html respond_to :html
# rubocop: disable CodeReuse/ActiveRecord
def index def index
if params[:username].present? if params[:username].present?
@user = User.find_by(username: params[:username]) @user = UserFinder.new(params[:username]).find_by_username!
return render_404 unless @user
@snippets = SnippetsFinder.new(current_user, author: @user, scope: params[:scope]) @snippets = SnippetsFinder.new(current_user, author: @user, scope: params[:scope])
.execute.page(params[:page]) .execute.page(params[:page])
...@@ -41,7 +38,6 @@ class SnippetsController < ApplicationController ...@@ -41,7 +38,6 @@ class SnippetsController < ApplicationController
redirect_to(current_user ? dashboard_snippets_path : explore_snippets_path) redirect_to(current_user ? dashboard_snippets_path : explore_snippets_path)
end end
end end
# rubocop: enable CodeReuse/ActiveRecord
def new def new
@snippet = PersonalSnippet.new @snippet = PersonalSnippet.new
......
...@@ -256,7 +256,7 @@ class IssuableFinder ...@@ -256,7 +256,7 @@ class IssuableFinder
if assignee_id? if assignee_id?
User.find_by(id: params[:assignee_id]) User.find_by(id: params[:assignee_id])
elsif assignee_username? elsif assignee_username?
User.find_by(username: params[:assignee_username]) User.find_by_username(params[:assignee_username])
else else
nil nil
end end
...@@ -284,7 +284,7 @@ class IssuableFinder ...@@ -284,7 +284,7 @@ class IssuableFinder
if author_id? if author_id?
User.find_by(id: params[:author_id]) User.find_by(id: params[:author_id])
elsif author_username? elsif author_username?
User.find_by(username: params[:author_username]) User.find_by_username(params[:author_username])
else else
nil nil
end end
......
...@@ -7,22 +7,52 @@ ...@@ -7,22 +7,52 @@
# times we may want to exclude blocked user. By using this finder (and extending # times we may want to exclude blocked user. By using this finder (and extending
# it whenever necessary) we can keep this logic in one place. # it whenever necessary) we can keep this logic in one place.
class UserFinder class UserFinder
attr_reader :params def initialize(username_or_id)
@username_or_id = username_or_id
end
# Tries to find a User by id, returning nil if none could be found.
def find_by_id
User.find_by_id(@username_or_id)
end
def initialize(params) # Tries to find a User by id, raising a `ActiveRecord::RecordNotFound` if it could
@params = params # not be found.
def find_by_id!
User.find(@username_or_id)
end end
# Tries to find a User, returning nil if none could be found. # Tries to find a User by username, returning nil if none could be found.
# rubocop: disable CodeReuse/ActiveRecord def find_by_username
def execute User.find_by_username(@username_or_id)
User.find_by(id: params[:id])
end end
# rubocop: enable CodeReuse/ActiveRecord
# Tries to find a User, raising a `ActiveRecord::RecordNotFound` if it could # Tries to find a User by username, raising a `ActiveRecord::RecordNotFound` if it could
# not be found. # not be found.
def execute! def find_by_username!
User.find(params[:id]) User.find_by_username!(@username_or_id)
end
# Tries to find a User by username or id, returning nil if none could be found.
def find_by_id_or_username
if input_is_id?
find_by_id
else
find_by_username
end
end
# Tries to find a User by username or id, raising a `ActiveRecord::RecordNotFound` if it could
# not be found.
def find_by_id_or_username!
if input_is_id?
find_by_id!
else
find_by_username!
end
end
def input_is_id?
@username_or_id.is_a?(Numeric) || @username_or_id =~ /^\d+$/
end end
end end
...@@ -45,13 +45,11 @@ class UsersFinder ...@@ -45,13 +45,11 @@ class UsersFinder
private private
# rubocop: disable CodeReuse/ActiveRecord
def by_username(users) def by_username(users)
return users unless params[:username] return users unless params[:username]
users.where(username: params[:username]) users.by_username(params[:username])
end end
# rubocop: enable CodeReuse/ActiveRecord
def by_search(users) def by_search(users)
return users unless params[:search].present? return users unless params[:search].present?
......
...@@ -273,7 +273,7 @@ class User < ActiveRecord::Base ...@@ -273,7 +273,7 @@ class User < ActiveRecord::Base
scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) } scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) }
scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) } scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) }
scope :confirmed, -> { where.not(confirmed_at: nil) } scope :confirmed, -> { where.not(confirmed_at: nil) }
scope :by_username, -> (usernames) { iwhere(username: usernames) } scope :by_username, -> (usernames) { iwhere(username: Array(usernames).map(&:to_s)) }
scope :for_todos, -> (todos) { where(id: todos.select(:user_id)) } scope :for_todos, -> (todos) { where(id: todos.select(:user_id)) }
# Limits the users to those that have TODOs, optionally in the given state. # Limits the users to those that have TODOs, optionally in the given state.
......
...@@ -19,9 +19,11 @@ module Groups ...@@ -19,9 +19,11 @@ module Groups
group.assign_attributes(params) group.assign_attributes(params)
begin begin
after_update if group.save success = group.save
true after_update if success
success
rescue Gitlab::UpdatePathError => e rescue Gitlab::UpdatePathError => e
group.errors.add(:base, e.message) group.errors.add(:base, e.message)
......
...@@ -119,7 +119,7 @@ ...@@ -119,7 +119,7 @@
= _("<strong>%{group_name}</strong> group members").html_safe % { group_name: @group.name } = _("<strong>%{group_name}</strong> group members").html_safe % { group_name: @group.name }
%span.badge.badge-pill= @group.members.size %span.badge.badge-pill= @group.members.size
.float-right .float-right
= link_to icon('pencil-square-o', text: _('Manage access')), polymorphic_url([@group, :members]), class: "btn btn-sm" = link_to icon('pencil-square-o', text: _('Manage access')), group_group_members_path(@group), class: "btn btn-sm"
%ul.content-list.group-users-list.content-list.members-list %ul.content-list.group-users-list.content-list.members-list
= render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false } = render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false }
.card-footer .card-footer
......
...@@ -185,7 +185,7 @@ ...@@ -185,7 +185,7 @@
project members project members
%span.badge.badge-pill= @project.users.size %span.badge.badge-pill= @project.users.size
.float-right .float-right
= link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@project, :members]), class: "btn btn-sm" = link_to icon('pencil-square-o', text: 'Manage access'), project_project_members_path(@project), class: "btn btn-sm"
%ul.content-list.project_members.members-list %ul.content-list.project_members.members-list
= render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false } = render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false }
.card-footer .card-footer
......
.tree-content-holder.js-tree-content{ 'data-logs-path': @logs_path, 'data-path-locks-available': (@project.feature_available?(:file_locks) ? 'true' : 'false'), 'data-path-locks-toggle': toggle_project_path_locks_path(@project), 'data-path-locks-path': @path } .tree-content-holder.js-tree-content{ 'data-logs-path': @logs_path, 'data-path-locks-available': (@project.feature_available?(:file_locks) ? 'true' : 'false'), 'data-path-locks-toggle': toggle_project_path_locks_path(@project), 'data-path-locks-path': @path }
.table-holder .table-holder
%table.table#tree-slider{ class: "table_#{@hex_path} tree-table" } %table.table#tree-slider{ class: "table_#{@hex_path} tree-table qa-file-tree" }
%thead %thead
%tr %tr
%th= s_('ProjectFileTree|Name') %th= s_('ProjectFileTree|Name')
......
...@@ -48,7 +48,7 @@ require File.expand_path('../config/environment', File.dirname(__FILE__)) ...@@ -48,7 +48,7 @@ require File.expand_path('../config/environment', File.dirname(__FILE__))
result = Gitlab::Profiler.profile(options[:url], result = Gitlab::Profiler.profile(options[:url],
logger: Logger.new(options[:sql_output]), logger: Logger.new(options[:sql_output]),
post_data: options[:post_data], post_data: options[:post_data],
user: User.find_by_username(options[:username]), user: UserFinder.new(options[:username]).find_by_username,
private_token: ENV['PRIVATE_TOKEN']) private_token: ENV['PRIVATE_TOKEN'])
printer = RubyProf::CallStackPrinter.new(result) printer = RubyProf::CallStackPrinter.new(result)
......
---
title: "Use case insensitve username lookups"
merge_request: 21728
author: William George
type: fixed
\ No newline at end of file
---
title: Fixes broken borders for reports section in MR widget
merge_request:
author:
type: fixed
---
title: Strip whitespace around GitHub personal access tokens
merge_request: 22432
author:
type: fixed
...@@ -240,7 +240,10 @@ provided you are authenticated as an administrator with an OAuth or Personal Acc ...@@ -240,7 +240,10 @@ provided you are authenticated as an administrator with an OAuth or Personal Acc
You need to pass the `sudo` parameter either via query string or a header with an ID/username of You need to pass the `sudo` parameter either via query string or a header with an ID/username of
the user you want to perform the operation as. If passed as a header, the the user you want to perform the operation as. If passed as a header, the
header name must be `Sudo`. header name must be `Sudo`.
NOTE: **Note:**
Usernames are case insensitive.
If a non administrative access token is provided, an error message will If a non administrative access token is provided, an error message will
be returned with status code `403`: be returned with status code `403`:
......
...@@ -61,6 +61,9 @@ GET /users?active=true ...@@ -61,6 +61,9 @@ GET /users?active=true
GET /users?blocked=true GET /users?blocked=true
``` ```
NOTE: **Note:**
Username search is case insensitive.
### For admins ### For admins
``` ```
......
# Code Review Guidelines # Code Review Guidelines
## Getting your merge request reviewed, approved, and merged This guide contains advice and best practices for performing code review, and
having your code reviewed.
All merge requests for GitLab CE and EE, whether written by a GitLab team member
or a volunteer contributor, must go through a code review process to ensure the
code is effective, understandable, and maintainable.
There are a few rules to get your merge request accepted: ## Getting your merge request reviewed, approved, and merged
1. Your merge request should only be **merged by a [maintainer][team]**. You are strongly encouraged to get your code **reviewed** by a
1. If your merge request includes only backend changes [^1], it must be [reviewer](https://about.gitlab.com/handbook/engineering/#reviewer) as soon as
**approved by a [backend maintainer][projects]**. there is any code to review, to get a second opinion on the chosen solution and
1. If your merge request includes only frontend changes [^1], it must be implementation, and an extra pair of eyes looking for bugs, logic problems, or
**approved by a [frontend maintainer][projects]**. uncovered edge cases. The reviewer can be from a different team, but it is
1. If your merge request includes UX changes [^1], it must recommended to pick someone who knows the domain well. You can read more about the
be **approved by a [UX team member][team]**. importance of involving reviewer(s) in the section on the responsibility of the author below.
If you need some guidance (e.g. it's your first merge request), feel free to ask
one of the [Merge request coaches][team].
Depending on the areas your merge request touches, it must be **approved** by one
or more [maintainers](https://about.gitlab.com/handbook/engineering/#maintainer):
1. If your merge request includes backend changes [^1], it must be
**approved by a [backend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_backend)**.
1. If your merge request includes frontend changes [^1], it must be
**approved by a [frontend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_frontend)**.
1. If your merge request includes UX changes [^1], it must be
**approved by a [UX team member][team]**.
1. If your merge request includes adding a new JavaScript library [^1], it must be 1. If your merge request includes adding a new JavaScript library [^1], it must be
**approved by a [frontend lead][team]**. **approved by a [frontend lead][team]**.
1. If your merge request includes adding a new UI/UX paradigm [^1], it must be 1. If your merge request includes adding a new UI/UX paradigm [^1], it must be
**approved by a [UX lead][team]**. **approved by a [UX lead][team]**.
1. If your merge request includes frontend and backend changes [^1], it must 1. If your merge request includes a new dependency or a filesystem change, it must be
be **approved by a [frontend and a backend maintainer][projects]**. **approved by a [Distribution team member][team]**. See how to work with the [Distribution team](https://about.gitlab.com/handbook/engineering/dev-backend/distribution/) for more details.
1. If your merge request includes UX and frontend changes [^1], it must
be **approved by a [UX team member and a frontend maintainer][team]**.
1. If your merge request includes UX, frontend and backend changes [^1], it must
be **approved by a [UX team member, a frontend and a backend maintainer][team]**.
1. If your merge request includes a new dependency or a filesystem change, it must
be *approved by a [Distribution team member][team]*. See how to work with the [Distribution team for more details.](https://about.gitlab.com/handbook/engineering/dev-backend/distribution/)
1. To lower the amount of merge requests maintainers need to review, you can
ask or assign any [reviewers][projects] for a first review.
1. If you need some guidance (e.g. it's your first merge request), feel free
to ask one of the [Merge request coaches][team].
1. It is recommended that you assign a maintainer that is from a different team than your own.
This ensures that all code across GitLab is consistent and can be easily understood by all contributors.
1. Keep in mind that maintainers are also going to perform a final code review.
The ideal scenario is that the reviewer has already addressed any concerns
the maintainer would have found, and the maintainer only has to perform the
merge, but be prepared for further review comments.
For more guidance, see [CONTRIBUTING.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md).
## Best practices Getting your merge request **merged** also requires a maintainer. If it requires
more than one approval, the last maintainer to review and approve it will also merge it.
This guide contains advice and best practices for performing code review, and As described in the section on the responsibility of the maintainer below, you
having your code reviewed. are recommended to get your merge request approved and merged by maintainer(s)
from other teams than your own.
All merge requests for GitLab CE and EE, whether written by a GitLab team member ### The responsibility of the merge request author
or a volunteer contributor, must go through a code review process to ensure the
code is effective, understandable, and maintainable.
Any developer can, and is encouraged to, perform code review on merge requests The responsibility to find the best solution and implement it lies with the
of colleagues and contributors. However, the final decision to accept a merge merge request author.
request is up to one the project's maintainers, denoted on the
[engineering projects][projects]. Before assigning a merge request to a maintainer for approval and merge, they
should be confident that it actually solves the problem it was meant to solve,
that it does so in the most appropriate way, that it satisfies all requirements,
and that there are no remaining bugs, logical problems, or uncovered edge cases.
The merge request should also have a completed task list in its description and
a passing CI pipeline to avoid unnecessary back and forth.
To reach the required level of confidence in their solution, an author is expected
to involve other people in the investigation and implementation processes as
appropriate.
They are encouraged to reach out to domain experts to discuss different solutions
or get an implementation reviewed, to product managers and UX designers to clear
up confusion or verify that the end result matches what they had in mind, to
database specialists to get input on the data model or specific queries, or to
any other developer to get an in-depth review of the solution.
If an author is unsure if a merge request needs a domain expert's opinion, that's
usually a pretty good sign that it does, since without it the required level of
confidence in their solution will not have been reached.
### The responsibility of the maintainer
Maintainers are responsible for the overall health, quality, and consistency of
the GitLab codebase, across domains and product areas.
Consequently, their reviews will focus primarily on things like overall
architecture, code organization, separation of concerns, tests, DRYness,
consistency, and readability.
Since a maintainer's job only depends on their knowledge of the overall GitLab
codebase, and not that of any specific domain, they can review, approve and merge
merge requests from any team and in any product area.
In fact, authors are recommended to get their merge requests merged by maintainers
from other teams than their own, to ensure that all code across GitLab is consistent
and can be easily understood by all contributors, from both inside and outside the
company, without requiring team-specific expertise.
Maintainers will do their best to also review the specifics of the chosen solution
before merging, but as they are not necessarily domain experts, they may be poorly
placed to do so without an unreasonable investment of time. In those cases, they
will defer to the judgment of the author and earlier reviewers and involved domain
experts, in favor of focusing on their primary responsibilities.
If a developer who happens to also be a maintainer was involved in a merge request
as a domain expert and/or reviewer, it is recommended that they are not also picked
as the maintainer to ultimately approve and merge it.
## Best practices
### Everyone ### Everyone
......
...@@ -356,6 +356,45 @@ for a release by the appropriate person. ...@@ -356,6 +356,45 @@ for a release by the appropriate person.
Make sure to mention the merge request that the ~"technical debt" issue or Make sure to mention the merge request that the ~"technical debt" issue or
~"UX debt" issue is associated with in the description of the issue. ~"UX debt" issue is associated with in the description of the issue.
## Technical debt in follow-up issues
It's common to discover technical debt during development of a new feature. In
the spirit of "minimum viable change", resolution is often deferred to a
follow-up issue. However, this cannot be used as an excuse to merge poor-quality
code that would otherwise not pass review, or to overlook trivial matters that
don't deserve the be scheduled independently, and would be best resolved in the
original merge request - or not tracked at all!
The overheads of scheduling, and rate of change in the GitLab codebase, mean
that the cost of a trivial technical debt issue can quickly exceed the value of
tracking it. This generally means we should resolve these in the original merge
request - or simply not create a follow-up issue at all.
For example, a typo in a comment that is being copied between files is worth
fixing in the same MR, but not worth creating a follow-up issue for. Renaming a
method that is used in many places to make its intent slightly clearer may be
worth fixing, but it should not happen in the same MR, and is generally not
worth the overhead of having an issue of its own. These issues would invariably
be labelled `~P4 ~S4` if we were to create them.
More severe technical debt can have implications for development velocity. If
it isn't addressed in a timely manner, the codebase becomes needlessly difficult
to change, new features become difficult to add, and regressions abound.
Discoveries of this kind of technical debt should be treated seriously, and
while resolution in a follow-up issue may be appropriate, maintainers should
generally obtain a scheduling commitment from the author of the original MR, or
the engineering or product manager for the relevant area. This may take the form
of appropriate Priority / Severity labels on the issue, or an explicit milestone
and assignee.
The maintainer must always agree before an outstanding discussion is resolved in
this manner, and will be the one to create the issue. The title and description
should be of the same quality as those created
[in the usual manner](#technical-and-ux-debt) - in particular, the issue title
**must not** begin with `Follow-up`! The creating maintainer should also expect
to be involved in some capacity when work begins on the follow-up issue.
## Stewardship ## Stewardship
For issues related to the open source stewardship of GitLab, For issues related to the open source stewardship of GitLab,
......
...@@ -460,11 +460,11 @@ GitLab-Pages uses [GNU Make](https://www.gnu.org/software/make/). This step is o ...@@ -460,11 +460,11 @@ GitLab-Pages uses [GNU Make](https://www.gnu.org/software/make/). This step is o
### Install Gitaly ### Install Gitaly
# Fetch Gitaly source with Git and compile with Go # Fetch Gitaly source with Git and compile with Go
sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly]" RAILS_ENV=production sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories]" RAILS_ENV=production
You can specify a different Git repository by providing it as an extra parameter: You can specify a different Git repository by providing it as an extra parameter:
sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,https://example.com/gitaly.git]" RAILS_ENV=production sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories,https://example.com/gitaly.git]" RAILS_ENV=production
Next, make sure gitaly configured: Next, make sure gitaly configured:
......
...@@ -83,7 +83,7 @@ sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workh ...@@ -83,7 +83,7 @@ sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workh
```bash ```bash
cd /home/git/gitlab cd /home/git/gitlab
sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly]" RAILS_ENV=production sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories]" RAILS_ENV=production
``` ```
### 6. Update gitlab-shell to the corresponding version ### 6. Update gitlab-shell to the corresponding version
......
...@@ -20,7 +20,7 @@ module API ...@@ -20,7 +20,7 @@ module API
def gate_targets(params) def gate_targets(params)
targets = [] targets = []
targets << Feature.group(params[:feature_group]) if params[:feature_group] targets << Feature.group(params[:feature_group]) if params[:feature_group]
targets << User.find_by_username(params[:user]) if params[:user] targets << UserFinder.new(params[:user]).find_by_username if params[:user]
targets targets
end end
......
...@@ -99,15 +99,9 @@ module API ...@@ -99,15 +99,9 @@ module API
LabelsFinder.new(current_user, search_params).execute LabelsFinder.new(current_user, search_params).execute
end end
# rubocop: disable CodeReuse/ActiveRecord
def find_user(id) def find_user(id)
if id =~ /^\d+$/ UserFinder.new(id).find_by_id_or_username
User.find_by(id: id)
else
User.find_by(username: id)
end
end end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def find_project(id) def find_project(id)
......
...@@ -40,7 +40,7 @@ module API ...@@ -40,7 +40,7 @@ module API
elsif params[:user_id] elsif params[:user_id]
User.find_by(id: params[:user_id]) User.find_by(id: params[:user_id])
elsif params[:username] elsif params[:username]
User.find_by_username(params[:username]) UserFinder.new(params[:username]).find_by_username
end end
protocol = params[:protocol] protocol = params[:protocol]
...@@ -154,7 +154,7 @@ module API ...@@ -154,7 +154,7 @@ module API
elsif params[:user_id] elsif params[:user_id]
user = User.find_by(id: params[:user_id]) user = User.find_by(id: params[:user_id])
elsif params[:username] elsif params[:username]
user = User.find_by(username: params[:username]) user = UserFinder.new(params[:username]).find_by_username
end end
present user, with: Entities::UserSafe present user, with: Entities::UserSafe
......
...@@ -161,7 +161,6 @@ module API ...@@ -161,7 +161,6 @@ module API
requires :username, type: String, desc: 'The username of the user' requires :username, type: String, desc: 'The username of the user'
use :optional_attributes use :optional_attributes
end end
# rubocop: disable CodeReuse/ActiveRecord
post do post do
authenticated_as_admin! authenticated_as_admin!
...@@ -172,17 +171,16 @@ module API ...@@ -172,17 +171,16 @@ module API
present user, with: Entities::UserPublic, current_user: current_user present user, with: Entities::UserPublic, current_user: current_user
else else
conflict!('Email has already been taken') if User conflict!('Email has already been taken') if User
.where(email: user.email) .by_any_email(user.email.downcase)
.count > 0 .any?
conflict!('Username has already been taken') if User conflict!('Username has already been taken') if User
.where(username: user.username) .by_username(user.username)
.count > 0 .any?
render_validation_error!(user) render_validation_error!(user)
end end
end end
# rubocop: enable CodeReuse/ActiveRecord
desc 'Update a user. Available only for admins.' do desc 'Update a user. Available only for admins.' do
success Entities::UserPublic success Entities::UserPublic
...@@ -204,11 +202,11 @@ module API ...@@ -204,11 +202,11 @@ module API
not_found!('User') unless user not_found!('User') unless user
conflict!('Email has already been taken') if params[:email] && conflict!('Email has already been taken') if params[:email] &&
User.where(email: params[:email]) User.by_any_email(params[:email].downcase)
.where.not(id: user.id).count > 0 .where.not(id: user.id).count > 0
conflict!('Username has already been taken') if params[:username] && conflict!('Username has already been taken') if params[:username] &&
User.where(username: params[:username]) User.by_username(params[:username])
.where.not(id: user.id).count > 0 .where.not(id: user.id).count > 0
user_params = declared_params(include_missing: false) user_params = declared_params(include_missing: false)
......
...@@ -102,7 +102,7 @@ module Gitlab ...@@ -102,7 +102,7 @@ module Gitlab
if username.start_with?("@") if username.start_with?("@")
username = username[1..-1] username = username[1..-1]
if user = User.find_by(username: username) if user = UserFinder.new(username).find_by_username
assignee_id = user.id assignee_id = user.id
end end
end end
......
...@@ -86,7 +86,7 @@ module Gitlab ...@@ -86,7 +86,7 @@ module Gitlab
# Example: # Example:
# #
# Gitlab::Metrics.measure(:find_by_username_duration) do # Gitlab::Metrics.measure(:find_by_username_duration) do
# User.find_by_username(some_username) # UserFinder.new(some_username).find_by_username
# end # end
# #
# name - The name of the field to store the execution time in. # name - The name of the field to store the execution time in.
......
...@@ -9,7 +9,7 @@ class GithubImport ...@@ -9,7 +9,7 @@ class GithubImport
def initialize(token, gitlab_username, project_path, extras) def initialize(token, gitlab_username, project_path, extras)
@options = { token: token } @options = { token: token }
@project_path = project_path @project_path = project_path
@current_user = User.find_by(username: gitlab_username) @current_user = UserFinder.new(gitlab_username).find_by_username
raise "GitLab user #{gitlab_username} not found. Please specify a valid username." unless @current_user raise "GitLab user #{gitlab_username} not found. Please specify a valid username." unless @current_user
......
...@@ -30,6 +30,14 @@ module QA ...@@ -30,6 +30,14 @@ module QA
@directory = dir @directory = dir
end end
def files=(files)
if !files.is_a?(Array) || files.empty?
raise ArgumentError, "Please provide an array of hashes e.g.: [{name: 'file1', content: 'foo'}]"
end
@files = files
end
def fabricate! def fabricate!
Git::Repository.perform do |repository| Git::Repository.perform do |repository|
if ssh_key if ssh_key
...@@ -63,6 +71,10 @@ module QA ...@@ -63,6 +71,10 @@ module QA
@directory.each_child do |f| @directory.each_child do |f|
repository.add_file(f.basename, f.read) if f.file? repository.add_file(f.basename, f.read) if f.file?
end end
elsif @files
@files.each do |f|
repository.add_file(f[:name], f[:content])
end
else else
repository.add_file(file_name, file_content) repository.add_file(file_name, file_content)
end end
......
...@@ -4,30 +4,39 @@ module QA::Page ...@@ -4,30 +4,39 @@ module QA::Page
COMPLETED_STATUSES = %w[passed failed canceled blocked skipped manual].freeze # excludes created, pending, running COMPLETED_STATUSES = %w[passed failed canceled blocked skipped manual].freeze # excludes created, pending, running
PASSED_STATUS = 'passed'.freeze PASSED_STATUS = 'passed'.freeze
view 'app/views/shared/builds/_build_output.html.haml' do view 'app/assets/javascripts/jobs/components/job_app.vue' do
element :build_output, '.js-build-output' # rubocop:disable QA/ElementWithPattern element :loading_animation
element :loading_animation, '.js-build-refresh' # rubocop:disable QA/ElementWithPattern end
view 'app/assets/javascripts/jobs/components/job_log.vue' do
element :build_trace
end end
view 'app/assets/javascripts/vue_shared/components/ci_badge_link.vue' do view 'app/assets/javascripts/vue_shared/components/ci_badge_link.vue' do
element :status_badge, 'ci-status' # rubocop:disable QA/ElementWithPattern element :status_badge
end end
def completed? def completed?
COMPLETED_STATUSES.include? find('.ci-status').text COMPLETED_STATUSES.include?(status_badge)
end end
def passed? def passed?
find('.ci-status').text == PASSED_STATUS status_badge == PASSED_STATUS
end end
def trace_loading? def trace_loading?
has_css?('.js-build-refresh') has_element?(:loading_animation)
end end
# Reminder: You may wish to wait for a particular job status before checking output # Reminder: You may wish to wait for a particular job status before checking output
def output def output
find('.js-build-output').text find_element(:build_trace).text
end
private
def status_badge
find_element(:status_badge).text
end end
end end
end end
......
...@@ -44,6 +44,10 @@ module QA ...@@ -44,6 +44,10 @@ module QA
element :web_ide_button element :web_ide_button
end end
view 'app/views/projects/tree/_tree_content.html.haml' do
element :file_tree
end
def project_name def project_name
find('.qa-project-name').text find('.qa-project-name').text
end end
...@@ -53,6 +57,12 @@ module QA ...@@ -53,6 +57,12 @@ module QA
click_element :new_file_option click_element :new_file_option
end end
def go_to_file(filename)
within_element(:file_tree) do
click_on filename
end
end
def switch_to_branch(branch_name) def switch_to_branch(branch_name)
find_element(:branches_select).click find_element(:branches_select).click
......
# frozen_string_literal: true
describe QA::Factory::Repository::Push do
describe '.files=' do
let(:files) do
[
{
name: 'file.txt',
content: 'foo'
}
]
end
it 'raises an error if files is not an array' do
expect { subject.files = '' }.to raise_error(ArgumentError)
end
it 'raises an error if files is an empty array' do
expect { subject.files = [] }.to raise_error(ArgumentError)
end
it 'does not raise if files is an array' do
expect { subject.files = files }.not_to raise_error
end
end
end
...@@ -3,40 +3,176 @@ ...@@ -3,40 +3,176 @@
require 'spec_helper' require 'spec_helper'
describe UserFinder do describe UserFinder do
describe '#execute' do set(:user) { create(:user) }
describe '#find_by_id' do
context 'when the user exists' do
it 'returns the user' do
found = described_class.new(user.id).find_by_id
expect(found).to eq(user)
end
end
context 'when the user exists (id as string)' do
it 'returns the user' do
found = described_class.new(user.id.to_s).find_by_id
expect(found).to eq(user)
end
end
context 'when the user does not exist' do
it 'returns nil' do
found = described_class.new(1).find_by_id
expect(found).to be_nil
end
end
end
describe '#find_by_username' do
context 'when the user exists' do context 'when the user exists' do
it 'returns the user' do it 'returns the user' do
user = create(:user) found = described_class.new(user.username).find_by_username
found = described_class.new(id: user.id).execute
expect(found).to eq(user)
end
end
context 'when the user does not exist' do
it 'returns nil' do
found = described_class.new("non_existent_username").find_by_username
expect(found).to be_nil
end
end
end
describe '#find_by_id_or_username' do
context 'when the user exists (id)' do
it 'returns the user' do
found = described_class.new(user.id).find_by_id_or_username
expect(found).to eq(user)
end
end
context 'when the user exists (id as string)' do
it 'returns the user' do
found = described_class.new(user.id.to_s).find_by_id_or_username
expect(found).to eq(user) expect(found).to eq(user)
end end
end end
context 'when the user exists (username)' do
it 'returns the user' do
found = described_class.new(user.username).find_by_id_or_username
expect(found).to eq(user)
end
end
context 'when the user does not exist (username)' do
it 'returns nil' do
found = described_class.new("non_existent_username").find_by_id_or_username
expect(found).to be_nil
end
end
context 'when the user does not exist' do context 'when the user does not exist' do
it 'returns nil' do it 'returns nil' do
found = described_class.new(id: 1).execute found = described_class.new(1).find_by_id_or_username
expect(found).to be_nil expect(found).to be_nil
end end
end end
end end
describe '#execute!' do describe '#find_by_id!' do
context 'when the user exists' do
it 'returns the user' do
found = described_class.new(user.id).find_by_id!
expect(found).to eq(user)
end
end
context 'when the user exists (id as string)' do
it 'returns the user' do
found = described_class.new(user.id.to_s).find_by_id!
expect(found).to eq(user)
end
end
context 'when the user does not exist' do
it 'raises ActiveRecord::RecordNotFound' do
finder = described_class.new(1)
expect { finder.find_by_id! }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
describe '#find_by_username!' do
context 'when the user exists' do context 'when the user exists' do
it 'returns the user' do it 'returns the user' do
user = create(:user) found = described_class.new(user.username).find_by_username!
found = described_class.new(id: user.id).execute!
expect(found).to eq(user)
end
end
context 'when the user does not exist' do
it 'raises ActiveRecord::RecordNotFound' do
finder = described_class.new("non_existent_username")
expect { finder.find_by_username! }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
describe '#find_by_id_or_username!' do
context 'when the user exists (id)' do
it 'returns the user' do
found = described_class.new(user.id).find_by_id_or_username!
expect(found).to eq(user)
end
end
context 'when the user exists (id as string)' do
it 'returns the user' do
found = described_class.new(user.id.to_s).find_by_id_or_username!
expect(found).to eq(user) expect(found).to eq(user)
end end
end end
context 'when the user exists (username)' do
it 'returns the user' do
found = described_class.new(user.username).find_by_id_or_username!
expect(found).to eq(user)
end
end
context 'when the user does not exist (username)' do
it 'raises ActiveRecord::RecordNotFound' do
finder = described_class.new("non_existent_username")
expect { finder.find_by_id_or_username! }.to raise_error(ActiveRecord::RecordNotFound)
end
end
context 'when the user does not exist' do context 'when the user does not exist' do
it 'raises ActiveRecord::RecordNotFound' do it 'raises ActiveRecord::RecordNotFound' do
finder = described_class.new(id: 1) finder = described_class.new(1)
expect { finder.execute! }.to raise_error(ActiveRecord::RecordNotFound) expect { finder.find_by_id_or_username! }.to raise_error(ActiveRecord::RecordNotFound)
end end
end end
end end
......
...@@ -22,6 +22,12 @@ describe UsersFinder do ...@@ -22,6 +22,12 @@ describe UsersFinder do
expect(users).to contain_exactly(user1) expect(users).to contain_exactly(user1)
end end
it 'filters by username (case insensitive)' do
users = described_class.new(user, username: 'joHNdoE').execute
expect(users).to contain_exactly(user1)
end
it 'filters by search' do it 'filters by search' do
users = described_class.new(user, search: 'orando').execute users = described_class.new(user, search: 'orando').execute
......
...@@ -10,8 +10,8 @@ describe('Ajax Loading Spinner', () => { ...@@ -10,8 +10,8 @@ describe('Ajax Loading Spinner', () => {
AjaxLoadingSpinner.init(); AjaxLoadingSpinner.init();
}); });
it('change current icon with spinner icon and disable link while waiting ajax response', (done) => { it('change current icon with spinner icon and disable link while waiting ajax response', done => {
spyOn($, 'ajax').and.callFake((req) => { spyOn($, 'ajax').and.callFake(req => {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
const ajaxLoadingSpinner = document.querySelector('.js-ajax-loading-spinner'); const ajaxLoadingSpinner = document.querySelector('.js-ajax-loading-spinner');
const icon = ajaxLoadingSpinner.querySelector('i'); const icon = ajaxLoadingSpinner.querySelector('i');
...@@ -33,8 +33,8 @@ describe('Ajax Loading Spinner', () => { ...@@ -33,8 +33,8 @@ describe('Ajax Loading Spinner', () => {
document.querySelector('.js-ajax-loading-spinner').click(); document.querySelector('.js-ajax-loading-spinner').click();
}); });
it('use original icon again and enabled the link after complete the ajax request', (done) => { it('use original icon again and enabled the link after complete the ajax request', done => {
spyOn($, 'ajax').and.callFake((req) => { spyOn($, 'ajax').and.callFake(req => {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
const ajaxLoadingSpinner = document.querySelector('.js-ajax-loading-spinner'); const ajaxLoadingSpinner = document.querySelector('.js-ajax-loading-spinner');
......
...@@ -21,7 +21,7 @@ describe('avatar_helper', () => { ...@@ -21,7 +21,7 @@ describe('avatar_helper', () => {
it(`wraps around if id is bigger than ${IDENTICON_BG_COUNT}`, () => { it(`wraps around if id is bigger than ${IDENTICON_BG_COUNT}`, () => {
expect(getIdenticonBackgroundClass(IDENTICON_BG_COUNT + 4)).toEqual('bg5'); expect(getIdenticonBackgroundClass(IDENTICON_BG_COUNT + 4)).toEqual('bg5');
expect(getIdenticonBackgroundClass((IDENTICON_BG_COUNT * 5) + 6)).toEqual('bg7'); expect(getIdenticonBackgroundClass(IDENTICON_BG_COUNT * 5 + 6)).toEqual('bg7');
}); });
}); });
......
This diff is collapsed.
import BindInOut from '~/behaviors/bind_in_out'; import BindInOut from '~/behaviors/bind_in_out';
import ClassSpecHelper from '../helpers/class_spec_helper'; import ClassSpecHelper from '../helpers/class_spec_helper';
describe('BindInOut', function () { describe('BindInOut', function() {
describe('constructor', function () { describe('constructor', function() {
beforeEach(function () { beforeEach(function() {
this.in = {}; this.in = {};
this.out = {}; this.out = {};
this.bindInOut = new BindInOut(this.in, this.out); this.bindInOut = new BindInOut(this.in, this.out);
}); });
it('should set .in', function () { it('should set .in', function() {
expect(this.bindInOut.in).toBe(this.in); expect(this.bindInOut.in).toBe(this.in);
}); });
it('should set .out', function () { it('should set .out', function() {
expect(this.bindInOut.out).toBe(this.out); expect(this.bindInOut.out).toBe(this.out);
}); });
it('should set .eventWrapper', function () { it('should set .eventWrapper', function() {
expect(this.bindInOut.eventWrapper).toEqual({}); expect(this.bindInOut.eventWrapper).toEqual({});
}); });
describe('if .in is an input', function () { describe('if .in is an input', function() {
beforeEach(function () { beforeEach(function() {
this.bindInOut = new BindInOut({ tagName: 'INPUT' }); this.bindInOut = new BindInOut({ tagName: 'INPUT' });
}); });
it('should set .eventType to keyup ', function () { it('should set .eventType to keyup ', function() {
expect(this.bindInOut.eventType).toEqual('keyup'); expect(this.bindInOut.eventType).toEqual('keyup');
}); });
}); });
describe('if .in is a textarea', function () { describe('if .in is a textarea', function() {
beforeEach(function () { beforeEach(function() {
this.bindInOut = new BindInOut({ tagName: 'TEXTAREA' }); this.bindInOut = new BindInOut({ tagName: 'TEXTAREA' });
}); });
it('should set .eventType to keyup ', function () { it('should set .eventType to keyup ', function() {
expect(this.bindInOut.eventType).toEqual('keyup'); expect(this.bindInOut.eventType).toEqual('keyup');
}); });
}); });
describe('if .in is not an input or textarea', function () { describe('if .in is not an input or textarea', function() {
beforeEach(function () { beforeEach(function() {
this.bindInOut = new BindInOut({ tagName: 'SELECT' }); this.bindInOut = new BindInOut({ tagName: 'SELECT' });
}); });
it('should set .eventType to change ', function () { it('should set .eventType to change ', function() {
expect(this.bindInOut.eventType).toEqual('change'); expect(this.bindInOut.eventType).toEqual('change');
}); });
}); });
}); });
describe('addEvents', function () { describe('addEvents', function() {
beforeEach(function () { beforeEach(function() {
this.in = jasmine.createSpyObj('in', ['addEventListener']); this.in = jasmine.createSpyObj('in', ['addEventListener']);
this.bindInOut = new BindInOut(this.in); this.bindInOut = new BindInOut(this.in);
...@@ -62,25 +62,24 @@ describe('BindInOut', function () { ...@@ -62,25 +62,24 @@ describe('BindInOut', function () {
this.addEvents = this.bindInOut.addEvents(); this.addEvents = this.bindInOut.addEvents();
}); });
it('should set .eventWrapper.updateOut', function () { it('should set .eventWrapper.updateOut', function() {
expect(this.bindInOut.eventWrapper.updateOut).toEqual(jasmine.any(Function)); expect(this.bindInOut.eventWrapper.updateOut).toEqual(jasmine.any(Function));
}); });
it('should call .addEventListener', function () { it('should call .addEventListener', function() {
expect(this.in.addEventListener) expect(this.in.addEventListener).toHaveBeenCalledWith(
.toHaveBeenCalledWith( this.bindInOut.eventType,
this.bindInOut.eventType, this.bindInOut.eventWrapper.updateOut,
this.bindInOut.eventWrapper.updateOut, );
);
}); });
it('should return the instance', function () { it('should return the instance', function() {
expect(this.addEvents).toBe(this.bindInOut); expect(this.addEvents).toBe(this.bindInOut);
}); });
}); });
describe('updateOut', function () { describe('updateOut', function() {
beforeEach(function () { beforeEach(function() {
this.in = { value: 'the-value' }; this.in = { value: 'the-value' };
this.out = { textContent: 'not-the-value' }; this.out = { textContent: 'not-the-value' };
...@@ -89,17 +88,17 @@ describe('BindInOut', function () { ...@@ -89,17 +88,17 @@ describe('BindInOut', function () {
this.updateOut = this.bindInOut.updateOut(); this.updateOut = this.bindInOut.updateOut();
}); });
it('should set .out.textContent to .in.value', function () { it('should set .out.textContent to .in.value', function() {
expect(this.out.textContent).toBe(this.in.value); expect(this.out.textContent).toBe(this.in.value);
}); });
it('should return the instance', function () { it('should return the instance', function() {
expect(this.updateOut).toBe(this.bindInOut); expect(this.updateOut).toBe(this.bindInOut);
}); });
}); });
describe('removeEvents', function () { describe('removeEvents', function() {
beforeEach(function () { beforeEach(function() {
this.in = jasmine.createSpyObj('in', ['removeEventListener']); this.in = jasmine.createSpyObj('in', ['removeEventListener']);
this.updateOut = () => {}; this.updateOut = () => {};
...@@ -109,21 +108,20 @@ describe('BindInOut', function () { ...@@ -109,21 +108,20 @@ describe('BindInOut', function () {
this.removeEvents = this.bindInOut.removeEvents(); this.removeEvents = this.bindInOut.removeEvents();
}); });
it('should call .removeEventListener', function () { it('should call .removeEventListener', function() {
expect(this.in.removeEventListener) expect(this.in.removeEventListener).toHaveBeenCalledWith(
.toHaveBeenCalledWith( this.bindInOut.eventType,
this.bindInOut.eventType, this.updateOut,
this.updateOut, );
);
}); });
it('should return the instance', function () { it('should return the instance', function() {
expect(this.removeEvents).toBe(this.bindInOut); expect(this.removeEvents).toBe(this.bindInOut);
}); });
}); });
describe('initAll', function () { describe('initAll', function() {
beforeEach(function () { beforeEach(function() {
this.ins = [0, 1, 2]; this.ins = [0, 1, 2];
this.instances = []; this.instances = [];
...@@ -136,43 +134,47 @@ describe('BindInOut', function () { ...@@ -136,43 +134,47 @@ describe('BindInOut', function () {
ClassSpecHelper.itShouldBeAStaticMethod(BindInOut, 'initAll'); ClassSpecHelper.itShouldBeAStaticMethod(BindInOut, 'initAll');
it('should call .querySelectorAll', function () { it('should call .querySelectorAll', function() {
expect(document.querySelectorAll).toHaveBeenCalledWith('*[data-bind-in]'); expect(document.querySelectorAll).toHaveBeenCalledWith('*[data-bind-in]');
}); });
it('should call .map', function () { it('should call .map', function() {
expect(Array.prototype.map).toHaveBeenCalledWith(jasmine.any(Function)); expect(Array.prototype.map).toHaveBeenCalledWith(jasmine.any(Function));
}); });
it('should call .init for each element', function () { it('should call .init for each element', function() {
expect(BindInOut.init.calls.count()).toEqual(3); expect(BindInOut.init.calls.count()).toEqual(3);
}); });
it('should return an array of instances', function () { it('should return an array of instances', function() {
expect(this.initAll).toEqual(jasmine.any(Array)); expect(this.initAll).toEqual(jasmine.any(Array));
}); });
}); });
describe('init', function () { describe('init', function() {
beforeEach(function () { beforeEach(function() {
spyOn(BindInOut.prototype, 'addEvents').and.callFake(function () { return this; }); spyOn(BindInOut.prototype, 'addEvents').and.callFake(function() {
spyOn(BindInOut.prototype, 'updateOut').and.callFake(function () { return this; }); return this;
});
spyOn(BindInOut.prototype, 'updateOut').and.callFake(function() {
return this;
});
this.init = BindInOut.init({}, {}); this.init = BindInOut.init({}, {});
}); });
ClassSpecHelper.itShouldBeAStaticMethod(BindInOut, 'init'); ClassSpecHelper.itShouldBeAStaticMethod(BindInOut, 'init');
it('should call .addEvents', function () { it('should call .addEvents', function() {
expect(BindInOut.prototype.addEvents).toHaveBeenCalled(); expect(BindInOut.prototype.addEvents).toHaveBeenCalled();
}); });
it('should call .updateOut', function () { it('should call .updateOut', function() {
expect(BindInOut.prototype.updateOut).toHaveBeenCalled(); expect(BindInOut.prototype.updateOut).toHaveBeenCalled();
}); });
describe('if no anOut is provided', function () { describe('if no anOut is provided', function() {
beforeEach(function () { beforeEach(function() {
this.anIn = { dataset: { bindIn: 'the-data-bind-in' } }; this.anIn = { dataset: { bindIn: 'the-data-bind-in' } };
spyOn(document, 'querySelector'); spyOn(document, 'querySelector');
...@@ -180,9 +182,10 @@ describe('BindInOut', function () { ...@@ -180,9 +182,10 @@ describe('BindInOut', function () {
BindInOut.init(this.anIn); BindInOut.init(this.anIn);
}); });
it('should call .querySelector', function () { it('should call .querySelector', function() {
expect(document.querySelector) expect(document.querySelector).toHaveBeenCalledWith(
.toHaveBeenCalledWith(`*[data-bind-out="${this.anIn.dataset.bindIn}"]`); `*[data-bind-out="${this.anIn.dataset.bindIn}"]`,
);
}); });
}); });
}); });
......
...@@ -56,7 +56,7 @@ describe('CopyAsGFM', () => { ...@@ -56,7 +56,7 @@ describe('CopyAsGFM', () => {
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
const node = document.createElement('div'); const node = document.createElement('div');
node.innerHTML = html; node.innerHTML = html;
Array.from(node.childNodes).forEach((item) => fragment.appendChild(item)); Array.from(node.childNodes).forEach(item => fragment.appendChild(item));
return fragment; return fragment;
}, },
}), }),
......
...@@ -13,7 +13,7 @@ describe('Unicode Support Map', () => { ...@@ -13,7 +13,7 @@ describe('Unicode Support Map', () => {
spyOn(JSON, 'stringify').and.returnValue(stringSupportMap); spyOn(JSON, 'stringify').and.returnValue(stringSupportMap);
}); });
describe('if isLocalStorageAvailable is `true`', function () { describe('if isLocalStorageAvailable is `true`', function() {
beforeEach(() => { beforeEach(() => {
AccessorUtilities.isLocalStorageAccessSafe.and.returnValue(true); AccessorUtilities.isLocalStorageAccessSafe.and.returnValue(true);
...@@ -36,7 +36,7 @@ describe('Unicode Support Map', () => { ...@@ -36,7 +36,7 @@ describe('Unicode Support Map', () => {
}); });
}); });
describe('if isLocalStorageAvailable is `false`', function () { describe('if isLocalStorageAvailable is `false`', function() {
beforeEach(() => { beforeEach(() => {
AccessorUtilities.isLocalStorageAccessSafe.and.returnValue(false); AccessorUtilities.isLocalStorageAccessSafe.and.returnValue(false);
......
import $ from 'jquery'; import $ from 'jquery';
import '~/behaviors/quick_submit'; import '~/behaviors/quick_submit';
describe('Quick Submit behavior', function () { 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('snippets/show.html.raw'); preloadFixtures('snippets/show.html.raw');
......
...@@ -32,18 +32,30 @@ describe('requiresInput', () => { ...@@ -32,18 +32,30 @@ describe('requiresInput', () => {
it('enables submit when all required fields receive input', () => { it('enables submit when all required fields receive input', () => {
$('.js-requires-input').requiresInput(); $('.js-requires-input').requiresInput();
$('#required1').val('input1').change(); $('#required1')
.val('input1')
.change();
expect(submitButton).toBeDisabled(); expect(submitButton).toBeDisabled();
$('#optional1').val('input1').change(); $('#optional1')
.val('input1')
.change();
expect(submitButton).toBeDisabled(); expect(submitButton).toBeDisabled();
$('#required2').val('input2').change(); $('#required2')
$('#required3').val('input3').change(); .val('input2')
$('#required4').val('input4').change(); .change();
$('#required5').val('1').change(); $('#required3')
.val('input3')
.change();
$('#required4')
.val('input4')
.change();
$('#required5')
.val('1')
.change();
expect($('.submit')).not.toBeDisabled(); expect($('.submit')).not.toBeDisabled();
}); });
......
...@@ -36,12 +36,7 @@ function setupSecretFixture( ...@@ -36,12 +36,7 @@ function setupSecretFixture(
placeholderClass = 'js-secret-value-placeholder', placeholderClass = 'js-secret-value-placeholder',
) { ) {
const wrapper = document.createElement('div'); const wrapper = document.createElement('div');
wrapper.innerHTML = generateFixtureMarkup( wrapper.innerHTML = generateFixtureMarkup(secrets, isRevealed, valueClass, placeholderClass);
secrets,
isRevealed,
valueClass,
placeholderClass,
);
const secretValues = new SecretValues({ const secretValues = new SecretValues({
container: wrapper.querySelector('.js-secret-container'), container: wrapper.querySelector('.js-secret-container'),
...@@ -127,12 +122,12 @@ describe('setupSecretValues', () => { ...@@ -127,12 +122,12 @@ describe('setupSecretValues', () => {
const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder'); const placeholders = wrapper.querySelectorAll('.js-secret-value-placeholder');
expect(values.length).toEqual(3); expect(values.length).toEqual(3);
values.forEach((value) => { values.forEach(value => {
expect(value.classList.contains('hide')).toEqual(true); expect(value.classList.contains('hide')).toEqual(true);
}); });
expect(placeholders.length).toEqual(3); expect(placeholders.length).toEqual(3);
placeholders.forEach((placeholder) => { placeholders.forEach(placeholder => {
expect(placeholder.classList.contains('hide')).toEqual(false); expect(placeholder.classList.contains('hide')).toEqual(false);
}); });
}); });
...@@ -146,24 +141,24 @@ describe('setupSecretValues', () => { ...@@ -146,24 +141,24 @@ describe('setupSecretValues', () => {
revealButton.click(); revealButton.click();
expect(values.length).toEqual(3); expect(values.length).toEqual(3);
values.forEach((value) => { values.forEach(value => {
expect(value.classList.contains('hide')).toEqual(false); expect(value.classList.contains('hide')).toEqual(false);
}); });
expect(placeholders.length).toEqual(3); expect(placeholders.length).toEqual(3);
placeholders.forEach((placeholder) => { placeholders.forEach(placeholder => {
expect(placeholder.classList.contains('hide')).toEqual(true); expect(placeholder.classList.contains('hide')).toEqual(true);
}); });
revealButton.click(); revealButton.click();
expect(values.length).toEqual(3); expect(values.length).toEqual(3);
values.forEach((value) => { values.forEach(value => {
expect(value.classList.contains('hide')).toEqual(true); expect(value.classList.contains('hide')).toEqual(true);
}); });
expect(placeholders.length).toEqual(3); expect(placeholders.length).toEqual(3);
placeholders.forEach((placeholder) => { placeholders.forEach(placeholder => {
expect(placeholder.classList.contains('hide')).toEqual(false); expect(placeholder.classList.contains('hide')).toEqual(false);
}); });
}); });
...@@ -175,7 +170,9 @@ describe('setupSecretValues', () => { ...@@ -175,7 +170,9 @@ describe('setupSecretValues', () => {
it('should toggle values and placeholders', () => { it('should toggle values and placeholders', () => {
const wrapper = setupSecretFixture(secrets, false); const wrapper = setupSecretFixture(secrets, false);
// Insert the new dynamic row // Insert the new dynamic row
wrapper.querySelector('.js-secret-container').insertAdjacentHTML('afterbegin', generateValueMarkup('foobarbazdynamic')); wrapper
.querySelector('.js-secret-container')
.insertAdjacentHTML('afterbegin', generateValueMarkup('foobarbazdynamic'));
const revealButton = wrapper.querySelector('.js-secret-value-reveal-button'); const revealButton = wrapper.querySelector('.js-secret-value-reveal-button');
const values = wrapper.querySelectorAll('.js-secret-value'); const values = wrapper.querySelectorAll('.js-secret-value');
...@@ -184,24 +181,24 @@ describe('setupSecretValues', () => { ...@@ -184,24 +181,24 @@ describe('setupSecretValues', () => {
revealButton.click(); revealButton.click();
expect(values.length).toEqual(4); expect(values.length).toEqual(4);
values.forEach((value) => { values.forEach(value => {
expect(value.classList.contains('hide')).toEqual(false); expect(value.classList.contains('hide')).toEqual(false);
}); });
expect(placeholders.length).toEqual(4); expect(placeholders.length).toEqual(4);
placeholders.forEach((placeholder) => { placeholders.forEach(placeholder => {
expect(placeholder.classList.contains('hide')).toEqual(true); expect(placeholder.classList.contains('hide')).toEqual(true);
}); });
revealButton.click(); revealButton.click();
expect(values.length).toEqual(4); expect(values.length).toEqual(4);
values.forEach((value) => { values.forEach(value => {
expect(value.classList.contains('hide')).toEqual(true); expect(value.classList.contains('hide')).toEqual(true);
}); });
expect(placeholders.length).toEqual(4); expect(placeholders.length).toEqual(4);
placeholders.forEach((placeholder) => { placeholders.forEach(placeholder => {
expect(placeholder.classList.contains('hide')).toEqual(false); expect(placeholder.classList.contains('hide')).toEqual(false);
}); });
}); });
......
import { import { BoxGeometry } from 'three/build/three.module';
BoxGeometry,
} from 'three/build/three.module';
import MeshObject from '~/blob/3d_viewer/mesh_object'; import MeshObject from '~/blob/3d_viewer/mesh_object';
describe('Mesh object', () => { describe('Mesh object', () => {
it('defaults to non-wireframe material', () => { it('defaults to non-wireframe material', () => {
const object = new MeshObject( const object = new MeshObject(new BoxGeometry(10, 10, 10));
new BoxGeometry(10, 10, 10),
);
expect(object.material.wireframe).toBeFalsy(); expect(object.material.wireframe).toBeFalsy();
}); });
it('changes to wirefame material', () => { it('changes to wirefame material', () => {
const object = new MeshObject( const object = new MeshObject(new BoxGeometry(10, 10, 10));
new BoxGeometry(10, 10, 10),
);
object.changeMaterial('wireframe'); object.changeMaterial('wireframe');
...@@ -23,18 +17,14 @@ describe('Mesh object', () => { ...@@ -23,18 +17,14 @@ describe('Mesh object', () => {
}); });
it('scales object down', () => { it('scales object down', () => {
const object = new MeshObject( const object = new MeshObject(new BoxGeometry(10, 10, 10));
new BoxGeometry(10, 10, 10),
);
const { radius } = object.geometry.boundingSphere; const { radius } = object.geometry.boundingSphere;
expect(radius).not.toBeGreaterThan(4); expect(radius).not.toBeGreaterThan(4);
}); });
it('does not scale object down', () => { it('does not scale object down', () => {
const object = new MeshObject( const object = new MeshObject(new BoxGeometry(1, 1, 1));
new BoxGeometry(1, 1, 1),
);
const { radius } = object.geometry.boundingSphere; const { radius } = object.geometry.boundingSphere;
expect(radius).toBeLessThan(1); expect(radius).toBeLessThan(1);
......
...@@ -16,10 +16,13 @@ describe('Balsamiq integration spec', () => { ...@@ -16,10 +16,13 @@ describe('Balsamiq integration spec', () => {
}); });
describe('successful response', () => { describe('successful response', () => {
beforeEach((done) => { beforeEach(done => {
endpoint = bmprPath; endpoint = bmprPath;
balsamiqViewer.loadFile(endpoint).then(done).catch(done.fail); balsamiqViewer
.loadFile(endpoint)
.then(done)
.catch(done.fail);
}); });
it('does not show loading icon', () => { it('does not show loading icon', () => {
...@@ -32,10 +35,13 @@ describe('Balsamiq integration spec', () => { ...@@ -32,10 +35,13 @@ describe('Balsamiq integration spec', () => {
}); });
describe('error getting file', () => { describe('error getting file', () => {
beforeEach((done) => { beforeEach(done => {
endpoint = 'invalid/path/to/file.bmpr'; endpoint = 'invalid/path/to/file.bmpr';
balsamiqViewer.loadFile(endpoint).then(done.fail, null).catch(done); balsamiqViewer
.loadFile(endpoint)
.then(done.fail, null)
.catch(done);
}); });
it('does not show loading icon', () => { it('does not show loading icon', () => {
......
...@@ -18,9 +18,7 @@ describe('BalsamiqViewer', () => { ...@@ -18,9 +18,7 @@ describe('BalsamiqViewer', () => {
}); });
}); });
describe('fileLoaded', () => { describe('fileLoaded', () => {});
});
describe('loadFile', () => { describe('loadFile', () => {
let xhr; let xhr;
...@@ -64,12 +62,16 @@ describe('BalsamiqViewer', () => { ...@@ -64,12 +62,16 @@ describe('BalsamiqViewer', () => {
viewer = jasmine.createSpyObj('viewer', ['appendChild']); viewer = jasmine.createSpyObj('viewer', ['appendChild']);
previews = [document.createElement('ul'), document.createElement('ul')]; previews = [document.createElement('ul'), document.createElement('ul')];
balsamiqViewer = jasmine.createSpyObj('balsamiqViewer', ['initDatabase', 'getPreviews', 'renderPreview']); balsamiqViewer = jasmine.createSpyObj('balsamiqViewer', [
'initDatabase',
'getPreviews',
'renderPreview',
]);
balsamiqViewer.viewer = viewer; balsamiqViewer.viewer = viewer;
balsamiqViewer.getPreviews.and.returnValue(previews); balsamiqViewer.getPreviews.and.returnValue(previews);
balsamiqViewer.renderPreview.and.callFake(preview => preview); balsamiqViewer.renderPreview.and.callFake(preview => preview);
viewer.appendChild.and.callFake((containerElement) => { viewer.appendChild.and.callFake(containerElement => {
container = containerElement; container = containerElement;
}); });
...@@ -198,7 +200,9 @@ describe('BalsamiqViewer', () => { ...@@ -198,7 +200,9 @@ describe('BalsamiqViewer', () => {
}); });
it('should call database.exec', () => { it('should call database.exec', () => {
expect(database.exec).toHaveBeenCalledWith(`SELECT * FROM resources WHERE id = '${resourceID}'`); expect(database.exec).toHaveBeenCalledWith(
`SELECT * FROM resources WHERE id = '${resourceID}'`,
);
}); });
it('should return the selected resource', () => { it('should return the selected resource', () => {
...@@ -281,7 +285,7 @@ describe('BalsamiqViewer', () => { ...@@ -281,7 +285,7 @@ describe('BalsamiqViewer', () => {
expect(BalsamiqViewer.parseTitle).toHaveBeenCalledWith(resource); expect(BalsamiqViewer.parseTitle).toHaveBeenCalledWith(resource);
}); });
it('should return the template string', function () { it('should return the template string', function() {
expect(renderTemplate.replace(/\s/g, '')).toEqual(template.replace(/\s/g, '')); expect(renderTemplate.replace(/\s/g, '')).toEqual(template.replace(/\s/g, ''));
}); });
}); });
......
import $ from 'jquery'; import $ from 'jquery';
import BlobFileDropzone from '~/blob/blob_file_dropzone'; import BlobFileDropzone from '~/blob/blob_file_dropzone';
describe('BlobFileDropzone', function () { describe('BlobFileDropzone', function() {
preloadFixtures('blob/show.html.raw'); preloadFixtures('blob/show.html.raw');
beforeEach(() => { beforeEach(() => {
......
...@@ -16,8 +16,7 @@ describe('BlobForkSuggestion', () => { ...@@ -16,8 +16,7 @@ describe('BlobForkSuggestion', () => {
cancelButtons: cancelButton, cancelButtons: cancelButton,
suggestionSections: suggestionSection, suggestionSections: suggestionSection,
actionTextPieces: actionTextPiece, actionTextPieces: actionTextPiece,
}) }).init();
.init();
}); });
afterEach(() => { afterEach(() => {
......
...@@ -12,29 +12,27 @@ describe('iPython notebook renderer', () => { ...@@ -12,29 +12,27 @@ describe('iPython notebook renderer', () => {
it('shows loading icon', () => { it('shows loading icon', () => {
renderNotebook(); renderNotebook();
expect( expect(document.querySelector('.loading')).not.toBeNull();
document.querySelector('.loading'),
).not.toBeNull();
}); });
describe('successful response', () => { describe('successful response', () => {
let mock; let mock;
beforeEach((done) => { beforeEach(done => {
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
mock.onGet('/test').reply(200, { mock.onGet('/test').reply(200, {
cells: [{ cells: [
cell_type: 'markdown', {
source: ['# test'], cell_type: 'markdown',
}, { source: ['# test'],
cell_type: 'code', },
execution_count: 1, {
source: [ cell_type: 'code',
'def test(str)', execution_count: 1,
' return str', source: ['def test(str)', ' return str'],
], outputs: [],
outputs: [], },
}], ],
}); });
renderNotebook(); renderNotebook();
...@@ -49,35 +47,23 @@ describe('iPython notebook renderer', () => { ...@@ -49,35 +47,23 @@ describe('iPython notebook renderer', () => {
}); });
it('does not show loading icon', () => { it('does not show loading icon', () => {
expect( expect(document.querySelector('.loading')).toBeNull();
document.querySelector('.loading'),
).toBeNull();
}); });
it('renders the notebook', () => { it('renders the notebook', () => {
expect( expect(document.querySelector('.md')).not.toBeNull();
document.querySelector('.md'),
).not.toBeNull();
}); });
it('renders the markdown cell', () => { it('renders the markdown cell', () => {
expect( expect(document.querySelector('h1')).not.toBeNull();
document.querySelector('h1'),
).not.toBeNull();
expect( expect(document.querySelector('h1').textContent.trim()).toBe('test');
document.querySelector('h1').textContent.trim(),
).toBe('test');
}); });
it('highlights code', () => { it('highlights code', () => {
expect( expect(document.querySelector('.token')).not.toBeNull();
document.querySelector('.token'),
).not.toBeNull();
expect( expect(document.querySelector('.language-python')).not.toBeNull();
document.querySelector('.language-python'),
).not.toBeNull();
}); });
}); });
...@@ -86,12 +72,10 @@ describe('iPython notebook renderer', () => { ...@@ -86,12 +72,10 @@ describe('iPython notebook renderer', () => {
beforeEach(done => { beforeEach(done => {
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
mock mock.onGet('/test').reply(() =>
.onGet('/test') // eslint-disable-next-line prefer-promise-reject-errors
.reply(() => Promise.reject({ status: 200, data: '{ "cells": [{"cell_type": "markdown"} }' }),
// eslint-disable-next-line prefer-promise-reject-errors );
Promise.reject({ status: 200, data: '{ "cells": [{"cell_type": "markdown"} }' }),
);
renderNotebook(); renderNotebook();
...@@ -105,22 +89,20 @@ describe('iPython notebook renderer', () => { ...@@ -105,22 +89,20 @@ describe('iPython notebook renderer', () => {
}); });
it('does not show loading icon', () => { it('does not show loading icon', () => {
expect( expect(document.querySelector('.loading')).toBeNull();
document.querySelector('.loading'),
).toBeNull();
}); });
it('shows error message', () => { it('shows error message', () => {
expect( expect(document.querySelector('.md').textContent.trim()).toBe(
document.querySelector('.md').textContent.trim(), 'An error occurred whilst parsing the file.',
).toBe('An error occurred whilst parsing the file.'); );
}); });
}); });
describe('error getting file', () => { describe('error getting file', () => {
let mock; let mock;
beforeEach((done) => { beforeEach(done => {
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
mock.onGet('/test').reply(500, ''); mock.onGet('/test').reply(500, '');
...@@ -136,15 +118,13 @@ describe('iPython notebook renderer', () => { ...@@ -136,15 +118,13 @@ describe('iPython notebook renderer', () => {
}); });
it('does not show loading icon', () => { it('does not show loading icon', () => {
expect( expect(document.querySelector('.loading')).toBeNull();
document.querySelector('.loading'),
).toBeNull();
}); });
it('shows error message', () => { it('shows error message', () => {
expect( expect(document.querySelector('.md').textContent.trim()).toBe(
document.querySelector('.md').textContent.trim(), 'An error occurred whilst loading the file. Please try again later.',
).toBe('An error occurred whilst loading the file. Please try again later.'); );
}); });
}); });
}); });
...@@ -5,7 +5,7 @@ describe('PDF renderer', () => { ...@@ -5,7 +5,7 @@ describe('PDF renderer', () => {
let viewer; let viewer;
let app; let app;
const checkLoaded = (done) => { const checkLoaded = done => {
if (app.loading) { if (app.loading) {
setTimeout(() => { setTimeout(() => {
checkLoaded(done); checkLoaded(done);
...@@ -26,39 +26,31 @@ describe('PDF renderer', () => { ...@@ -26,39 +26,31 @@ describe('PDF renderer', () => {
it('shows loading icon', () => { it('shows loading icon', () => {
renderPDF(); renderPDF();
expect( expect(document.querySelector('.loading')).not.toBeNull();
document.querySelector('.loading'),
).not.toBeNull();
}); });
describe('successful response', () => { describe('successful response', () => {
beforeEach((done) => { beforeEach(done => {
app = renderPDF(); app = renderPDF();
checkLoaded(done); checkLoaded(done);
}); });
it('does not show loading icon', () => { it('does not show loading icon', () => {
expect( expect(document.querySelector('.loading')).toBeNull();
document.querySelector('.loading'),
).toBeNull();
}); });
it('renders the PDF', () => { it('renders the PDF', () => {
expect( expect(document.querySelector('.pdf-viewer')).not.toBeNull();
document.querySelector('.pdf-viewer'),
).not.toBeNull();
}); });
it('renders the PDF page', () => { it('renders the PDF page', () => {
expect( expect(document.querySelector('.pdf-page')).not.toBeNull();
document.querySelector('.pdf-page'),
).not.toBeNull();
}); });
}); });
describe('error getting file', () => { describe('error getting file', () => {
beforeEach((done) => { beforeEach(done => {
viewer.dataset.endpoint = 'invalid/path/to/file.pdf'; viewer.dataset.endpoint = 'invalid/path/to/file.pdf';
app = renderPDF(); app = renderPDF();
...@@ -66,15 +58,13 @@ describe('PDF renderer', () => { ...@@ -66,15 +58,13 @@ describe('PDF renderer', () => {
}); });
it('does not show loading icon', () => { it('does not show loading icon', () => {
expect( expect(document.querySelector('.loading')).toBeNull();
document.querySelector('.loading'),
).toBeNull();
}); });
it('shows error message', () => { it('shows error message', () => {
expect( expect(document.querySelector('.md').textContent.trim()).toBe(
document.querySelector('.md').textContent.trim(), 'An error occurred whilst loading the file. Please try again later.',
).toBe('An error occurred whilst loading the file. Please try again later.'); );
}); });
}); });
}); });
...@@ -4,15 +4,13 @@ import SketchLoader from '~/blob/sketch'; ...@@ -4,15 +4,13 @@ import SketchLoader from '~/blob/sketch';
describe('Sketch viewer', () => { describe('Sketch viewer', () => {
const generateZipFileArrayBuffer = (zipFile, resolve, done) => { const generateZipFileArrayBuffer = (zipFile, resolve, done) => {
zipFile zipFile.generateAsync({ type: 'arrayBuffer' }).then(content => {
.generateAsync({ type: 'arrayBuffer' }) resolve(content);
.then((content) => {
resolve(content); setTimeout(() => {
done();
setTimeout(() => { }, 100);
done(); });
}, 100);
});
}; };
preloadFixtures('static/sketch_viewer.html.raw'); preloadFixtures('static/sketch_viewer.html.raw');
...@@ -22,60 +20,63 @@ describe('Sketch viewer', () => { ...@@ -22,60 +20,63 @@ describe('Sketch viewer', () => {
}); });
describe('with error message', () => { describe('with error message', () => {
beforeEach((done) => { beforeEach(done => {
spyOn(SketchLoader.prototype, 'getZipFile').and.callFake(() => new Promise((resolve, reject) => { spyOn(SketchLoader.prototype, 'getZipFile').and.callFake(
reject(); () =>
new Promise((resolve, reject) => {
setTimeout(() => { reject();
done();
}); setTimeout(() => {
})); done();
});
}),
);
new SketchLoader(document.getElementById('js-sketch-viewer')); new SketchLoader(document.getElementById('js-sketch-viewer'));
}); });
it('renders error message', () => { it('renders error message', () => {
expect( expect(document.querySelector('#js-sketch-viewer p')).not.toBeNull();
document.querySelector('#js-sketch-viewer p'),
).not.toBeNull();
expect( expect(document.querySelector('#js-sketch-viewer p').textContent.trim()).toContain(
document.querySelector('#js-sketch-viewer p').textContent.trim(), 'Cannot show preview.',
).toContain('Cannot show preview.'); );
}); });
it('removes render the loading icon', () => { it('removes render the loading icon', () => {
expect( expect(document.querySelector('.js-loading-icon')).toBeNull();
document.querySelector('.js-loading-icon'),
).toBeNull();
}); });
}); });
describe('success', () => { describe('success', () => {
beforeEach((done) => { beforeEach(done => {
spyOn(SketchLoader.prototype, 'getZipFile').and.callFake(() => new Promise((resolve) => { spyOn(SketchLoader.prototype, 'getZipFile').and.callFake(
const zipFile = new JSZip(); () =>
zipFile.folder('previews') new Promise(resolve => {
.file('preview.png', 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAMAAAAoyzS7AAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAA1JREFUeNoBAgD9/wAAAAIAAVMrnDAAAAAASUVORK5CYII=', { const zipFile = new JSZip();
base64: true, zipFile
}); .folder('previews')
.file(
generateZipFileArrayBuffer(zipFile, resolve, done); 'preview.png',
})); 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAMAAAAoyzS7AAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAA1JREFUeNoBAgD9/wAAAAIAAVMrnDAAAAAASUVORK5CYII=',
{
base64: true,
},
);
generateZipFileArrayBuffer(zipFile, resolve, done);
}),
);
new SketchLoader(document.getElementById('js-sketch-viewer')); new SketchLoader(document.getElementById('js-sketch-viewer'));
}); });
it('does not render error message', () => { it('does not render error message', () => {
expect( expect(document.querySelector('#js-sketch-viewer p')).toBeNull();
document.querySelector('#js-sketch-viewer p'),
).toBeNull();
}); });
it('removes render the loading icon', () => { it('removes render the loading icon', () => {
expect( expect(document.querySelector('.js-loading-icon')).toBeNull();
document.querySelector('.js-loading-icon'),
).toBeNull();
}); });
it('renders preview img', () => { it('renders preview img', () => {
...@@ -95,24 +96,25 @@ describe('Sketch viewer', () => { ...@@ -95,24 +96,25 @@ describe('Sketch viewer', () => {
}); });
describe('incorrect file', () => { describe('incorrect file', () => {
beforeEach((done) => { beforeEach(done => {
spyOn(SketchLoader.prototype, 'getZipFile').and.callFake(() => new Promise((resolve) => { spyOn(SketchLoader.prototype, 'getZipFile').and.callFake(
const zipFile = new JSZip(); () =>
new Promise(resolve => {
const zipFile = new JSZip();
generateZipFileArrayBuffer(zipFile, resolve, done); generateZipFileArrayBuffer(zipFile, resolve, done);
})); }),
);
new SketchLoader(document.getElementById('js-sketch-viewer')); new SketchLoader(document.getElementById('js-sketch-viewer'));
}); });
it('renders error message', () => { it('renders error message', () => {
expect( expect(document.querySelector('#js-sketch-viewer p')).not.toBeNull();
document.querySelector('#js-sketch-viewer p'),
).not.toBeNull();
expect( expect(document.querySelector('#js-sketch-viewer p').textContent.trim()).toContain(
document.querySelector('#js-sketch-viewer p').textContent.trim(), 'Cannot show preview.',
).toContain('Cannot show preview.'); );
}); });
}); });
}); });
...@@ -35,12 +35,13 @@ describe('Blob viewer', () => { ...@@ -35,12 +35,13 @@ describe('Blob viewer', () => {
window.location.hash = ''; window.location.hash = '';
}); });
it('loads source file after switching views', (done) => { it('loads source file after switching views', done => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click(); document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(() => { setTimeout(() => {
expect( expect(
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]') document
.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]')
.classList.contains('hidden'), .classList.contains('hidden'),
).toBeFalsy(); ).toBeFalsy();
...@@ -48,14 +49,15 @@ describe('Blob viewer', () => { ...@@ -48,14 +49,15 @@ describe('Blob viewer', () => {
}); });
}); });
it('loads source file when line number is in hash', (done) => { it('loads source file when line number is in hash', done => {
window.location.hash = '#L1'; window.location.hash = '#L1';
new BlobViewer(); new BlobViewer();
setTimeout(() => { setTimeout(() => {
expect( expect(
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]') document
.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]')
.classList.contains('hidden'), .classList.contains('hidden'),
).toBeFalsy(); ).toBeFalsy();
...@@ -63,12 +65,13 @@ describe('Blob viewer', () => { ...@@ -63,12 +65,13 @@ describe('Blob viewer', () => {
}); });
}); });
it('doesnt reload file if already loaded', (done) => { it('doesnt reload file if already loaded', done => {
const asyncClick = () => new Promise((resolve) => { const asyncClick = () =>
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click(); new Promise(resolve => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(resolve); setTimeout(resolve);
}); });
asyncClick() asyncClick()
.then(() => asyncClick()) .then(() => asyncClick())
...@@ -93,15 +96,13 @@ describe('Blob viewer', () => { ...@@ -93,15 +96,13 @@ describe('Blob viewer', () => {
}); });
it('disabled on load', () => { it('disabled on load', () => {
expect( expect(copyButton.classList.contains('disabled')).toBeTruthy();
copyButton.classList.contains('disabled'),
).toBeTruthy();
}); });
it('has tooltip when disabled', () => { it('has tooltip when disabled', () => {
expect( expect(copyButton.getAttribute('data-original-title')).toBe(
copyButton.getAttribute('data-original-title'), 'Switch to the source to copy it to the clipboard',
).toBe('Switch to the source to copy it to the clipboard'); );
}); });
it('is blurred when clicked and disabled', () => { it('is blurred when clicked and disabled', () => {
...@@ -121,25 +122,21 @@ describe('Blob viewer', () => { ...@@ -121,25 +122,21 @@ describe('Blob viewer', () => {
expect(copyButton.blur).not.toHaveBeenCalled(); expect(copyButton.blur).not.toHaveBeenCalled();
}); });
it('enables after switching to simple view', (done) => { it('enables after switching to simple view', done => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click(); document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(() => { setTimeout(() => {
expect( expect(copyButton.classList.contains('disabled')).toBeFalsy();
copyButton.classList.contains('disabled'),
).toBeFalsy();
done(); done();
}); });
}); });
it('updates tooltip after switching to simple view', (done) => { it('updates tooltip after switching to simple view', done => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click(); document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(() => { setTimeout(() => {
expect( expect(copyButton.getAttribute('data-original-title')).toBe('Copy source to clipboard');
copyButton.getAttribute('data-original-title'),
).toBe('Copy source to clipboard');
done(); done();
}); });
...@@ -162,9 +159,7 @@ describe('Blob viewer', () => { ...@@ -162,9 +159,7 @@ describe('Blob viewer', () => {
blob.switchToViewer('simple'); blob.switchToViewer('simple');
expect( expect(simpleBtn.classList.contains('active')).toBeTruthy();
simpleBtn.classList.contains('active'),
).toBeTruthy();
expect(simpleBtn.blur).toHaveBeenCalled(); expect(simpleBtn.blur).toHaveBeenCalled();
}); });
......
...@@ -7,29 +7,35 @@ describe('Boards blank state', () => { ...@@ -7,29 +7,35 @@ describe('Boards blank state', () => {
let vm; let vm;
let fail = false; let fail = false;
beforeEach((done) => { beforeEach(done => {
const Comp = Vue.extend(BoardBlankState); const Comp = Vue.extend(BoardBlankState);
boardsStore.create(); boardsStore.create();
gl.boardService = mockBoardService(); gl.boardService = mockBoardService();
spyOn(gl.boardService, 'generateDefaultLists').and.callFake(() => new Promise((resolve, reject) => { spyOn(gl.boardService, 'generateDefaultLists').and.callFake(
if (fail) { () =>
reject(); new Promise((resolve, reject) => {
} else { if (fail) {
resolve({ reject();
data: [{ } else {
id: 1, resolve({
title: 'To Do', data: [
label: { id: 1 }, {
}, { id: 1,
id: 2, title: 'To Do',
title: 'Doing', label: { id: 1 },
label: { id: 2 }, },
}], {
}); id: 2,
} title: 'Doing',
})); label: { id: 2 },
},
],
});
}
}),
);
vm = new Comp(); vm = new Comp();
...@@ -40,20 +46,18 @@ describe('Boards blank state', () => { ...@@ -40,20 +46,18 @@ describe('Boards blank state', () => {
}); });
it('renders pre-defined labels', () => { it('renders pre-defined labels', () => {
expect( expect(vm.$el.querySelectorAll('.board-blank-state-list li').length).toBe(2);
vm.$el.querySelectorAll('.board-blank-state-list li').length,
).toBe(2);
expect( expect(vm.$el.querySelectorAll('.board-blank-state-list li')[0].textContent.trim()).toEqual(
vm.$el.querySelectorAll('.board-blank-state-list li')[0].textContent.trim(), 'To Do',
).toEqual('To Do'); );
expect( expect(vm.$el.querySelectorAll('.board-blank-state-list li')[1].textContent.trim()).toEqual(
vm.$el.querySelectorAll('.board-blank-state-list li')[1].textContent.trim(), 'Doing',
).toEqual('Doing'); );
}); });
it('clears blank state', (done) => { it('clears blank state', done => {
vm.$el.querySelector('.btn-default').click(); vm.$el.querySelector('.btn-default').click();
setTimeout(() => { setTimeout(() => {
...@@ -63,7 +67,7 @@ describe('Boards blank state', () => { ...@@ -63,7 +67,7 @@ describe('Boards blank state', () => {
}); });
}); });
it('creates pre-defined labels', (done) => { it('creates pre-defined labels', done => {
vm.$el.querySelector('.btn-success').click(); vm.$el.querySelector('.btn-success').click();
setTimeout(() => { setTimeout(() => {
...@@ -75,7 +79,7 @@ describe('Boards blank state', () => { ...@@ -75,7 +79,7 @@ describe('Boards blank state', () => {
}); });
}); });
it('resets the store if request fails', (done) => { it('resets the store if request fails', done => {
fail = true; fail = true;
vm.$el.querySelector('.btn-success').click(); vm.$el.querySelector('.btn-success').click();
......
...@@ -18,7 +18,7 @@ describe('Board card', () => { ...@@ -18,7 +18,7 @@ describe('Board card', () => {
let vm; let vm;
let mock; let mock;
beforeEach((done) => { beforeEach(done => {
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
mock.onAny().reply(boardsMockInterceptor); mock.onAny().reply(boardsMockInterceptor);
...@@ -71,7 +71,7 @@ describe('Board card', () => { ...@@ -71,7 +71,7 @@ describe('Board card', () => {
expect(vm.$el.classList.contains('user-can-drag')).toBe(true); expect(vm.$el.classList.contains('user-can-drag')).toBe(true);
}); });
it('does not add user-can-drag class disabled', (done) => { it('does not add user-can-drag class disabled', done => {
vm.disabled = true; vm.disabled = true;
setTimeout(() => { setTimeout(() => {
...@@ -84,7 +84,7 @@ describe('Board card', () => { ...@@ -84,7 +84,7 @@ describe('Board card', () => {
expect(vm.$el.classList.contains('is-disabled')).toBe(false); expect(vm.$el.classList.contains('is-disabled')).toBe(false);
}); });
it('adds disabled class is disabled is true', (done) => { it('adds disabled class is disabled is true', done => {
vm.disabled = true; vm.disabled = true;
setTimeout(() => { setTimeout(() => {
...@@ -96,8 +96,23 @@ describe('Board card', () => { ...@@ -96,8 +96,23 @@ describe('Board card', () => {
describe('mouse events', () => { describe('mouse events', () => {
const triggerEvent = (eventName, el = vm.$el) => { const triggerEvent = (eventName, el = vm.$el) => {
const event = document.createEvent('MouseEvents'); const event = document.createEvent('MouseEvents');
event.initMouseEvent(eventName, true, true, window, 1, 0, 0, 0, 0, false, false, event.initMouseEvent(
false, false, 0, null); eventName,
true,
true,
window,
1,
0,
0,
0,
0,
false,
false,
false,
false,
0,
null,
);
el.dispatchEvent(event); el.dispatchEvent(event);
}; };
...@@ -134,13 +149,15 @@ describe('Board card', () => { ...@@ -134,13 +149,15 @@ describe('Board card', () => {
expect(boardsStore.detail.issue).toEqual({}); expect(boardsStore.detail.issue).toEqual({});
}); });
it('does not set detail issue if img is clicked', (done) => { it('does not set detail issue if img is clicked', done => {
vm.issue.assignees = [new ListAssignee({ vm.issue.assignees = [
id: 1, new ListAssignee({
name: 'testing 123', id: 1,
username: 'test', name: 'testing 123',
avatar: 'test_image', username: 'test',
})]; avatar: 'test_image',
}),
];
Vue.nextTick(() => { Vue.nextTick(() => {
triggerEvent('mouseup', vm.$el.querySelector('img')); triggerEvent('mouseup', vm.$el.querySelector('img'));
...@@ -167,7 +184,7 @@ describe('Board card', () => { ...@@ -167,7 +184,7 @@ describe('Board card', () => {
expect(boardsStore.detail.list).toEqual(vm.list); expect(boardsStore.detail.list).toEqual(vm.list);
}); });
it('adds active class if detail issue is set', (done) => { it('adds active class if detail issue is set', done => {
vm.detailIssue.issue = vm.issue; vm.detailIssue.issue = vm.issue;
Vue.nextTick() Vue.nextTick()
......
...@@ -28,7 +28,7 @@ describe('Issue boards new issue form', () => { ...@@ -28,7 +28,7 @@ describe('Issue boards new issue form', () => {
return vm.submit(dummySubmitEvent); return vm.submit(dummySubmitEvent);
}; };
beforeEach((done) => { beforeEach(done => {
setFixtures('<div class="test-container"></div>'); setFixtures('<div class="test-container"></div>');
const BoardNewIssueComp = Vue.extend(boardNewIssue); const BoardNewIssueComp = Vue.extend(boardNewIssue);
...@@ -60,7 +60,7 @@ describe('Issue boards new issue form', () => { ...@@ -60,7 +60,7 @@ describe('Issue boards new issue form', () => {
mock.restore(); mock.restore();
}); });
it('calls submit if submit button is clicked', (done) => { it('calls submit if submit button is clicked', done => {
spyOn(vm, 'submit').and.callFake(e => e.preventDefault()); spyOn(vm, 'submit').and.callFake(e => e.preventDefault());
vm.title = 'Testing Title'; vm.title = 'Testing Title';
...@@ -78,7 +78,7 @@ describe('Issue boards new issue form', () => { ...@@ -78,7 +78,7 @@ describe('Issue boards new issue form', () => {
expect(vm.$el.querySelector('.btn-success').disabled).toBe(true); expect(vm.$el.querySelector('.btn-success').disabled).toBe(true);
}); });
it('enables submit button if title is not empty', (done) => { it('enables submit button if title is not empty', done => {
vm.title = 'Testing Title'; vm.title = 'Testing Title';
Vue.nextTick() Vue.nextTick()
...@@ -90,7 +90,7 @@ describe('Issue boards new issue form', () => { ...@@ -90,7 +90,7 @@ describe('Issue boards new issue form', () => {
.catch(done.fail); .catch(done.fail);
}); });
it('clears title after clicking cancel', (done) => { it('clears title after clicking cancel', done => {
vm.$el.querySelector('.btn-default').click(); vm.$el.querySelector('.btn-default').click();
Vue.nextTick() Vue.nextTick()
...@@ -101,7 +101,7 @@ describe('Issue boards new issue form', () => { ...@@ -101,7 +101,7 @@ describe('Issue boards new issue form', () => {
.catch(done.fail); .catch(done.fail);
}); });
it('does not create new issue if title is empty', (done) => { it('does not create new issue if title is empty', done => {
submitIssue() submitIssue()
.then(() => { .then(() => {
expect(list.newIssue).not.toHaveBeenCalled(); expect(list.newIssue).not.toHaveBeenCalled();
...@@ -111,7 +111,7 @@ describe('Issue boards new issue form', () => { ...@@ -111,7 +111,7 @@ describe('Issue boards new issue form', () => {
}); });
describe('submit success', () => { describe('submit success', () => {
it('creates new issue', (done) => { it('creates new issue', done => {
vm.title = 'submit title'; vm.title = 'submit title';
Vue.nextTick() Vue.nextTick()
...@@ -123,7 +123,7 @@ describe('Issue boards new issue form', () => { ...@@ -123,7 +123,7 @@ describe('Issue boards new issue form', () => {
.catch(done.fail); .catch(done.fail);
}); });
it('enables button after submit', (done) => { it('enables button after submit', done => {
vm.title = 'submit issue'; vm.title = 'submit issue';
Vue.nextTick() Vue.nextTick()
...@@ -135,7 +135,7 @@ describe('Issue boards new issue form', () => { ...@@ -135,7 +135,7 @@ describe('Issue boards new issue form', () => {
.catch(done.fail); .catch(done.fail);
}); });
it('clears title after submit', (done) => { it('clears title after submit', done => {
vm.title = 'submit issue'; vm.title = 'submit issue';
Vue.nextTick() Vue.nextTick()
...@@ -147,7 +147,7 @@ describe('Issue boards new issue form', () => { ...@@ -147,7 +147,7 @@ describe('Issue boards new issue form', () => {
.catch(done.fail); .catch(done.fail);
}); });
it('sets detail issue after submit', (done) => { it('sets detail issue after submit', done => {
expect(boardsStore.detail.issue.title).toBe(undefined); expect(boardsStore.detail.issue.title).toBe(undefined);
vm.title = 'submit issue'; vm.title = 'submit issue';
...@@ -160,7 +160,7 @@ describe('Issue boards new issue form', () => { ...@@ -160,7 +160,7 @@ describe('Issue boards new issue form', () => {
.catch(done.fail); .catch(done.fail);
}); });
it('sets detail list after submit', (done) => { it('sets detail list after submit', done => {
vm.title = 'submit issue'; vm.title = 'submit issue';
Vue.nextTick() Vue.nextTick()
...@@ -179,7 +179,7 @@ describe('Issue boards new issue form', () => { ...@@ -179,7 +179,7 @@ describe('Issue boards new issue form', () => {
vm.title = 'error'; vm.title = 'error';
}); });
it('removes issue', (done) => { it('removes issue', done => {
Vue.nextTick() Vue.nextTick()
.then(submitIssue) .then(submitIssue)
.then(() => { .then(() => {
...@@ -189,7 +189,7 @@ describe('Issue boards new issue form', () => { ...@@ -189,7 +189,7 @@ describe('Issue boards new issue form', () => {
.catch(done.fail); .catch(done.fail);
}); });
it('shows error', (done) => { it('shows error', done => {
Vue.nextTick() Vue.nextTick()
.then(submitIssue) .then(submitIssue)
.then(() => { .then(() => {
......
...@@ -23,13 +23,16 @@ describe('Store', () => { ...@@ -23,13 +23,16 @@ describe('Store', () => {
gl.boardService = mockBoardService(); gl.boardService = mockBoardService();
boardsStore.create(); boardsStore.create();
spyOn(gl.boardService, 'moveIssue').and.callFake(() => new Promise((resolve) => { spyOn(gl.boardService, 'moveIssue').and.callFake(
resolve(); () =>
})); new Promise(resolve => {
resolve();
}),
);
Cookies.set('issue_board_welcome_hidden', 'false', { Cookies.set('issue_board_welcome_hidden', 'false', {
expires: 365 * 10, expires: 365 * 10,
path: '' path: '',
}); });
}); });
...@@ -62,7 +65,7 @@ describe('Store', () => { ...@@ -62,7 +65,7 @@ describe('Store', () => {
expect(list).toBeDefined(); expect(list).toBeDefined();
}); });
it('gets issue when new list added', (done) => { it('gets issue when new list added', done => {
boardsStore.addList(listObj); boardsStore.addList(listObj);
const list = boardsStore.findList('id', listObj.id); const list = boardsStore.findList('id', listObj.id);
...@@ -75,7 +78,7 @@ describe('Store', () => { ...@@ -75,7 +78,7 @@ describe('Store', () => {
}, 0); }, 0);
}); });
it('persists new list', (done) => { it('persists new list', done => {
boardsStore.new({ boardsStore.new({
title: 'Test', title: 'Test',
list_type: 'label', list_type: 'label',
...@@ -83,8 +86,8 @@ describe('Store', () => { ...@@ -83,8 +86,8 @@ describe('Store', () => {
id: 1, id: 1,
title: 'Testing', title: 'Testing',
color: 'red', color: 'red',
description: 'testing;' description: 'testing;',
} },
}); });
expect(boardsStore.state.lists.length).toBe(1); expect(boardsStore.state.lists.length).toBe(1);
...@@ -111,7 +114,7 @@ describe('Store', () => { ...@@ -111,7 +114,7 @@ describe('Store', () => {
it('check for blank state adding when closed list exist', () => { it('check for blank state adding when closed list exist', () => {
boardsStore.addList({ boardsStore.addList({
list_type: 'closed' list_type: 'closed',
}); });
expect(boardsStore.shouldAddBlankState()).toBe(true); expect(boardsStore.shouldAddBlankState()).toBe(true);
...@@ -146,7 +149,7 @@ describe('Store', () => { ...@@ -146,7 +149,7 @@ describe('Store', () => {
expect(listOne.position).toBe(1); expect(listOne.position).toBe(1);
}); });
it('moves an issue from one list to another', (done) => { it('moves an issue from one list to another', done => {
const listOne = boardsStore.addList(listObj); const listOne = boardsStore.addList(listObj);
const listTwo = boardsStore.addList(listObjDuplicate); const listTwo = boardsStore.addList(listObjDuplicate);
...@@ -165,7 +168,7 @@ describe('Store', () => { ...@@ -165,7 +168,7 @@ describe('Store', () => {
}, 0); }, 0);
}); });
it('moves an issue from backlog to a list', (done) => { it('moves an issue from backlog to a list', done => {
const backlog = boardsStore.addList({ const backlog = boardsStore.addList({
...listObj, ...listObj,
list_type: 'backlog', list_type: 'backlog',
...@@ -187,7 +190,7 @@ describe('Store', () => { ...@@ -187,7 +190,7 @@ describe('Store', () => {
}, 0); }, 0);
}); });
it('moves issue to top of another list', (done) => { it('moves issue to top of another list', done => {
const listOne = boardsStore.addList(listObj); const listOne = boardsStore.addList(listObj);
const listTwo = boardsStore.addList(listObjDuplicate); const listTwo = boardsStore.addList(listObjDuplicate);
...@@ -210,7 +213,7 @@ describe('Store', () => { ...@@ -210,7 +213,7 @@ describe('Store', () => {
}, 0); }, 0);
}); });
it('moves issue to bottom of another list', (done) => { it('moves issue to bottom of another list', done => {
const listOne = boardsStore.addList(listObj); const listOne = boardsStore.addList(listObj);
const listTwo = boardsStore.addList(listObjDuplicate); const listTwo = boardsStore.addList(listObjDuplicate);
...@@ -233,7 +236,7 @@ describe('Store', () => { ...@@ -233,7 +236,7 @@ describe('Store', () => {
}, 0); }, 0);
}); });
it('moves issue in list', (done) => { it('moves issue in list', done => {
const issue = new ListIssue({ const issue = new ListIssue({
title: 'Testing', title: 'Testing',
id: 2, id: 2,
......
...@@ -21,18 +21,22 @@ describe('Issue model', () => { ...@@ -21,18 +21,22 @@ describe('Issue model', () => {
id: 1, id: 1,
iid: 1, iid: 1,
confidential: false, confidential: false,
labels: [{ labels: [
id: 1, {
title: 'test', id: 1,
color: 'red', title: 'test',
description: 'testing' color: 'red',
}], description: 'testing',
assignees: [{ },
id: 1, ],
name: 'name', assignees: [
username: 'username', {
avatar_url: 'http://avatar_url', id: 1,
}], name: 'name',
username: 'username',
avatar_url: 'http://avatar_url',
},
],
}); });
}); });
...@@ -45,7 +49,7 @@ describe('Issue model', () => { ...@@ -45,7 +49,7 @@ describe('Issue model', () => {
id: 2, id: 2,
title: 'bug', title: 'bug',
color: 'blue', color: 'blue',
description: 'bugs!' description: 'bugs!',
}); });
expect(issue.labels.length).toBe(2); expect(issue.labels.length).toBe(2);
...@@ -56,7 +60,7 @@ describe('Issue model', () => { ...@@ -56,7 +60,7 @@ describe('Issue model', () => {
id: 2, id: 2,
title: 'test', title: 'test',
color: 'blue', color: 'blue',
description: 'bugs!' description: 'bugs!',
}); });
expect(issue.labels.length).toBe(1); expect(issue.labels.length).toBe(1);
...@@ -80,7 +84,7 @@ describe('Issue model', () => { ...@@ -80,7 +84,7 @@ describe('Issue model', () => {
id: 2, id: 2,
title: 'bug', title: 'bug',
color: 'blue', color: 'blue',
description: 'bugs!' description: 'bugs!',
}); });
expect(issue.labels.length).toBe(2); expect(issue.labels.length).toBe(2);
...@@ -158,7 +162,7 @@ describe('Issue model', () => { ...@@ -158,7 +162,7 @@ describe('Issue model', () => {
}); });
describe('update', () => { describe('update', () => {
it('passes assignee ids when there are assignees', (done) => { it('passes assignee ids when there are assignees', done => {
spyOn(Vue.http, 'patch').and.callFake((url, data) => { spyOn(Vue.http, 'patch').and.callFake((url, data) => {
expect(data.issue.assignee_ids).toEqual([1]); expect(data.issue.assignee_ids).toEqual([1]);
done(); done();
...@@ -167,7 +171,7 @@ describe('Issue model', () => { ...@@ -167,7 +171,7 @@ describe('Issue model', () => {
issue.update('url'); issue.update('url');
}); });
it('passes assignee ids of [0] when there are no assignees', (done) => { it('passes assignee ids of [0] when there are no assignees', done => {
spyOn(Vue.http, 'patch').and.callFake((url, data) => { spyOn(Vue.http, 'patch').and.callFake((url, data) => {
expect(data.issue.assignee_ids).toEqual([0]); expect(data.issue.assignee_ids).toEqual([0]);
done(); done();
......
...@@ -31,21 +31,21 @@ describe('List model', () => { ...@@ -31,21 +31,21 @@ describe('List model', () => {
mock.restore(); mock.restore();
}); });
it('gets issues when created', (done) => { it('gets issues when created', done => {
setTimeout(() => { setTimeout(() => {
expect(list.issues.length).toBe(1); expect(list.issues.length).toBe(1);
done(); done();
}, 0); }, 0);
}); });
it('saves list and returns ID', (done) => { it('saves list and returns ID', done => {
list = new List({ list = new List({
title: 'test', title: 'test',
label: { label: {
id: _.random(10000), id: _.random(10000),
title: 'test', title: 'test',
color: 'red' color: 'red',
} },
}); });
list.save(); list.save();
...@@ -57,7 +57,7 @@ describe('List model', () => { ...@@ -57,7 +57,7 @@ describe('List model', () => {
}, 0); }, 0);
}); });
it('destroys the list', (done) => { it('destroys the list', done => {
boardsStore.addList(listObj); boardsStore.addList(listObj);
list = boardsStore.findList('id', listObj.id); list = boardsStore.findList('id', listObj.id);
...@@ -70,7 +70,7 @@ describe('List model', () => { ...@@ -70,7 +70,7 @@ describe('List model', () => {
}, 0); }, 0);
}); });
it('gets issue from list', (done) => { it('gets issue from list', done => {
setTimeout(() => { setTimeout(() => {
const issue = list.findIssue(1); const issue = list.findIssue(1);
...@@ -79,7 +79,7 @@ describe('List model', () => { ...@@ -79,7 +79,7 @@ describe('List model', () => {
}, 0); }, 0);
}); });
it('removes issue', (done) => { it('removes issue', done => {
setTimeout(() => { setTimeout(() => {
const issue = list.findIssue(1); const issue = list.findIssue(1);
...@@ -109,8 +109,13 @@ describe('List model', () => { ...@@ -109,8 +109,13 @@ describe('List model', () => {
listDup.updateIssueLabel(issue, list); listDup.updateIssueLabel(issue, list);
expect(gl.boardService.moveIssue) expect(gl.boardService.moveIssue).toHaveBeenCalledWith(
.toHaveBeenCalledWith(issue.id, list.id, listDup.id, undefined, undefined); issue.id,
list.id,
listDup.id,
undefined,
undefined,
);
}); });
describe('page number', () => { describe('page number', () => {
...@@ -120,14 +125,16 @@ describe('List model', () => { ...@@ -120,14 +125,16 @@ describe('List model', () => {
it('increase page number if current issue count is more than the page size', () => { it('increase page number if current issue count is more than the page size', () => {
for (let i = 0; i < 30; i += 1) { for (let i = 0; i < 30; i += 1) {
list.issues.push(new ListIssue({ list.issues.push(
title: 'Testing', new ListIssue({
id: _.random(10000) + i, title: 'Testing',
iid: _.random(10000) + i, id: _.random(10000) + i,
confidential: false, iid: _.random(10000) + i,
labels: [list.label], confidential: false,
assignees: [], labels: [list.label],
})); assignees: [],
}),
);
} }
list.issuesSize = 50; list.issuesSize = 50;
...@@ -140,13 +147,15 @@ describe('List model', () => { ...@@ -140,13 +147,15 @@ describe('List model', () => {
}); });
it('does not increase page number if issue count is less than the page size', () => { it('does not increase page number if issue count is less than the page size', () => {
list.issues.push(new ListIssue({ list.issues.push(
title: 'Testing', new ListIssue({
id: _.random(10000), title: 'Testing',
confidential: false, id: _.random(10000),
labels: [list.label], confidential: false,
assignees: [], labels: [list.label],
})); assignees: [],
}),
);
list.issuesSize = 2; list.issuesSize = 2;
list.nextPage(); list.nextPage();
...@@ -158,21 +167,25 @@ describe('List model', () => { ...@@ -158,21 +167,25 @@ describe('List model', () => {
describe('newIssue', () => { describe('newIssue', () => {
beforeEach(() => { beforeEach(() => {
spyOn(gl.boardService, 'newIssue').and.returnValue(Promise.resolve({ spyOn(gl.boardService, 'newIssue').and.returnValue(
data: { Promise.resolve({
id: 42, data: {
}, id: 42,
})); },
}),
);
}); });
it('adds new issue to top of list', (done) => { it('adds new issue to top of list', done => {
list.issues.push(new ListIssue({ list.issues.push(
title: 'Testing', new ListIssue({
id: _.random(10000), title: 'Testing',
confidential: false, id: _.random(10000),
labels: [list.label], confidential: false,
assignees: [], labels: [list.label],
})); assignees: [],
}),
);
const dummyIssue = new ListIssue({ const dummyIssue = new ListIssue({
title: 'new issue', title: 'new issue',
id: _.random(10000), id: _.random(10000),
...@@ -181,7 +194,8 @@ describe('List model', () => { ...@@ -181,7 +194,8 @@ describe('List model', () => {
assignees: [], assignees: [],
}); });
list.newIssue(dummyIssue) list
.newIssue(dummyIssue)
.then(() => { .then(() => {
expect(list.issues.length).toBe(2); expect(list.issues.length).toBe(2);
expect(list.issues[0]).toBe(dummyIssue); expect(list.issues[0]).toBe(dummyIssue);
......
...@@ -3,47 +3,45 @@ ...@@ -3,47 +3,45 @@
import $ from 'jquery'; import $ from 'jquery';
import '~/commons/bootstrap'; import '~/commons/bootstrap';
(function() { describe('Bootstrap jQuery extensions', function() {
describe('Bootstrap jQuery extensions', function() { describe('disable', function() {
describe('disable', function() { beforeEach(function() {
beforeEach(function() { return setFixtures('<input type="text" />');
return setFixtures('<input type="text" />');
});
it('adds the disabled attribute', function() {
var $input;
$input = $('input').first();
$input.disable();
expect($input).toHaveAttr('disabled', 'disabled');
});
return it('adds the disabled class', function() {
var $input;
$input = $('input').first();
$input.disable();
expect($input).toHaveClass('disabled');
});
}); });
return describe('enable', function() {
beforeEach(function() { it('adds the disabled attribute', function() {
return setFixtures('<input type="text" disabled="disabled" class="disabled" />'); var $input;
}); $input = $('input').first();
$input.disable();
it('removes the disabled attribute', function() {
var $input; expect($input).toHaveAttr('disabled', 'disabled');
$input = $('input').first(); });
$input.enable(); return it('adds the disabled class', function() {
var $input;
expect($input).not.toHaveAttr('disabled'); $input = $('input').first();
}); $input.disable();
return it('removes the disabled class', function() {
var $input; expect($input).toHaveClass('disabled');
$input = $('input').first(); });
$input.enable(); });
return describe('enable', function() {
expect($input).not.toHaveClass('disabled'); beforeEach(function() {
}); return setFixtures('<input type="text" disabled="disabled" class="disabled" />');
});
it('removes the disabled attribute', function() {
var $input;
$input = $('input').first();
$input.enable();
expect($input).not.toHaveAttr('disabled');
});
return it('removes the disabled class', function() {
var $input;
$input = $('input').first();
$input.enable();
expect($input).not.toHaveClass('disabled');
}); });
}); });
}).call(window); });
import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs'; import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs';
(() => { describe('Linked Tabs', () => {
describe('Linked Tabs', () => { preloadFixtures('static/linked_tabs.html.raw');
preloadFixtures('static/linked_tabs.html.raw');
beforeEach(() => {
loadFixtures('static/linked_tabs.html.raw');
});
describe('when is initialized', () => {
beforeEach(() => { beforeEach(() => {
loadFixtures('static/linked_tabs.html.raw'); spyOn(window.history, 'replaceState').and.callFake(function() {});
}); });
describe('when is initialized', () => { it('should activate the tab correspondent to the given action', () => {
beforeEach(() => { // eslint-disable-next-line no-new
spyOn(window.history, 'replaceState').and.callFake(function () {}); new LinkedTabs({
action: 'tab1',
defaultAction: 'tab1',
parentEl: '.linked-tabs',
}); });
it('should activate the tab correspondent to the given action', () => { expect(document.querySelector('#tab1').classList).toContain('active');
const linkedTabs = new LinkedTabs({ // eslint-disable-line });
action: 'tab1',
defaultAction: 'tab1',
parentEl: '.linked-tabs',
});
expect(document.querySelector('#tab1').classList).toContain('active'); it('should active the default tab action when the action is show', () => {
// eslint-disable-next-line no-new
new LinkedTabs({
action: 'show',
defaultAction: 'tab1',
parentEl: '.linked-tabs',
}); });
it('should active the default tab action when the action is show', () => { expect(document.querySelector('#tab1').classList).toContain('active');
const linkedTabs = new LinkedTabs({ // eslint-disable-line
action: 'show',
defaultAction: 'tab1',
parentEl: '.linked-tabs',
});
expect(document.querySelector('#tab1').classList).toContain('active');
});
}); });
});
describe('on click', () => { describe('on click', () => {
it('should change the url according to the clicked tab', () => { it('should change the url according to the clicked tab', () => {
const historySpy = spyOn(window.history, 'replaceState').and.callFake(() => {}); const historySpy = spyOn(window.history, 'replaceState').and.callFake(() => {});
const linkedTabs = new LinkedTabs({ const linkedTabs = new LinkedTabs({
action: 'show', action: 'show',
defaultAction: 'tab1', defaultAction: 'tab1',
parentEl: '.linked-tabs', parentEl: '.linked-tabs',
}); });
const secondTab = document.querySelector('.linked-tabs li:nth-child(2) a'); const secondTab = document.querySelector('.linked-tabs li:nth-child(2) a');
const newState = secondTab.getAttribute('href') + linkedTabs.currentLocation.search + linkedTabs.currentLocation.hash; const newState =
secondTab.getAttribute('href') +
linkedTabs.currentLocation.search +
linkedTabs.currentLocation.hash;
secondTab.click(); secondTab.click();
if (historySpy) { if (historySpy) {
expect(historySpy).toHaveBeenCalledWith({ expect(historySpy).toHaveBeenCalledWith(
{
url: newState, url: newState,
}, document.title, newState); },
} document.title,
}); newState,
);
}
}); });
}); });
})(); });
import bp, { import bp, { breakpoints } from '~/breakpoints';
breakpoints,
} from '~/breakpoints';
describe('breakpoints', () => { describe('breakpoints', () => {
Object.keys(breakpoints).forEach((key) => { Object.keys(breakpoints).forEach(key => {
const size = breakpoints[key]; const size = breakpoints[key];
it(`returns ${key} when larger than ${size}`, () => { it(`returns ${key} when larger than ${size}`, () => {
......
...@@ -43,7 +43,7 @@ describe('AjaxFormVariableList', () => { ...@@ -43,7 +43,7 @@ describe('AjaxFormVariableList', () => {
}); });
describe('onSaveClicked', () => { describe('onSaveClicked', () => {
it('shows loading spinner while waiting for the request', (done) => { it('shows loading spinner while waiting for the request', done => {
const loadingIcon = saveButton.querySelector('.js-secret-variables-save-loading-icon'); const loadingIcon = saveButton.querySelector('.js-secret-variables-save-loading-icon');
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(() => { mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(() => {
...@@ -54,7 +54,8 @@ describe('AjaxFormVariableList', () => { ...@@ -54,7 +54,8 @@ describe('AjaxFormVariableList', () => {
expect(loadingIcon.classList.contains(HIDE_CLASS)).toEqual(true); expect(loadingIcon.classList.contains(HIDE_CLASS)).toEqual(true);
ajaxVariableList.onSaveClicked() ajaxVariableList
.onSaveClicked()
.then(() => { .then(() => {
expect(loadingIcon.classList.contains(HIDE_CLASS)).toEqual(true); expect(loadingIcon.classList.contains(HIDE_CLASS)).toEqual(true);
}) })
...@@ -62,27 +63,30 @@ describe('AjaxFormVariableList', () => { ...@@ -62,27 +63,30 @@ describe('AjaxFormVariableList', () => {
.catch(done.fail); .catch(done.fail);
}); });
it('calls `updateRowsWithPersistedVariables` with the persisted variables', (done) => { it('calls `updateRowsWithPersistedVariables` with the persisted variables', done => {
const variablesResponse = [{ id: 1, key: 'foo', value: 'bar' }]; const variablesResponse = [{ id: 1, key: 'foo', value: 'bar' }];
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200, { mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200, {
variables: variablesResponse, variables: variablesResponse,
}); });
ajaxVariableList.onSaveClicked() ajaxVariableList
.onSaveClicked()
.then(() => { .then(() => {
expect(ajaxVariableList.updateRowsWithPersistedVariables) expect(ajaxVariableList.updateRowsWithPersistedVariables).toHaveBeenCalledWith(
.toHaveBeenCalledWith(variablesResponse); variablesResponse,
);
}) })
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
}); });
it('hides any previous error box', (done) => { it('hides any previous error box', done => {
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200); mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200);
expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true); expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true);
ajaxVariableList.onSaveClicked() ajaxVariableList
.onSaveClicked()
.then(() => { .then(() => {
expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true); expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true);
}) })
...@@ -90,14 +94,15 @@ describe('AjaxFormVariableList', () => { ...@@ -90,14 +94,15 @@ describe('AjaxFormVariableList', () => {
.catch(done.fail); .catch(done.fail);
}); });
it('disables remove buttons while waiting for the request', (done) => { it('disables remove buttons while waiting for the request', done => {
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(() => { mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(() => {
expect(ajaxVariableList.variableList.toggleEnableRow).toHaveBeenCalledWith(false); expect(ajaxVariableList.variableList.toggleEnableRow).toHaveBeenCalledWith(false);
return [200, {}]; return [200, {}];
}); });
ajaxVariableList.onSaveClicked() ajaxVariableList
.onSaveClicked()
.then(() => { .then(() => {
expect(ajaxVariableList.variableList.toggleEnableRow).toHaveBeenCalledWith(true); expect(ajaxVariableList.variableList.toggleEnableRow).toHaveBeenCalledWith(true);
}) })
...@@ -105,7 +110,7 @@ describe('AjaxFormVariableList', () => { ...@@ -105,7 +110,7 @@ describe('AjaxFormVariableList', () => {
.catch(done.fail); .catch(done.fail);
}); });
it('hides secret values', (done) => { it('hides secret values', done => {
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200, {}); mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200, {});
const row = container.querySelector('.js-row:first-child'); const row = container.querySelector('.js-row:first-child');
...@@ -118,7 +123,8 @@ describe('AjaxFormVariableList', () => { ...@@ -118,7 +123,8 @@ describe('AjaxFormVariableList', () => {
expect(valuePlaceholder.classList.contains(HIDE_CLASS)).toBe(true); expect(valuePlaceholder.classList.contains(HIDE_CLASS)).toBe(true);
expect(valueInput.classList.contains(HIDE_CLASS)).toBe(false); expect(valueInput.classList.contains(HIDE_CLASS)).toBe(false);
ajaxVariableList.onSaveClicked() ajaxVariableList
.onSaveClicked()
.then(() => { .then(() => {
expect(valuePlaceholder.classList.contains(HIDE_CLASS)).toBe(false); expect(valuePlaceholder.classList.contains(HIDE_CLASS)).toBe(false);
expect(valueInput.classList.contains(HIDE_CLASS)).toBe(true); expect(valueInput.classList.contains(HIDE_CLASS)).toBe(true);
...@@ -127,29 +133,31 @@ describe('AjaxFormVariableList', () => { ...@@ -127,29 +133,31 @@ describe('AjaxFormVariableList', () => {
.catch(done.fail); .catch(done.fail);
}); });
it('shows error box with validation errors', (done) => { it('shows error box with validation errors', done => {
const validationError = 'some validation error'; const validationError = 'some validation error';
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(400, [ mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(400, [validationError]);
validationError,
]);
expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true); expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true);
ajaxVariableList.onSaveClicked() ajaxVariableList
.onSaveClicked()
.then(() => { .then(() => {
expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(false); expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(false);
expect(errorBox.textContent.trim().replace(/\n+\s+/m, ' ')).toEqual(`Validation failed ${validationError}`); expect(errorBox.textContent.trim().replace(/\n+\s+/m, ' ')).toEqual(
`Validation failed ${validationError}`,
);
}) })
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
}); });
it('shows flash message when request fails', (done) => { it('shows flash message when request fails', done => {
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(500); mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(500);
expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true); expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true);
ajaxVariableList.onSaveClicked() ajaxVariableList
.onSaveClicked()
.then(() => { .then(() => {
expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true); expect(errorBox.classList.contains(HIDE_CLASS)).toEqual(true);
}) })
...@@ -200,11 +208,13 @@ describe('AjaxFormVariableList', () => { ...@@ -200,11 +208,13 @@ describe('AjaxFormVariableList', () => {
expect(idInput.value).toEqual(''); expect(idInput.value).toEqual('');
ajaxVariableList.updateRowsWithPersistedVariables([{ ajaxVariableList.updateRowsWithPersistedVariables([
id: 3, {
key: 'foo', id: 3,
value: 'bar', key: 'foo',
}]); value: 'bar',
},
]);
expect(idInput.value).toEqual('3'); expect(idInput.value).toEqual('3');
expect(row.dataset.isPersisted).toEqual('true'); expect(row.dataset.isPersisted).toEqual('true');
......
...@@ -33,7 +33,8 @@ describe('VariableList', () => { ...@@ -33,7 +33,8 @@ describe('VariableList', () => {
it('should add another row when editing the last rows key input', () => { it('should add another row when editing the last rows key input', () => {
const $row = $wrapper.find('.js-row'); const $row = $wrapper.find('.js-row');
$row.find('.js-ci-variable-input-key') $row
.find('.js-ci-variable-input-key')
.val('foo') .val('foo')
.trigger('input'); .trigger('input');
...@@ -47,7 +48,8 @@ describe('VariableList', () => { ...@@ -47,7 +48,8 @@ describe('VariableList', () => {
it('should add another row when editing the last rows value textarea', () => { it('should add another row when editing the last rows value textarea', () => {
const $row = $wrapper.find('.js-row'); const $row = $wrapper.find('.js-row');
$row.find('.js-ci-variable-input-value') $row
.find('.js-ci-variable-input-value')
.val('foo') .val('foo')
.trigger('input'); .trigger('input');
...@@ -61,13 +63,15 @@ describe('VariableList', () => { ...@@ -61,13 +63,15 @@ describe('VariableList', () => {
it('should remove empty row after blurring', () => { it('should remove empty row after blurring', () => {
const $row = $wrapper.find('.js-row'); const $row = $wrapper.find('.js-row');
$row.find('.js-ci-variable-input-key') $row
.find('.js-ci-variable-input-key')
.val('foo') .val('foo')
.trigger('input'); .trigger('input');
expect($wrapper.find('.js-row').length).toBe(2); expect($wrapper.find('.js-row').length).toBe(2);
$row.find('.js-ci-variable-input-key') $row
.find('.js-ci-variable-input-key')
.val('') .val('')
.trigger('input') .trigger('input')
.trigger('blur'); .trigger('blur');
...@@ -121,7 +125,7 @@ describe('VariableList', () => { ...@@ -121,7 +125,7 @@ describe('VariableList', () => {
variableList.init(); variableList.init();
}); });
it('should add another row when editing the last rows protected checkbox', (done) => { it('should add another row when editing the last rows protected checkbox', done => {
const $row = $wrapper.find('.js-row:last-child'); const $row = $wrapper.find('.js-row:last-child');
$row.find('.ci-variable-protected-item .js-project-feature-toggle').click(); $row.find('.ci-variable-protected-item .js-project-feature-toggle').click();
...@@ -130,7 +134,9 @@ describe('VariableList', () => { ...@@ -130,7 +134,9 @@ describe('VariableList', () => {
expect($wrapper.find('.js-row').length).toBe(2); expect($wrapper.find('.js-row').length).toBe(2);
// Check for the correct default in the new row // Check for the correct default in the new row
const $protectedInput = $wrapper.find('.js-row:last-child').find('.js-ci-variable-input-protected'); const $protectedInput = $wrapper
.find('.js-row:last-child')
.find('.js-ci-variable-input-protected');
expect($protectedInput.val()).toBe('false'); expect($protectedInput.val()).toBe('false');
}) })
...@@ -205,7 +211,8 @@ describe('VariableList', () => { ...@@ -205,7 +211,8 @@ describe('VariableList', () => {
const $inputValue = $row.find('.js-ci-variable-input-value'); const $inputValue = $row.find('.js-ci-variable-input-value');
const $placeholder = $row.find('.js-secret-value-placeholder'); const $placeholder = $row.find('.js-secret-value-placeholder');
$row.find('.js-ci-variable-input-value') $row
.find('.js-ci-variable-input-value')
.val('foo') .val('foo')
.trigger('input'); .trigger('input');
......
...@@ -20,8 +20,13 @@ describe('NativeFormVariableList', () => { ...@@ -20,8 +20,13 @@ describe('NativeFormVariableList', () => {
it('should clear out the `name` attribute on the inputs for the last empty row on form submission (avoid BE validation)', () => { it('should clear out the `name` attribute on the inputs for the last empty row on form submission (avoid BE validation)', () => {
const $row = $wrapper.find('.js-row'); const $row = $wrapper.find('.js-row');
expect($row.find('.js-ci-variable-input-key').attr('name')).toBe('schedule[variables_attributes][][key]'); expect($row.find('.js-ci-variable-input-key').attr('name')).toBe(
expect($row.find('.js-ci-variable-input-value').attr('name')).toBe('schedule[variables_attributes][][secret_value]'); 'schedule[variables_attributes][][key]',
);
expect($row.find('.js-ci-variable-input-value').attr('name')).toBe(
'schedule[variables_attributes][][secret_value]',
);
$wrapper.closest('form').trigger('trigger-submit'); $wrapper.closest('form').trigger('trigger-submit');
......
...@@ -10,7 +10,7 @@ describe('CloseReopenReportToggle', () => { ...@@ -10,7 +10,7 @@ describe('CloseReopenReportToggle', () => {
const button = {}; const button = {};
let commentTypeToggle; let commentTypeToggle;
beforeEach(function () { beforeEach(function() {
commentTypeToggle = new CloseReopenReportToggle({ commentTypeToggle = new CloseReopenReportToggle({
dropdownTrigger, dropdownTrigger,
dropdownList, dropdownList,
...@@ -18,15 +18,15 @@ describe('CloseReopenReportToggle', () => { ...@@ -18,15 +18,15 @@ describe('CloseReopenReportToggle', () => {
}); });
}); });
it('sets .dropdownTrigger', function () { it('sets .dropdownTrigger', function() {
expect(commentTypeToggle.dropdownTrigger).toBe(dropdownTrigger); expect(commentTypeToggle.dropdownTrigger).toBe(dropdownTrigger);
}); });
it('sets .dropdownList', function () { it('sets .dropdownList', function() {
expect(commentTypeToggle.dropdownList).toBe(dropdownList); expect(commentTypeToggle.dropdownList).toBe(dropdownList);
}); });
it('sets .button', function () { it('sets .button', function() {
expect(commentTypeToggle.button).toBe(button); expect(commentTypeToggle.button).toBe(button);
}); });
}); });
......
...@@ -6,67 +6,77 @@ const CLUSTERS_MOCK_DATA = { ...@@ -6,67 +6,77 @@ const CLUSTERS_MOCK_DATA = {
data: { data: {
status: 'errored', status: 'errored',
status_reason: 'Failed to request to CloudPlatform.', status_reason: 'Failed to request to CloudPlatform.',
applications: [{ applications: [
name: 'helm', {
status: APPLICATION_STATUS.INSTALLABLE, name: 'helm',
status_reason: null, status: APPLICATION_STATUS.INSTALLABLE,
}, { status_reason: null,
name: 'ingress', },
status: APPLICATION_STATUS.ERROR, {
status_reason: 'Cannot connect', name: 'ingress',
external_ip: null, status: APPLICATION_STATUS.ERROR,
}, { status_reason: 'Cannot connect',
name: 'runner', external_ip: null,
status: APPLICATION_STATUS.INSTALLING, },
status_reason: null, {
}, name: 'runner',
{ status: APPLICATION_STATUS.INSTALLING,
name: 'prometheus', status_reason: null,
status: APPLICATION_STATUS.ERROR, },
status_reason: 'Cannot connect', {
}, { name: 'prometheus',
name: 'jupyter', status: APPLICATION_STATUS.ERROR,
status: APPLICATION_STATUS.INSTALLING, status_reason: 'Cannot connect',
status_reason: 'Cannot connect', },
}], {
name: 'jupyter',
status: APPLICATION_STATUS.INSTALLING,
status_reason: 'Cannot connect',
},
],
}, },
}, },
'/gitlab-org/gitlab-shell/clusters/2/status.json': { '/gitlab-org/gitlab-shell/clusters/2/status.json': {
data: { data: {
status: 'errored', status: 'errored',
status_reason: 'Failed to request to CloudPlatform.', status_reason: 'Failed to request to CloudPlatform.',
applications: [{ applications: [
name: 'helm', {
status: APPLICATION_STATUS.INSTALLED, name: 'helm',
status_reason: null, status: APPLICATION_STATUS.INSTALLED,
}, { status_reason: null,
name: 'ingress', },
status: APPLICATION_STATUS.INSTALLED, {
status_reason: 'Cannot connect', name: 'ingress',
external_ip: '1.1.1.1', status: APPLICATION_STATUS.INSTALLED,
}, { status_reason: 'Cannot connect',
name: 'runner', external_ip: '1.1.1.1',
status: APPLICATION_STATUS.INSTALLING, },
status_reason: null, {
}, name: 'runner',
{ status: APPLICATION_STATUS.INSTALLING,
name: 'prometheus', status_reason: null,
status: APPLICATION_STATUS.ERROR, },
status_reason: 'Cannot connect', {
}, { name: 'prometheus',
name: 'jupyter', status: APPLICATION_STATUS.ERROR,
status: APPLICATION_STATUS.INSTALLABLE, status_reason: 'Cannot connect',
status_reason: 'Cannot connect', },
}], {
name: 'jupyter',
status: APPLICATION_STATUS.INSTALLABLE,
status_reason: 'Cannot connect',
},
],
}, },
}, },
}, },
POST: { POST: {
'/gitlab-org/gitlab-shell/clusters/1/applications/helm': { }, '/gitlab-org/gitlab-shell/clusters/1/applications/helm': {},
'/gitlab-org/gitlab-shell/clusters/1/applications/ingress': { }, '/gitlab-org/gitlab-shell/clusters/1/applications/ingress': {},
'/gitlab-org/gitlab-shell/clusters/1/applications/runner': { }, '/gitlab-org/gitlab-shell/clusters/1/applications/runner': {},
'/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': { }, '/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': {},
'/gitlab-org/gitlab-shell/clusters/1/applications/jupyter': { }, '/gitlab-org/gitlab-shell/clusters/1/applications/jupyter': {},
}, },
}; };
...@@ -81,7 +91,4 @@ const DEFAULT_APPLICATION_STATE = { ...@@ -81,7 +91,4 @@ const DEFAULT_APPLICATION_STATE = {
requestReason: null, requestReason: null,
}; };
export { export { CLUSTERS_MOCK_DATA, DEFAULT_APPLICATION_STATE };
CLUSTERS_MOCK_DATA,
DEFAULT_APPLICATION_STATE,
};
...@@ -53,7 +53,8 @@ describe('Clusters Store', () => { ...@@ -53,7 +53,8 @@ describe('Clusters Store', () => {
describe('updateStateFromServer', () => { describe('updateStateFromServer', () => {
it('should store new polling data from server', () => { it('should store new polling data from server', () => {
const mockResponseData = CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/1/status.json'].data; const mockResponseData =
CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/1/status.json'].data;
store.updateStateFromServer(mockResponseData); store.updateStateFromServer(mockResponseData);
expect(store.state).toEqual({ expect(store.state).toEqual({
...@@ -104,13 +105,14 @@ describe('Clusters Store', () => { ...@@ -104,13 +105,14 @@ describe('Clusters Store', () => {
}); });
it('sets default hostname for jupyter when ingress has a ip address', () => { it('sets default hostname for jupyter when ingress has a ip address', () => {
const mockResponseData = CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/2/status.json'].data; const mockResponseData =
CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/2/status.json'].data;
store.updateStateFromServer(mockResponseData); store.updateStateFromServer(mockResponseData);
expect( expect(store.state.applications.jupyter.hostname).toEqual(
store.state.applications.jupyter.hostname, `jupyter.${store.state.applications.ingress.externalIp}.nip.io`,
).toEqual(`jupyter.${store.state.applications.ingress.externalIp}.nip.io`); );
}); });
}); });
}); });
...@@ -18,10 +18,8 @@ describe('Issuable right sidebar collapsed todo toggle', () => { ...@@ -18,10 +18,8 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
new Sidebar(); new Sidebar();
loadFixtures(fixtureName); loadFixtures(fixtureName);
document.querySelector('.js-right-sidebar') document.querySelector('.js-right-sidebar').classList.toggle('right-sidebar-expanded');
.classList.toggle('right-sidebar-expanded'); document.querySelector('.js-right-sidebar').classList.toggle('right-sidebar-collapsed');
document.querySelector('.js-right-sidebar')
.classList.toggle('right-sidebar-collapsed');
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
...@@ -44,9 +42,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => { ...@@ -44,9 +42,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
}); });
it('shows add todo button', () => { it('shows add todo button', () => {
expect( expect(document.querySelector('.js-issuable-todo.sidebar-collapsed-icon')).not.toBeNull();
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon'),
).not.toBeNull();
expect( expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .fa-plus-square'), document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .fa-plus-square'),
...@@ -63,7 +59,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => { ...@@ -63,7 +59,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
).toBe('Add todo'); ).toBe('Add todo');
}); });
it('toggle todo state', (done) => { it('toggle todo state', done => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
setTimeout(() => { setTimeout(() => {
...@@ -79,7 +75,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => { ...@@ -79,7 +75,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
}); });
}); });
it('toggle todo state of expanded todo toggle', (done) => { it('toggle todo state of expanded todo toggle', done => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
setTimeout(() => { setTimeout(() => {
...@@ -91,19 +87,21 @@ describe('Issuable right sidebar collapsed todo toggle', () => { ...@@ -91,19 +87,21 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
}); });
}); });
it('toggles todo button tooltip', (done) => { it('toggles todo button tooltip', done => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
setTimeout(() => { setTimeout(() => {
expect( expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('data-original-title'), document
.querySelector('.js-issuable-todo.sidebar-collapsed-icon')
.getAttribute('data-original-title'),
).toBe('Mark todo as done'); ).toBe('Mark todo as done');
done(); done();
}); });
}); });
it('marks todo as done', (done) => { it('marks todo as done', done => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
timeoutPromise() timeoutPromise()
...@@ -128,25 +126,29 @@ describe('Issuable right sidebar collapsed todo toggle', () => { ...@@ -128,25 +126,29 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
.catch(done.fail); .catch(done.fail);
}); });
it('updates aria-label to mark todo as done', (done) => { it('updates aria-label to mark todo as done', done => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
setTimeout(() => { setTimeout(() => {
expect( expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('aria-label'), document
.querySelector('.js-issuable-todo.sidebar-collapsed-icon')
.getAttribute('aria-label'),
).toBe('Mark todo as done'); ).toBe('Mark todo as done');
done(); done();
}); });
}); });
it('updates aria-label to add todo', (done) => { it('updates aria-label to add todo', done => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
timeoutPromise() timeoutPromise()
.then(() => { .then(() => {
expect( expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('aria-label'), document
.querySelector('.js-issuable-todo.sidebar-collapsed-icon')
.getAttribute('aria-label'),
).toBe('Mark todo as done'); ).toBe('Mark todo as done');
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
...@@ -154,7 +156,9 @@ describe('Issuable right sidebar collapsed todo toggle', () => { ...@@ -154,7 +156,9 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
.then(timeoutPromise) .then(timeoutPromise)
.then(() => { .then(() => {
expect( expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('aria-label'), document
.querySelector('.js-issuable-todo.sidebar-collapsed-icon')
.getAttribute('aria-label'),
).toBe('Add todo'); ).toBe('Add todo');
}) })
.then(done) .then(done)
......
import CommentTypeToggle from '~/comment_type_toggle'; import CommentTypeToggle from '~/comment_type_toggle';
import InputSetter from '~/droplab/plugins/input_setter'; import InputSetter from '~/droplab/plugins/input_setter';
describe('CommentTypeToggle', function () { describe('CommentTypeToggle', function() {
describe('class constructor', function () { describe('class constructor', function() {
beforeEach(function () { beforeEach(function() {
this.dropdownTrigger = {}; this.dropdownTrigger = {};
this.dropdownList = {}; this.dropdownList = {};
this.noteTypeInput = {}; this.noteTypeInput = {};
...@@ -19,33 +19,33 @@ describe('CommentTypeToggle', function () { ...@@ -19,33 +19,33 @@ describe('CommentTypeToggle', function () {
}); });
}); });
it('should set .dropdownTrigger', function () { it('should set .dropdownTrigger', function() {
expect(this.commentTypeToggle.dropdownTrigger).toBe(this.dropdownTrigger); expect(this.commentTypeToggle.dropdownTrigger).toBe(this.dropdownTrigger);
}); });
it('should set .dropdownList', function () { it('should set .dropdownList', function() {
expect(this.commentTypeToggle.dropdownList).toBe(this.dropdownList); expect(this.commentTypeToggle.dropdownList).toBe(this.dropdownList);
}); });
it('should set .noteTypeInput', function () { it('should set .noteTypeInput', function() {
expect(this.commentTypeToggle.noteTypeInput).toBe(this.noteTypeInput); expect(this.commentTypeToggle.noteTypeInput).toBe(this.noteTypeInput);
}); });
it('should set .submitButton', function () { it('should set .submitButton', function() {
expect(this.commentTypeToggle.submitButton).toBe(this.submitButton); expect(this.commentTypeToggle.submitButton).toBe(this.submitButton);
}); });
it('should set .closeButton', function () { it('should set .closeButton', function() {
expect(this.commentTypeToggle.closeButton).toBe(this.closeButton); expect(this.commentTypeToggle.closeButton).toBe(this.closeButton);
}); });
it('should set .reopenButton', function () { it('should set .reopenButton', function() {
expect(this.commentTypeToggle.reopenButton).toBe(this.reopenButton); expect(this.commentTypeToggle.reopenButton).toBe(this.reopenButton);
}); });
}); });
describe('initDroplab', function () { describe('initDroplab', function() {
beforeEach(function () { beforeEach(function() {
this.commentTypeToggle = { this.commentTypeToggle = {
dropdownTrigger: {}, dropdownTrigger: {},
dropdownList: {}, dropdownList: {},
...@@ -58,25 +58,27 @@ describe('CommentTypeToggle', function () { ...@@ -58,25 +58,27 @@ describe('CommentTypeToggle', function () {
this.droplab = jasmine.createSpyObj('droplab', ['init']); this.droplab = jasmine.createSpyObj('droplab', ['init']);
this.droplabConstructor = spyOnDependency(CommentTypeToggle, 'DropLab').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(this.droplabConstructor).toHaveBeenCalled(); expect(this.droplabConstructor).toHaveBeenCalled();
}); });
it('should set .droplab', function () { it('should set .droplab', function() {
expect(this.commentTypeToggle.droplab).toBe(this.droplab); expect(this.commentTypeToggle.droplab).toBe(this.droplab);
}); });
it('should call .setConfig', function () { it('should call .setConfig', function() {
expect(this.commentTypeToggle.setConfig).toHaveBeenCalled(); expect(this.commentTypeToggle.setConfig).toHaveBeenCalled();
}); });
it('should call DropLab.prototype.init', function () { it('should call DropLab.prototype.init', function() {
expect(this.droplab.init).toHaveBeenCalledWith( expect(this.droplab.init).toHaveBeenCalledWith(
this.commentTypeToggle.dropdownTrigger, this.commentTypeToggle.dropdownTrigger,
this.commentTypeToggle.dropdownList, this.commentTypeToggle.dropdownList,
...@@ -86,9 +88,9 @@ describe('CommentTypeToggle', function () { ...@@ -86,9 +88,9 @@ describe('CommentTypeToggle', function () {
}); });
}); });
describe('setConfig', function () { describe('setConfig', function() {
describe('if no .closeButton is provided', function () { describe('if no .closeButton is provided', function() {
beforeEach(function () { beforeEach(function() {
this.commentTypeToggle = { this.commentTypeToggle = {
dropdownTrigger: {}, dropdownTrigger: {},
dropdownList: {}, dropdownList: {},
...@@ -100,28 +102,33 @@ describe('CommentTypeToggle', function () { ...@@ -100,28 +102,33 @@ describe('CommentTypeToggle', function () {
this.setConfig = CommentTypeToggle.prototype.setConfig.call(this.commentTypeToggle); this.setConfig = CommentTypeToggle.prototype.setConfig.call(this.commentTypeToggle);
}); });
it('should not add .closeButton related InputSetter config', function () { it('should not add .closeButton related InputSetter config', function() {
expect(this.setConfig).toEqual({ expect(this.setConfig).toEqual({
InputSetter: [{ InputSetter: [
input: this.commentTypeToggle.noteTypeInput, {
valueAttribute: 'data-value', input: this.commentTypeToggle.noteTypeInput,
}, { valueAttribute: 'data-value',
input: this.commentTypeToggle.submitButton, },
valueAttribute: 'data-submit-text', {
}, { input: this.commentTypeToggle.submitButton,
input: this.commentTypeToggle.reopenButton, valueAttribute: 'data-submit-text',
valueAttribute: 'data-reopen-text', },
}, { {
input: this.commentTypeToggle.reopenButton, input: this.commentTypeToggle.reopenButton,
valueAttribute: 'data-reopen-text', valueAttribute: 'data-reopen-text',
inputAttribute: 'data-alternative-text', },
}], {
input: this.commentTypeToggle.reopenButton,
valueAttribute: 'data-reopen-text',
inputAttribute: 'data-alternative-text',
},
],
}); });
}); });
}); });
describe('if no .reopenButton is provided', function () { describe('if no .reopenButton is provided', function() {
beforeEach(function () { beforeEach(function() {
this.commentTypeToggle = { this.commentTypeToggle = {
dropdownTrigger: {}, dropdownTrigger: {},
dropdownList: {}, dropdownList: {},
...@@ -133,22 +140,27 @@ describe('CommentTypeToggle', function () { ...@@ -133,22 +140,27 @@ describe('CommentTypeToggle', function () {
this.setConfig = CommentTypeToggle.prototype.setConfig.call(this.commentTypeToggle); this.setConfig = CommentTypeToggle.prototype.setConfig.call(this.commentTypeToggle);
}); });
it('should not add .reopenButton related InputSetter config', function () { it('should not add .reopenButton related InputSetter config', function() {
expect(this.setConfig).toEqual({ expect(this.setConfig).toEqual({
InputSetter: [{ InputSetter: [
input: this.commentTypeToggle.noteTypeInput, {
valueAttribute: 'data-value', input: this.commentTypeToggle.noteTypeInput,
}, { valueAttribute: 'data-value',
input: this.commentTypeToggle.submitButton, },
valueAttribute: 'data-submit-text', {
}, { input: this.commentTypeToggle.submitButton,
input: this.commentTypeToggle.closeButton, valueAttribute: 'data-submit-text',
valueAttribute: 'data-close-text', },
}, { {
input: this.commentTypeToggle.closeButton, input: this.commentTypeToggle.closeButton,
valueAttribute: 'data-close-text', valueAttribute: 'data-close-text',
inputAttribute: 'data-alternative-text', },
}], {
input: this.commentTypeToggle.closeButton,
valueAttribute: 'data-close-text',
inputAttribute: 'data-alternative-text',
},
],
}); });
}); });
}); });
......
...@@ -26,15 +26,18 @@ describe('Commit pipeline status component', () => { ...@@ -26,15 +26,18 @@ describe('Commit pipeline status component', () => {
beforeEach(() => { beforeEach(() => {
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
mock.onGet('/dummy/endpoint').reply(() => { mock.onGet('/dummy/endpoint').reply(() => {
const res = Promise.resolve([200, { const res = Promise.resolve([
pipelines: [ 200,
{ {
details: { pipelines: [
status: mockCiStatus, {
details: {
status: mockCiStatus,
},
}, },
}, ],
], },
}]); ]);
return res; return res;
}); });
vm = mountComponent(Component, { vm = mountComponent(Component, {
...@@ -48,7 +51,7 @@ describe('Commit pipeline status component', () => { ...@@ -48,7 +51,7 @@ describe('Commit pipeline status component', () => {
mock.restore(); mock.restore();
}); });
it('shows the loading icon when polling is starting', (done) => { it('shows the loading icon when polling is starting', done => {
expect(vm.$el.querySelector('.loading-container')).not.toBe(null); expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
setTimeout(() => { setTimeout(() => {
expect(vm.$el.querySelector('.loading-container')).toBe(null); expect(vm.$el.querySelector('.loading-container')).toBe(null);
...@@ -56,17 +59,19 @@ describe('Commit pipeline status component', () => { ...@@ -56,17 +59,19 @@ describe('Commit pipeline status component', () => {
}); });
}); });
it('contains a ciStatus when the polling is succesful ', (done) => { it('contains a ciStatus when the polling is succesful ', done => {
setTimeout(() => { setTimeout(() => {
expect(vm.ciStatus).toEqual(mockCiStatus); expect(vm.ciStatus).toEqual(mockCiStatus);
done(); done();
}); });
}); });
it('contains a ci-status icon when polling is succesful', (done) => { it('contains a ci-status icon when polling is succesful', done => {
setTimeout(() => { setTimeout(() => {
expect(vm.$el.querySelector('.ci-status-icon')).not.toBe(null); expect(vm.$el.querySelector('.ci-status-icon')).not.toBe(null);
expect(vm.$el.querySelector('.ci-status-icon').classList).toContain(`ci-status-icon-${mockCiStatus.group}`); expect(vm.$el.querySelector('.ci-status-icon').classList).toContain(
`ci-status-icon-${mockCiStatus.group}`,
);
done(); done();
}); });
}); });
...@@ -89,7 +94,7 @@ describe('Commit pipeline status component', () => { ...@@ -89,7 +94,7 @@ describe('Commit pipeline status component', () => {
mock.restore(); mock.restore();
}); });
it('calls an errorCallback', (done) => { it('calls an errorCallback', done => {
spyOn(vm, 'errorCallback').and.callThrough(); spyOn(vm, 'errorCallback').and.callThrough();
vm.$mount(); vm.$mount();
setTimeout(() => { setTimeout(() => {
......
...@@ -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', function () { 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;
...@@ -29,7 +29,7 @@ describe('Pipelines table in Commits and Merge requests', function () { ...@@ -29,7 +29,7 @@ describe('Pipelines table in Commits and Merge requests', function () {
describe('successful request', () => { describe('successful request', () => {
describe('without pipelines', () => { describe('without pipelines', () => {
beforeEach(function () { beforeEach(function() {
mock.onGet('endpoint.json').reply(200, []); mock.onGet('endpoint.json').reply(200, []);
vm = mountComponent(PipelinesTable, { vm = mountComponent(PipelinesTable, {
...@@ -41,7 +41,7 @@ describe('Pipelines table in Commits and Merge requests', function () { ...@@ -41,7 +41,7 @@ describe('Pipelines table in Commits and Merge requests', function () {
}); });
}); });
it('should render the empty state', function (done) { it('should render the empty state', function(done) {
setTimeout(() => { setTimeout(() => {
expect(vm.$el.querySelector('.empty-state')).toBeDefined(); expect(vm.$el.querySelector('.empty-state')).toBeDefined();
expect(vm.$el.querySelector('.realtime-loading')).toBe(null); expect(vm.$el.querySelector('.realtime-loading')).toBe(null);
...@@ -63,7 +63,7 @@ describe('Pipelines table in Commits and Merge requests', function () { ...@@ -63,7 +63,7 @@ describe('Pipelines table in Commits and Merge requests', function () {
}); });
}); });
it('should render a table with the received pipelines', (done) => { it('should render a table with the received pipelines', done => {
setTimeout(() => { setTimeout(() => {
expect(vm.$el.querySelectorAll('.ci-table .commit').length).toEqual(1); expect(vm.$el.querySelectorAll('.ci-table .commit').length).toEqual(1);
expect(vm.$el.querySelector('.realtime-loading')).toBe(null); expect(vm.$el.querySelector('.realtime-loading')).toBe(null);
...@@ -79,11 +79,11 @@ describe('Pipelines table in Commits and Merge requests', function () { ...@@ -79,11 +79,11 @@ describe('Pipelines table in Commits and Merge requests', function () {
mock.onGet('endpoint.json').reply(200, [pipeline]); mock.onGet('endpoint.json').reply(200, [pipeline]);
}); });
it('should receive update-pipelines-count event', (done) => { it('should receive update-pipelines-count event', done => {
const element = document.createElement('div'); const element = document.createElement('div');
document.body.appendChild(element); document.body.appendChild(element);
element.addEventListener('update-pipelines-count', (event) => { element.addEventListener('update-pipelines-count', event => {
expect(event.detail.pipelines).toEqual([pipeline]); expect(event.detail.pipelines).toEqual([pipeline]);
done(); done();
}); });
...@@ -114,7 +114,7 @@ describe('Pipelines table in Commits and Merge requests', function () { ...@@ -114,7 +114,7 @@ describe('Pipelines table in Commits and Merge requests', function () {
}); });
}); });
it('should render error state', function (done) { it('should render error state', function(done) {
setTimeout(() => { setTimeout(() => {
expect(vm.$el.querySelector('.js-pipelines-error-state')).toBeDefined(); expect(vm.$el.querySelector('.js-pipelines-error-state')).toBeDefined();
expect(vm.$el.querySelector('.realtime-loading')).toBe(null); expect(vm.$el.querySelector('.realtime-loading')).toBe(null);
......
...@@ -3,7 +3,10 @@ import * as CommitMergeRequests from '~/commit_merge_requests'; ...@@ -3,7 +3,10 @@ import * as CommitMergeRequests from '~/commit_merge_requests';
describe('CommitMergeRequests', () => { describe('CommitMergeRequests', () => {
describe('createContent', () => { describe('createContent', () => {
it('should return created content', () => { it('should return created content', () => {
const content1 = CommitMergeRequests.createContent([{ iid: 1, path: '/path1', title: 'foo' }, { iid: 2, path: '/path2', title: 'baz' }])[0]; const content1 = CommitMergeRequests.createContent([
{ iid: 1, path: '/path1', title: 'foo' },
{ iid: 2, path: '/path2', title: 'baz' },
])[0];
expect(content1.tagName).toEqual('SPAN'); expect(content1.tagName).toEqual('SPAN');
expect(content1.childElementCount).toEqual(4); expect(content1.childElementCount).toEqual(4);
......
import $ from 'jquery'; import $ from 'jquery';
import CreateItemDropdown from '~/create_item_dropdown'; import CreateItemDropdown from '~/create_item_dropdown';
const DROPDOWN_ITEM_DATA = [{ const DROPDOWN_ITEM_DATA = [
title: 'one', {
id: 'one', title: 'one',
text: 'one', id: 'one',
}, { text: 'one',
title: 'two', },
id: 'two', {
text: 'two', title: 'two',
}, { id: 'two',
title: 'three', text: 'two',
id: 'three', },
text: 'three', {
}]; title: 'three',
id: 'three',
text: 'three',
},
];
describe('CreateItemDropdown', () => { describe('CreateItemDropdown', () => {
preloadFixtures('static/create_item_dropdown.html.raw'); preloadFixtures('static/create_item_dropdown.html.raw');
...@@ -23,7 +27,8 @@ describe('CreateItemDropdown', () => { ...@@ -23,7 +27,8 @@ describe('CreateItemDropdown', () => {
function createItemAndClearInput(text) { function createItemAndClearInput(text) {
// Filter for the new item // Filter for the new item
$wrapperEl.find('.dropdown-input-field') $wrapperEl
.find('.dropdown-input-field')
.val(text) .val(text)
.trigger('input'); .trigger('input');
...@@ -32,7 +37,8 @@ describe('CreateItemDropdown', () => { ...@@ -32,7 +37,8 @@ describe('CreateItemDropdown', () => {
$createButton.click(); $createButton.click();
// Clear out the filter // Clear out the filter
$wrapperEl.find('.dropdown-input-field') $wrapperEl
.find('.dropdown-input-field')
.val('') .val('')
.trigger('input'); .trigger('input');
} }
...@@ -85,7 +91,8 @@ describe('CreateItemDropdown', () => { ...@@ -85,7 +91,8 @@ describe('CreateItemDropdown', () => {
$('.js-dropdown-menu-toggle').click(); $('.js-dropdown-menu-toggle').click();
// Filter for the new item // Filter for the new item
$wrapperEl.find('.dropdown-input-field') $wrapperEl
.find('.dropdown-input-field')
.val(NEW_ITEM_TEXT) .val(NEW_ITEM_TEXT)
.trigger('input'); .trigger('input');
}); });
...@@ -140,9 +147,7 @@ describe('CreateItemDropdown', () => { ...@@ -140,9 +147,7 @@ describe('CreateItemDropdown', () => {
$('.js-dropdown-menu-toggle').click(); $('.js-dropdown-menu-toggle').click();
// Filter for an item // Filter for an item
filterInput filterInput.val('one').trigger('input');
.val('one')
.trigger('input');
const $itemElsAfterFilter = $wrapperEl.find('.js-dropdown-content a'); const $itemElsAfterFilter = $wrapperEl.find('.js-dropdown-content a');
......
...@@ -17,21 +17,20 @@ describe('Cycle analytics banner', () => { ...@@ -17,21 +17,20 @@ describe('Cycle analytics banner', () => {
}); });
it('should render cycle analytics information', () => { it('should render cycle analytics information', () => {
expect( expect(vm.$el.querySelector('h4').textContent.trim()).toEqual('Introducing Cycle Analytics');
vm.$el.querySelector('h4').textContent.trim(),
).toEqual('Introducing Cycle Analytics');
expect( expect(
vm.$el.querySelector('p').textContent.trim().replace(/[\r\n]+/g, ' '), vm.$el
).toContain('Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.'); .querySelector('p')
.textContent.trim()
.replace(/[\r\n]+/g, ' '),
).toContain(
'Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.',
);
expect( expect(vm.$el.querySelector('a').textContent.trim()).toEqual('Read more');
vm.$el.querySelector('a').textContent.trim(),
).toEqual('Read more');
expect( expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('path');
vm.$el.querySelector('a').getAttribute('href'),
).toEqual('path');
}); });
it('should emit an event when close button is clicked', () => { it('should emit an event when close button is clicked', () => {
......
...@@ -10,7 +10,7 @@ describe('Deploy keys app component', () => { ...@@ -10,7 +10,7 @@ describe('Deploy keys app component', () => {
let vm; let vm;
let mock; let mock;
beforeEach((done) => { beforeEach(done => {
// set up axios mock before component // set up axios mock before component
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
mock.onGet(`${TEST_HOST}/dummy/`).replyOnce(200, data); mock.onGet(`${TEST_HOST}/dummy/`).replyOnce(200, data);
......
...@@ -44,8 +44,7 @@ describe('diffs/components/app', () => { ...@@ -44,8 +44,7 @@ describe('diffs/components/app', () => {
it('shows comments message, with commit', done => { it('shows comments message, with commit', done => {
vm.$store.state.diffs.commit = getDiffWithCommit().commit; vm.$store.state.diffs.commit = getDiffWithCommit().commit;
vm vm.$nextTick()
.$nextTick()
.then(() => { .then(() => {
expect(vm.$el).toContainText('Only comments from the following commit are shown below'); expect(vm.$el).toContainText('Only comments from the following commit are shown below');
expect(vm.$el).toContainElement('.blob-commit-info'); expect(vm.$el).toContainElement('.blob-commit-info');
...@@ -58,8 +57,7 @@ describe('diffs/components/app', () => { ...@@ -58,8 +57,7 @@ describe('diffs/components/app', () => {
vm.$store.state.diffs.mergeRequestDiff = { latest: false }; vm.$store.state.diffs.mergeRequestDiff = { latest: false };
vm.$store.state.diffs.targetBranch = 'master'; vm.$store.state.diffs.targetBranch = 'master';
vm vm.$nextTick()
.$nextTick()
.then(() => { .then(() => {
expect(vm.$el).toContainText( expect(vm.$el).toContainText(
"Not all comments are displayed because you're viewing an old version of the diff.", "Not all comments are displayed because you're viewing an old version of the diff.",
...@@ -72,8 +70,7 @@ describe('diffs/components/app', () => { ...@@ -72,8 +70,7 @@ describe('diffs/components/app', () => {
it('shows comments message, with startVersion', done => { it('shows comments message, with startVersion', done => {
vm.$store.state.diffs.startVersion = 'test'; vm.$store.state.diffs.startVersion = 'test';
vm vm.$nextTick()
.$nextTick()
.then(() => { .then(() => {
expect(vm.$el).toContainText( expect(vm.$el).toContainText(
"Not all comments are displayed because you're comparing two versions of the diff.", "Not all comments are displayed because you're comparing two versions of the diff.",
......
...@@ -14,7 +14,8 @@ const TEST_PIPELINE_STATUS_PATH = `${TEST_HOST}/pipeline/status`; ...@@ -14,7 +14,8 @@ const TEST_PIPELINE_STATUS_PATH = `${TEST_HOST}/pipeline/status`;
const getTitleElement = vm => vm.$el.querySelector('.commit-row-message.item-title'); const getTitleElement = vm => vm.$el.querySelector('.commit-row-message.item-title');
const getDescElement = vm => vm.$el.querySelector('pre.commit-row-description'); const getDescElement = vm => vm.$el.querySelector('pre.commit-row-description');
const getDescExpandElement = vm => vm.$el.querySelector('.commit-content .text-expander.js-toggle-button'); const getDescExpandElement = vm =>
vm.$el.querySelector('.commit-content .text-expander.js-toggle-button');
const getShaElement = vm => vm.$el.querySelector('.commit-sha-group'); const getShaElement = vm => vm.$el.querySelector('.commit-sha-group');
const getAvatarElement = vm => vm.$el.querySelector('.user-avatar-link'); const getAvatarElement = vm => vm.$el.querySelector('.user-avatar-link');
const getCommitterElement = vm => vm.$el.querySelector('.commiter'); const getCommitterElement = vm => vm.$el.querySelector('.commiter');
......
...@@ -316,7 +316,9 @@ describe('diff_file_header', () => { ...@@ -316,7 +316,9 @@ describe('diff_file_header', () => {
const button = vm.$el.querySelector('.btn-clipboard'); const button = vm.$el.querySelector('.btn-clipboard');
expect(button).not.toBe(null); expect(button).not.toBe(null);
expect(button.dataset.clipboardText).toBe('{"text":"files/ruby/popen.rb","gfm":"`files/ruby/popen.rb`"}'); expect(button.dataset.clipboardText).toBe(
'{"text":"files/ruby/popen.rb","gfm":"`files/ruby/popen.rb`"}',
);
}); });
describe('file mode', () => { describe('file mode', () => {
......
...@@ -99,7 +99,9 @@ describe('DiffFile', () => { ...@@ -99,7 +99,9 @@ describe('DiffFile', () => {
); );
expect(vm.$el.querySelector('.js-too-large-diff')).toBeDefined(); expect(vm.$el.querySelector('.js-too-large-diff')).toBeDefined();
expect(vm.$el.querySelector('.js-too-large-diff a').href.indexOf(BLOB_LINK)).toBeGreaterThan(-1); expect(
vm.$el.querySelector('.js-too-large-diff a').href.indexOf(BLOB_LINK),
).toBeGreaterThan(-1);
done(); done();
}); });
......
...@@ -5,8 +5,5 @@ const FIXTURE = 'merge_request_diffs/with_commit.json'; ...@@ -5,8 +5,5 @@ const FIXTURE = 'merge_request_diffs/with_commit.json';
preloadFixtures(FIXTURE); preloadFixtures(FIXTURE);
export default function getDiffWithCommit() { export default function getDiffWithCommit() {
return convertObjectPropsToCamelCase( return convertObjectPropsToCamelCase(getJSONFixture(FIXTURE), { deep: true });
getJSONFixture(FIXTURE),
{ deep: true },
);
} }
import DirtySubmitForm from '~/dirty_submit/dirty_submit_form'; import DirtySubmitForm from '~/dirty_submit/dirty_submit_form';
import { setInput, createForm } from './helper'; import { setInput, createForm } from './helper';
......
This diff is collapsed.
import Hook from '~/droplab/hook'; import Hook from '~/droplab/hook';
describe('Hook', function () { describe('Hook', function() {
describe('class constructor', function () { describe('class constructor', function() {
beforeEach(function () { beforeEach(function() {
this.trigger = { id: 'id' }; this.trigger = { id: 'id' };
this.list = {}; this.list = {};
this.plugins = {}; this.plugins = {};
...@@ -14,58 +14,58 @@ describe('Hook', function () { ...@@ -14,58 +14,58 @@ describe('Hook', function () {
this.hook = new Hook(this.trigger, this.list, this.plugins, this.config); this.hook = new Hook(this.trigger, this.list, this.plugins, this.config);
}); });
it('should set .trigger', function () { it('should set .trigger', function() {
expect(this.hook.trigger).toBe(this.trigger); expect(this.hook.trigger).toBe(this.trigger);
}); });
it('should set .list', function () { it('should set .list', function() {
expect(this.hook.list).toBe(this.dropdown); expect(this.hook.list).toBe(this.dropdown);
}); });
it('should call DropDown constructor', function () { it('should call DropDown constructor', function() {
expect(this.dropdownConstructor).toHaveBeenCalledWith(this.list, this.config); expect(this.dropdownConstructor).toHaveBeenCalledWith(this.list, this.config);
}); });
it('should set .type', function () { it('should set .type', function() {
expect(this.hook.type).toBe('Hook'); expect(this.hook.type).toBe('Hook');
}); });
it('should set .event', function () { it('should set .event', function() {
expect(this.hook.event).toBe('click'); expect(this.hook.event).toBe('click');
}); });
it('should set .plugins', function () { it('should set .plugins', function() {
expect(this.hook.plugins).toBe(this.plugins); expect(this.hook.plugins).toBe(this.plugins);
}); });
it('should set .config', function () { it('should set .config', function() {
expect(this.hook.config).toBe(this.config); expect(this.hook.config).toBe(this.config);
}); });
it('should set .id', function () { it('should set .id', function() {
expect(this.hook.id).toBe(this.trigger.id); expect(this.hook.id).toBe(this.trigger.id);
}); });
describe('if config argument is undefined', function () { describe('if config argument is undefined', function() {
beforeEach(function () { beforeEach(function() {
this.config = undefined; this.config = undefined;
this.hook = new Hook(this.trigger, this.list, this.plugins, this.config); this.hook = new Hook(this.trigger, this.list, this.plugins, this.config);
}); });
it('should set .config to an empty object', function () { it('should set .config to an empty object', function() {
expect(this.hook.config).toEqual({}); expect(this.hook.config).toEqual({});
}); });
}); });
describe('if plugins argument is undefined', function () { describe('if plugins argument is undefined', function() {
beforeEach(function () { beforeEach(function() {
this.plugins = undefined; this.plugins = undefined;
this.hook = new Hook(this.trigger, this.list, this.plugins, this.config); this.hook = new Hook(this.trigger, this.list, this.plugins, this.config);
}); });
it('should set .plugins to an empty array', function () { it('should set .plugins to an empty array', function() {
expect(this.hook.plugins).toEqual([]); expect(this.hook.plugins).toEqual([]);
}); });
}); });
......
...@@ -38,8 +38,8 @@ describe('AjaxFilter', () => { ...@@ -38,8 +38,8 @@ describe('AjaxFilter', () => {
dummyList.list.appendChild(dynamicList); dummyList.list.appendChild(dynamicList);
}); });
it('calls onLoadingFinished after loading data', (done) => { it('calls onLoadingFinished after loading data', done => {
ajaxSpy = (url) => { ajaxSpy = url => {
expect(url).toBe('dummy endpoint?dummy search key='); expect(url).toBe('dummy endpoint?dummy search key=');
return Promise.resolve(dummyData); return Promise.resolve(dummyData);
}; };
...@@ -52,16 +52,16 @@ describe('AjaxFilter', () => { ...@@ -52,16 +52,16 @@ describe('AjaxFilter', () => {
.catch(done.fail); .catch(done.fail);
}); });
it('does not call onLoadingFinished if Ajax call fails', (done) => { it('does not call onLoadingFinished if Ajax call fails', done => {
const dummyError = new Error('My dummy is sick! :-('); const dummyError = new Error('My dummy is sick! :-(');
ajaxSpy = (url) => { ajaxSpy = url => {
expect(url).toBe('dummy endpoint?dummy search key='); expect(url).toBe('dummy endpoint?dummy search key=');
return Promise.reject(dummyError); return Promise.reject(dummyError);
}; };
AjaxFilter.trigger() AjaxFilter.trigger()
.then(done.fail) .then(done.fail)
.catch((error) => { .catch(error => {
expect(error).toBe(dummyError); expect(error).toBe(dummyError);
expect(dummyConfig.onLoadingFinished.calls.count()).toBe(0); expect(dummyConfig.onLoadingFinished.calls.count()).toBe(0);
}) })
......
...@@ -29,7 +29,7 @@ describe('Ajax', () => { ...@@ -29,7 +29,7 @@ describe('Ajax', () => {
it('overrides AjaxCache', () => { it('overrides AjaxCache', () => {
spyOn(AjaxCache, 'override').and.callFake((endpoint, results) => { spyOn(AjaxCache, 'override').and.callFake((endpoint, results) => {
expect(results).toEqual(processedArray); expect(results).toEqual(processedArray);
}); });
Ajax.preprocessing(config, []); Ajax.preprocessing(config, []);
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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