Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-01-24 15:11:33 +00:00
parent 4d3bbc4990
commit 87543246d9
70 changed files with 490 additions and 611 deletions

View File

@ -4,10 +4,6 @@ GraphQL/OrderedArguments:
- app/graphql/resolvers/base_issues_resolver.rb
- app/graphql/resolvers/design_management/designs_resolver.rb
- app/graphql/resolvers/design_management/version/design_at_version_resolver.rb
- app/graphql/resolvers/merge_requests_resolver.rb
- app/graphql/resolvers/paginated_tree_resolver.rb
- app/graphql/resolvers/tree_resolver.rb
- app/graphql/resolvers/users/groups_resolver.rb
- app/graphql/types/commit_action_type.rb
- app/graphql/types/diff_paths_input_type.rb
- app/graphql/types/issues/negated_issue_filter_input_type.rb

View File

@ -5,7 +5,6 @@ import { __ } from '../../../locale';
export default class PayloadPreviewer {
constructor(trigger) {
this.trigger = trigger;
this.container = document.querySelector(trigger.dataset.payloadSelector);
this.isVisible = false;
this.isInserted = false;
}
@ -23,21 +22,27 @@ export default class PayloadPreviewer {
});
}
getContainer() {
return document.querySelector(this.trigger.dataset.payloadSelector);
}
requestPayload() {
if (this.isInserted) return this.showPayload();
this.spinner.classList.add('d-inline-flex');
this.spinner.classList.add('gl-display-inline-flex');
const container = this.getContainer();
return axios
.get(this.container.dataset.endpoint, {
.get(container.dataset.endpoint, {
responseType: 'text',
})
.then(({ data }) => {
this.spinner.classList.remove('d-inline-flex');
this.spinner.classList.remove('gl-display-inline-flex');
this.insertPayload(data);
})
.catch(() => {
this.spinner.classList.remove('d-inline-flex');
this.spinner.classList.remove('gl-display-inline-flex');
createFlash({
message: __('Error fetching payload data.'),
});
@ -46,19 +51,19 @@ export default class PayloadPreviewer {
hidePayload() {
this.isVisible = false;
this.container.classList.add('d-none');
this.getContainer().classList.add('gl-display-none');
this.text.textContent = __('Preview payload');
}
showPayload() {
this.isVisible = true;
this.container.classList.remove('d-none');
this.getContainer().classList.remove('gl-display-none');
this.text.textContent = __('Hide payload');
}
insertPayload(data) {
this.isInserted = true;
this.container.innerHTML = data;
this.getContainer().innerHTML = data;
this.showPayload();
}
}

View File

@ -28,7 +28,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
if group_link.expires?
render json: {
expires_in: helpers.distance_of_time_in_words_to_now(group_link.expires_at),
expires_in: helpers.time_ago_with_tooltip(group_link.expires_at),
expires_soon: group_link.expires_soon?
}
else

View File

@ -55,6 +55,13 @@ module Resolvers
required: false,
description: 'Limit result to draft merge requests.'
argument :created_after, Types::TimeType,
required: false,
description: 'Merge requests created after this timestamp.'
argument :created_before, Types::TimeType,
required: false,
description: 'Merge requests created before this timestamp.'
argument :labels, [GraphQL::Types::String],
required: false,
as: :label_name,
@ -72,12 +79,6 @@ module Resolvers
description: 'Sort merge requests by this criteria.',
required: false,
default_value: :created_desc
argument :created_after, Types::TimeType,
required: false,
description: 'Merge requests created after this timestamp.'
argument :created_before, Types::TimeType,
required: false,
description: 'Merge requests created before this timestamp.'
negated do
argument :labels, [GraphQL::Types::String],

View File

@ -11,14 +11,14 @@ module Resolvers
required: false,
default_value: '', # root of the repository
description: 'Path to get the tree for. Default value is the root of the repository.'
argument :ref, GraphQL::Types::String,
required: false,
default_value: :head,
description: 'Commit ref to get the tree for. Default value is HEAD.'
argument :recursive, GraphQL::Types::Boolean,
required: false,
default_value: false,
description: 'Used to get a recursive tree. Default is false.'
argument :ref, GraphQL::Types::String,
required: false,
default_value: :head,
description: 'Commit ref to get the tree for. Default value is HEAD.'
alias_method :repository, :object

View File

@ -10,14 +10,14 @@ module Resolvers
required: false,
default_value: '',
description: 'Path to get the tree for. Default value is the root of the repository.'
argument :ref, GraphQL::Types::String,
required: false,
default_value: :head,
description: 'Commit ref to get the tree for. Default value is HEAD.'
argument :recursive, GraphQL::Types::Boolean,
required: false,
default_value: false,
description: 'Used to get a recursive tree. Default is false.'
argument :ref, GraphQL::Types::String,
required: false,
default_value: :head,
description: 'Commit ref to get the tree for. Default value is HEAD.'
alias_method :repository, :object

View File

@ -11,13 +11,13 @@ module Resolvers
authorize :read_user_groups
authorizes_object!
argument :search, GraphQL::Types::String,
required: false,
description: 'Search by group name or path.'
argument :permission_scope,
::Types::PermissionTypes::GroupEnum,
required: false,
description: 'Filter by permissions the user has on groups.'
argument :search, GraphQL::Types::String,
required: false,
description: 'Search by group name or path.'
before_connection_authorization do |nodes, current_user|
Preloaders::GroupPolicyPreloader.new(nodes, current_user).execute

View File

@ -180,6 +180,7 @@ class Member < ApplicationRecord
scope :on_project_and_ancestors, ->(project) { where(source: [project] + project.ancestors) }
before_validation :set_member_namespace_id, on: :create
before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? && !member.invite_accepted_at? }
after_create :send_invite, if: :invite?, unless: :importing?
@ -380,6 +381,12 @@ class Member < ApplicationRecord
private
# TODO: https://gitlab.com/groups/gitlab-org/-/epics/7054
# temporary until we can we properly remove the source columns
def set_member_namespace_id
self.member_namespace_id = self.source_id
end
def access_level_inclusion
return if access_level.in?(Gitlab::Access.all_values)

View File

@ -118,6 +118,13 @@ class ProjectMember < Member
# rubocop:enable CodeReuse/ServiceClass
end
# TODO: https://gitlab.com/groups/gitlab-org/-/epics/7054
# temporary until we can we properly remove the source columns
override :set_member_namespace_id
def set_member_namespace_id
self.member_namespace_id = project&.project_namespace_id
end
def send_invite
run_after_commit_or_now { notification_service.invite_project_member(self, @raw_invite_token) }

View File

@ -18,11 +18,11 @@ class StateNote < SyntheticNote
def note_text(html: false)
if event.state == 'closed'
if event.close_after_error_tracking_resolve
return 'resolved the corresponding error and closed the issue.'
return 'resolved the corresponding error and closed the issue'
end
if event.close_auto_resolve_prometheus_alert
return 'automatically closed this issue because the alert resolved.'
return 'automatically closed this incident because the alert resolved'
end
end

View File

@ -81,7 +81,7 @@ module Issues
return if alert.resolved?
if alert.resolve
SystemNotes::AlertManagementService.new(noteable: alert, project: alert.project, author: current_user).closed_alert_issue(issue)
SystemNoteService.change_alert_status(alert, current_user, " by closing incident #{issue.to_reference(project)}")
else
Gitlab::AppLogger.warn(
message: 'Cannot resolve an associated Alert Management alert',
@ -97,7 +97,7 @@ module Issues
status = issue.incident_management_issuable_escalation_status || issue.build_incident_management_issuable_escalation_status
SystemNoteService.resolve_incident_status(issue, current_user) if status.resolve
SystemNoteService.change_incident_status(issue, current_user, ' by closing the incident') if status.resolve
end
def store_first_mentioned_in_commit_at(issue, merge_request, max_commit_lookup: 100)

View File

@ -47,8 +47,7 @@ module Projects
end
def save_all!
if save_exporters
Gitlab::ImportExport::Saver.save(exportable: project, shared: shared)
if save_exporters && save_export_archive
notify_success
else
notify_error!
@ -59,6 +58,10 @@ module Projects
exporters.all?(&:save)
end
def save_export_archive
Gitlab::ImportExport::Saver.save(exportable: project, shared: shared)
end
def exporters
[
version_saver, avatar_saver, project_tree_saver, uploads_saver,

View File

@ -335,10 +335,6 @@ module SystemNoteService
::SystemNotes::IncidentService.new(noteable: incident, project: incident.project, author: author).change_incident_severity
end
def resolve_incident_status(incident, author)
::SystemNotes::IncidentService.new(noteable: incident, project: incident.project, author: author).resolve_incident_status
end
def change_incident_status(incident, author, reason = nil)
::SystemNotes::IncidentService.new(noteable: incident, project: incident.project, author: author).change_incident_status(reason)
end

View File

@ -40,30 +40,15 @@ module SystemNotes
#
# Example Note text:
#
# "created issue #17 for this alert"
# "created incident #17 for this alert"
#
# Returns the created Note object
def new_alert_issue(issue)
body = "created issue #{issue.to_reference(project)} for this alert"
body = "created incident #{issue.to_reference(project)} for this alert"
create_note(NoteSummary.new(noteable, project, author, body, action: 'alert_issue_added'))
end
# Called when an AlertManagement::Alert is resolved due to the associated issue being closed
#
# issue - Issue object.
#
# Example Note text:
#
# "changed the status to Resolved by closing issue #17"
#
# Returns the created Note object
def closed_alert_issue(issue)
body = "changed the status to **Resolved** by closing issue #{issue.to_reference(project)}"
create_note(NoteSummary.new(noteable, project, author, body, action: 'status'))
end
# Called when an alert is resolved due to received resolving alert payload
#
# alert - AlertManagement::Alert object.

View File

@ -26,12 +26,6 @@ module SystemNotes
end
end
def resolve_incident_status
body = 'changed the status to **Resolved** by closing the incident'
create_note(NoteSummary.new(noteable, project, author, body, action: 'status'))
end
# Called when the status of an IncidentManagement::IssuableEscalationStatus has changed
#
# reason - String.

View File

@ -28,8 +28,8 @@
%button.gl-button.btn.btn-default.js-payload-preview-trigger{ type: 'button', data: { payload_selector: ".#{payload_class}" } }
.gl-spinner.js-spinner.gl-display-none.gl-mr-2
.js-text.d-inline= _('Preview payload')
%pre.service-data-payload-container.js-syntax-highlight.code.highlight.mt-2.d-none{ class: payload_class, data: { endpoint: usage_data_admin_application_settings_path(format: :html) } }
.js-text.gl-display-inline= _('Preview payload')
%pre.service-data-payload-container.js-syntax-highlight.code.highlight.gl-mt-2.gl-display-none{ class: payload_class, data: { endpoint: usage_data_admin_application_settings_path(format: :html) } }
- else
= _('Service ping is disabled in your configuration file, and cannot be enabled through this form.')
- deactivating_service_ping_path = help_page_path('development/service_ping/index.md', anchor: 'disable-service-ping-using-the-configuration-file')

View File

@ -4,7 +4,6 @@
.content-wrapper.content-wrapper-margin{ class: "#{@content_wrapper_class}" }
.mobile-overlay
= render_if_exists 'layouts/header/verification_reminder'
= yield :group_invite_members_banner
.alert-wrapper.gl-force-block-formatting-context
= render 'shared/outdated_browser'
= render_if_exists "layouts/header/licensed_user_count_threshold"
@ -21,6 +20,7 @@
= render_if_exists "shared/namespace_user_cap_reached_alert"
= render_if_exists "shared/new_user_signups_cap_reached_alert"
= yield :page_level_alert
= yield :group_invite_members_banner
- unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs"
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" }

View File

@ -0,0 +1,15 @@
- name: "`started` iterations API field" # The name of the feature to be deprecated
announcement_milestone: "14.8" # The milestone when this feature was first announced as deprecated.
announcement_date: "2022-02-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
removal_milestone: "15.0" # The milestone when this feature is planned to be removed
removal_date: "2022-05-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
breaking_change: true # If this deprecation is a breaking change, set this value to true
body: | # Do not modify this line, instead modify the lines below.
The `started` field in the [iterations API](https://docs.gitlab.com/ee/api/iterations.html#list-project-iterations) is being deprecated and will be removed in GitLab 15.0. This field is being replaced with the `current` field (already available) which aligns with the naming for other time-based entities, such as milestones.
# The following items are not published on the docs page, but may be used in the future.
stage: plan
tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334018
documentation_url: # (optional) This is a link to the current documentation page
image_url: # (optional) This is a link to a thumbnail image depicting the feature
video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg

View File

@ -85,10 +85,9 @@ To remove a metric:
1. Verify that removing the metric from the Service Ping payload does not cause
errors in [Version App](https://gitlab.com/gitlab-services/version-gitlab-com)
when the updated payload is collected and processed. Version App collects
and persists all Service Ping reports. To do that you can modify
[fixtures](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/master/spec/support/usage_data_helpers.rb#L540)
used to test
[`UsageDataController#create`](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/3760ef28/spec/controllers/usage_data_controller_spec.rb#L75)
and persists all Service Ping reports. To verify Service Ping processing in your local development environment, follow this [guide](https://www.youtube.com/watch?v=FS5emplabRU).
Alternatively, you can modify [fixtures](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/master/spec/support/usage_data_helpers.rb#L540)
used to test the [`UsageDataController#create`](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/3760ef28/spec/controllers/usage_data_controller_spec.rb#L75)
endpoint, and assure that test suite does not fail when metric that you wish to remove is not included into test payload.
1. Create an issue in the

View File

@ -716,3 +716,15 @@ Support for `fixup!` is now considered deprecated, and will be
removed in GitLab 15.0.
**Planned removal milestone: 15.0 (2022-06-22)**
### `started` iterations API field
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
The `started` field in the [iterations API](https://docs.gitlab.com/ee/api/iterations.html#list-project-iterations) is being deprecated and will be removed in GitLab 15.0. This field is being replaced with the `current` field (already available) which aligns with the naming for other time-based entities, such as milestones.
**Planned removal milestone: 15.0 (2022-05-22)**

View File

@ -54,6 +54,8 @@ module Gitlab
File.open(archive_file) { |file| upload.export_file = file }
upload.save!
true
end
def error_message

View File

@ -9,9 +9,7 @@ module QA
attribute :id
attribute :project do
Project.fabricate! do |project|
project.initialize_with_readme = true
end
Project.fabricate!
end
attribute :token do
Page::Project::Settings::AccessTokens.perform(&:created_access_token)

View File

@ -4,7 +4,9 @@ module QA
RSpec.describe 'Manage' do
describe 'Project access token' do
before(:all) do
@project_access_token = QA::Resource::ProjectAccessToken.fabricate_via_api!
@project_access_token = QA::Resource::ProjectAccessToken.fabricate_via_api! do |pat|
pat.project = Resource::ReusableProject.fabricate_via_api!
end
@user_api_client = Runtime::API::Client.new(:gitlab, personal_access_token: @project_access_token.token)
end
@ -76,11 +78,6 @@ module QA
@different_project.remove_via_api!
end
end
after(:all) do
@project_access_token.remove_via_api!
@project_access_token.project.remove_via_api!
end
end
end
end

View File

@ -11,12 +11,7 @@ module QA
let(:title) { "MR push options test #{SecureRandom.hex(8)}" }
let(:commit_message) { 'Add README.md' }
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'merge-request-push-options'
project.initialize_with_readme = true
end
end
let(:project) { Resource::ReusableProject.fabricate_via_api! }
def create_new_mr_via_push
Resource::Repository::ProjectPush.fabricate! do |push|

View File

@ -3,9 +3,10 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
require_relative '../../support/stub_settings_source'
require_relative '../../../sidekiq_cluster/cli'
RSpec.describe Gitlab::SidekiqCluster::CLI, stubbing_settings_source: true do # rubocop:disable RSpec/FilePath
RSpec.describe Gitlab::SidekiqCluster::CLI, stub_settings_source: true do # rubocop:disable RSpec/FilePath
let(:cli) { described_class.new('/dev/null') }
let(:timeout) { Gitlab::SidekiqCluster::DEFAULT_SOFT_TIMEOUT_SECONDS }
let(:default_options) do

View File

@ -178,7 +178,7 @@ RSpec.describe Projects::GroupLinksController do
context 'when `expires_at` is set' do
it 'returns correct json response' do
expect(json_response).to eq({ "expires_in" => "about 1 month", "expires_soon" => false })
expect(json_response).to eq({ "expires_in" => controller.helpers.time_ago_with_tooltip(expiry_date), "expires_soon" => false })
end
end

View File

@ -528,7 +528,7 @@ RSpec.describe 'Admin updates settings' do
expect(find_field('Allow access to members of the following group').value).to be_nil
end
it 'loads usage ping payload on click', :js do
it 'loads togglable usage ping payload on click', :js do
stub_usage_data_connections
stub_database_flavor_check
@ -544,6 +544,10 @@ RSpec.describe 'Admin updates settings' do
expect(page).to have_selector '.js-service-ping-payload'
expect(page).to have_button 'Hide payload'
expect(page).to have_content expected_payload_content
click_button('Hide payload')
expect(page).not_to have_content expected_payload_content
end
end
end

View File

@ -1,5 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import { DUMMY_IMAGE_URL, TEST_HOST } from 'helpers/test_constants';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import BadgeForm from '~/badges/components/badge_form.vue';
@ -74,7 +74,7 @@ describe('BadgeForm component', () => {
expect(feedbackElement).toBeVisible();
};
beforeEach((done) => {
beforeEach(async () => {
jest.spyOn(vm, submitAction).mockReturnValue(Promise.resolve());
store.replaceState({
...store.state,
@ -83,14 +83,10 @@ describe('BadgeForm component', () => {
isSaving: false,
});
Vue.nextTick()
.then(() => {
setValue(nameSelector, 'TestBadge');
setValue(linkUrlSelector, `${TEST_HOST}/link/url`);
setValue(imageUrlSelector, `${window.location.origin}${DUMMY_IMAGE_URL}`);
})
.then(done)
.catch(done.fail);
await nextTick();
setValue(nameSelector, 'TestBadge');
setValue(linkUrlSelector, `${TEST_HOST}/link/url`);
setValue(imageUrlSelector, `${window.location.origin}${DUMMY_IMAGE_URL}`);
});
it('returns immediately if imageUrl is empty', () => {

View File

@ -1,4 +1,4 @@
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import BadgeListRow from '~/badges/components/badge_list_row.vue';
import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
@ -73,25 +73,21 @@ describe('BadgeListRow component', () => {
expect(vm.editBadge).toHaveBeenCalled();
});
it('calls updateBadgeInModal and shows modal when clicking then delete button', (done) => {
it('calls updateBadgeInModal and shows modal when clicking then delete button', async () => {
jest.spyOn(vm, 'updateBadgeInModal').mockImplementation(() => {});
const deleteButton = vm.$el.querySelector('.table-button-footer button:last-of-type');
deleteButton.click();
Vue.nextTick()
.then(() => {
expect(vm.updateBadgeInModal).toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
await nextTick();
expect(vm.updateBadgeInModal).toHaveBeenCalled();
});
describe('for a group badge', () => {
beforeEach((done) => {
beforeEach(async () => {
badge.kind = GROUP_BADGE;
Vue.nextTick().then(done).catch(done.fail);
await nextTick();
});
it('renders the badge kind', () => {

View File

@ -1,4 +1,4 @@
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import BadgeList from '~/badges/components/badge_list.vue';
import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
@ -48,46 +48,34 @@ describe('BadgeList component', () => {
expect(rows).toHaveLength(numberOfDummyBadges);
});
it('renders a message if no badges exist', (done) => {
it('renders a message if no badges exist', async () => {
store.state.badges = [];
Vue.nextTick()
.then(() => {
expect(vm.$el.innerText).toMatch('This project has no badges');
})
.then(done)
.catch(done.fail);
await nextTick();
expect(vm.$el.innerText).toMatch('This project has no badges');
});
it('shows a loading icon when loading', (done) => {
it('shows a loading icon when loading', async () => {
store.state.isLoading = true;
Vue.nextTick()
.then(() => {
const loadingIcon = vm.$el.querySelector('.gl-spinner');
await nextTick();
const loadingIcon = vm.$el.querySelector('.gl-spinner');
expect(loadingIcon).toBeVisible();
})
.then(done)
.catch(done.fail);
expect(loadingIcon).toBeVisible();
});
describe('for group badges', () => {
beforeEach((done) => {
beforeEach(async () => {
store.state.kind = GROUP_BADGE;
Vue.nextTick().then(done).catch(done.fail);
await nextTick();
});
it('renders a message if no badges exist', (done) => {
it('renders a message if no badges exist', async () => {
store.state.badges = [];
Vue.nextTick()
.then(() => {
expect(vm.$el.innerText).toMatch('This group has no badges');
})
.then(done)
.catch(done.fail);
await nextTick();
expect(vm.$el.innerText).toMatch('This group has no badges');
});
});
});

View File

@ -1,4 +1,4 @@
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import { DUMMY_IMAGE_URL, TEST_HOST } from 'spec/test_constants';
import Badge from '~/badges/components/badge.vue';
@ -27,7 +27,7 @@ describe('Badge component', () => {
badgeImage.addEventListener('load', resolve);
// Manually dispatch load event as it is not triggered
badgeImage.dispatchEvent(new Event('load'));
}).then(() => Vue.nextTick());
}).then(() => nextTick());
};
afterEach(() => {
@ -36,34 +36,25 @@ describe('Badge component', () => {
describe('watchers', () => {
describe('imageUrl', () => {
it('sets isLoading and resets numRetries and hasError', (done) => {
it('sets isLoading and resets numRetries and hasError', async () => {
const props = { ...dummyProps };
createComponent(props)
.then(() => {
expect(vm.isLoading).toBe(false);
vm.hasError = true;
vm.numRetries = 42;
await createComponent(props);
expect(vm.isLoading).toBe(false);
vm.hasError = true;
vm.numRetries = 42;
vm.imageUrl = `${props.imageUrl}#something/else`;
return Vue.nextTick();
})
.then(() => {
expect(vm.isLoading).toBe(true);
expect(vm.numRetries).toBe(0);
expect(vm.hasError).toBe(false);
})
.then(done)
.catch(done.fail);
vm.imageUrl = `${props.imageUrl}#something/else`;
await nextTick();
expect(vm.isLoading).toBe(true);
expect(vm.numRetries).toBe(0);
expect(vm.hasError).toBe(false);
});
});
});
describe('methods', () => {
beforeEach((done) => {
createComponent({ ...dummyProps })
.then(done)
.catch(done.fail);
beforeEach(async () => {
await createComponent({ ...dummyProps });
});
it('onError resets isLoading and sets hasError', () => {
@ -116,37 +107,29 @@ describe('Badge component', () => {
expect(vm.$el.querySelector('.btn-group')).toBeHidden();
});
it('shows a loading icon when loading', (done) => {
it('shows a loading icon when loading', async () => {
vm.isLoading = true;
Vue.nextTick()
.then(() => {
const { badgeImage, loadingIcon, reloadButton } = findElements();
await nextTick();
const { badgeImage, loadingIcon, reloadButton } = findElements();
expect(badgeImage).toBeHidden();
expect(loadingIcon).toBeVisible();
expect(reloadButton).toBeHidden();
expect(vm.$el.querySelector('.btn-group')).toBeHidden();
})
.then(done)
.catch(done.fail);
expect(badgeImage).toBeHidden();
expect(loadingIcon).toBeVisible();
expect(reloadButton).toBeHidden();
expect(vm.$el.querySelector('.btn-group')).toBeHidden();
});
it('shows an error and reload button if loading failed', (done) => {
it('shows an error and reload button if loading failed', async () => {
vm.hasError = true;
Vue.nextTick()
.then(() => {
const { badgeImage, loadingIcon, reloadButton } = findElements();
await nextTick();
const { badgeImage, loadingIcon, reloadButton } = findElements();
expect(badgeImage).toBeHidden();
expect(loadingIcon).toBeHidden();
expect(reloadButton).toBeVisible();
expect(reloadButton).toHaveSpriteIcon('retry');
expect(vm.$el.innerText.trim()).toBe('No badge image');
})
.then(done)
.catch(done.fail);
expect(badgeImage).toBeHidden();
expect(loadingIcon).toBeHidden();
expect(reloadButton).toBeVisible();
expect(reloadButton).toHaveSpriteIcon('retry');
expect(vm.$el.innerText.trim()).toBe('No badge image');
});
});
});

View File

@ -1,4 +1,4 @@
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import PreviewDropdown from '~/batch_comments/components/preview_dropdown.vue';
@ -49,7 +49,7 @@ describe('Batch comments preview dropdown', () => {
wrapper.findByTestId('preview-item').vm.$emit('click');
await Vue.nextTick();
await nextTick();
expect(setCurrentFileHash).toHaveBeenCalledWith(expect.anything(), 'hash');
expect(scrollToDraft).toHaveBeenCalledWith(expect.anything(), { id: 1, file_hash: 'hash' });
@ -63,7 +63,7 @@ describe('Batch comments preview dropdown', () => {
wrapper.findByTestId('preview-item').vm.$emit('click');
await Vue.nextTick();
await nextTick();
expect(scrollToDraft).toHaveBeenCalledWith(expect.anything(), { id: 1 });
});

View File

@ -1,4 +1,4 @@
import Vue from 'vue';
import { nextTick } from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import initConfirmModal from '~/confirm_modal';
@ -92,10 +92,9 @@ describe('ConfirmModal', () => {
});
describe('Cancel Button', () => {
beforeEach(() => {
beforeEach(async () => {
findModalCancelButton(findModal()).click();
return Vue.nextTick();
await nextTick();
});
it('closes the modal', () => {

View File

@ -1,6 +1,6 @@
import { GlFormCheckbox } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import EksClusterConfigurationForm from '~/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue';
@ -223,108 +223,98 @@ describe('EksClusterConfigurationForm', () => {
});
});
it('sets isLoadingRoles to RoleDropdown loading property', () => {
it('sets isLoadingRoles to RoleDropdown loading property', async () => {
rolesState.isLoadingItems = true;
return Vue.nextTick().then(() => {
expect(findRoleDropdown().props('loading')).toBe(rolesState.isLoadingItems);
});
await nextTick();
expect(findRoleDropdown().props('loading')).toBe(rolesState.isLoadingItems);
});
it('sets roles to RoleDropdown items property', () => {
expect(findRoleDropdown().props('items')).toBe(rolesState.items);
});
it('sets RoleDropdown hasErrors to true when loading roles failed', () => {
it('sets RoleDropdown hasErrors to true when loading roles failed', async () => {
rolesState.loadingItemsError = new Error();
return Vue.nextTick().then(() => {
expect(findRoleDropdown().props('hasErrors')).toEqual(true);
});
await nextTick();
expect(findRoleDropdown().props('hasErrors')).toEqual(true);
});
it('disables KeyPairDropdown when no region is selected', () => {
expect(findKeyPairDropdown().props('disabled')).toBe(true);
});
it('enables KeyPairDropdown when no region is selected', () => {
it('enables KeyPairDropdown when no region is selected', async () => {
state.selectedRegion = { name: 'west-1 ' };
return Vue.nextTick().then(() => {
expect(findKeyPairDropdown().props('disabled')).toBe(false);
});
await nextTick();
expect(findKeyPairDropdown().props('disabled')).toBe(false);
});
it('sets isLoadingKeyPairs to KeyPairDropdown loading property', () => {
it('sets isLoadingKeyPairs to KeyPairDropdown loading property', async () => {
keyPairsState.isLoadingItems = true;
return Vue.nextTick().then(() => {
expect(findKeyPairDropdown().props('loading')).toBe(keyPairsState.isLoadingItems);
});
await nextTick();
expect(findKeyPairDropdown().props('loading')).toBe(keyPairsState.isLoadingItems);
});
it('sets keyPairs to KeyPairDropdown items property', () => {
expect(findKeyPairDropdown().props('items')).toBe(keyPairsState.items);
});
it('sets KeyPairDropdown hasErrors to true when loading key pairs fails', () => {
it('sets KeyPairDropdown hasErrors to true when loading key pairs fails', async () => {
keyPairsState.loadingItemsError = new Error();
return Vue.nextTick().then(() => {
expect(findKeyPairDropdown().props('hasErrors')).toEqual(true);
});
await nextTick();
expect(findKeyPairDropdown().props('hasErrors')).toEqual(true);
});
it('disables VpcDropdown when no region is selected', () => {
expect(findVpcDropdown().props('disabled')).toBe(true);
});
it('enables VpcDropdown when no region is selected', () => {
it('enables VpcDropdown when no region is selected', async () => {
state.selectedRegion = { name: 'west-1 ' };
return Vue.nextTick().then(() => {
expect(findVpcDropdown().props('disabled')).toBe(false);
});
await nextTick();
expect(findVpcDropdown().props('disabled')).toBe(false);
});
it('sets isLoadingVpcs to VpcDropdown loading property', () => {
it('sets isLoadingVpcs to VpcDropdown loading property', async () => {
vpcsState.isLoadingItems = true;
return Vue.nextTick().then(() => {
expect(findVpcDropdown().props('loading')).toBe(vpcsState.isLoadingItems);
});
await nextTick();
expect(findVpcDropdown().props('loading')).toBe(vpcsState.isLoadingItems);
});
it('sets vpcs to VpcDropdown items property', () => {
expect(findVpcDropdown().props('items')).toBe(vpcsState.items);
});
it('sets VpcDropdown hasErrors to true when loading vpcs fails', () => {
it('sets VpcDropdown hasErrors to true when loading vpcs fails', async () => {
vpcsState.loadingItemsError = new Error();
return Vue.nextTick().then(() => {
expect(findVpcDropdown().props('hasErrors')).toEqual(true);
});
await nextTick();
expect(findVpcDropdown().props('hasErrors')).toEqual(true);
});
it('disables SubnetDropdown when no vpc is selected', () => {
expect(findSubnetDropdown().props('disabled')).toBe(true);
});
it('enables SubnetDropdown when a vpc is selected', () => {
it('enables SubnetDropdown when a vpc is selected', async () => {
state.selectedVpc = { name: 'vpc-1 ' };
return Vue.nextTick().then(() => {
expect(findSubnetDropdown().props('disabled')).toBe(false);
});
await nextTick();
expect(findSubnetDropdown().props('disabled')).toBe(false);
});
it('sets isLoadingSubnets to SubnetDropdown loading property', () => {
it('sets isLoadingSubnets to SubnetDropdown loading property', async () => {
subnetsState.isLoadingItems = true;
return Vue.nextTick().then(() => {
expect(findSubnetDropdown().props('loading')).toBe(subnetsState.isLoadingItems);
});
await nextTick();
expect(findSubnetDropdown().props('loading')).toBe(subnetsState.isLoadingItems);
});
it('sets subnets to SubnetDropdown items property', () => {
@ -360,32 +350,29 @@ describe('EksClusterConfigurationForm', () => {
expect(findSecurityGroupDropdown().props('disabled')).toBe(true);
});
it('enables SecurityGroupDropdown when a vpc is selected', () => {
it('enables SecurityGroupDropdown when a vpc is selected', async () => {
state.selectedVpc = { name: 'vpc-1 ' };
return Vue.nextTick().then(() => {
expect(findSecurityGroupDropdown().props('disabled')).toBe(false);
});
await nextTick();
expect(findSecurityGroupDropdown().props('disabled')).toBe(false);
});
it('sets isLoadingSecurityGroups to SecurityGroupDropdown loading property', () => {
it('sets isLoadingSecurityGroups to SecurityGroupDropdown loading property', async () => {
securityGroupsState.isLoadingItems = true;
return Vue.nextTick().then(() => {
expect(findSecurityGroupDropdown().props('loading')).toBe(securityGroupsState.isLoadingItems);
});
await nextTick();
expect(findSecurityGroupDropdown().props('loading')).toBe(securityGroupsState.isLoadingItems);
});
it('sets securityGroups to SecurityGroupDropdown items property', () => {
expect(findSecurityGroupDropdown().props('items')).toBe(securityGroupsState.items);
});
it('sets SecurityGroupDropdown hasErrors to true when loading security groups fails', () => {
it('sets SecurityGroupDropdown hasErrors to true when loading security groups fails', async () => {
securityGroupsState.loadingItemsError = new Error();
return Vue.nextTick().then(() => {
expect(findSecurityGroupDropdown().props('hasErrors')).toEqual(true);
});
await nextTick();
expect(findSecurityGroupDropdown().props('hasErrors')).toEqual(true);
});
it('dispatches setClusterName when cluster name input changes', () => {

View File

@ -1,4 +1,4 @@
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import groupFolderComponent from '~/groups/components/group_folder.vue';
import groupItemComponent from '~/groups/components/group_item.vue';
@ -18,13 +18,13 @@ const createComponent = (groups = mockGroups, parentGroup = mockParentGroupItem)
describe('GroupFolderComponent', () => {
let vm;
beforeEach(() => {
beforeEach(async () => {
Vue.component('GroupItem', groupItemComponent);
vm = createComponent();
vm.$mount();
return Vue.nextTick();
await nextTick();
});
afterEach(() => {

View File

@ -1,4 +1,4 @@
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import commitSidebarList from '~/ide/components/commit_sidebar/list.vue';
import { createStore } from '~/ide/stores';
@ -31,12 +31,11 @@ describe('Multi-file editor commit sidebar list', () => {
});
describe('with a list of files', () => {
beforeEach((done) => {
beforeEach(async () => {
const f = file('file name');
f.changed = true;
vm.fileList.push(f);
Vue.nextTick(done);
await nextTick();
});
it('renders list', () => {

View File

@ -1,4 +1,4 @@
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import radioGroup from '~/ide/components/commit_sidebar/radio_group.vue';
import { createStore } from '~/ide/stores';
@ -7,7 +7,7 @@ describe('IDE commit sidebar radio group', () => {
let vm;
let store;
beforeEach((done) => {
beforeEach(async () => {
store = createStore();
const Component = Vue.extend(radioGroup);
@ -22,7 +22,7 @@ describe('IDE commit sidebar radio group', () => {
vm.$mount();
Vue.nextTick(done);
await nextTick();
});
afterEach(() => {
@ -33,7 +33,7 @@ describe('IDE commit sidebar radio group', () => {
expect(vm.$el.textContent).toContain('test');
});
it('uses slot if label is not present', (done) => {
it('uses slot if label is not present', async () => {
vm.$destroy();
vm = new Vue({
@ -47,25 +47,19 @@ describe('IDE commit sidebar radio group', () => {
vm.$mount();
Vue.nextTick(() => {
expect(vm.$el.textContent).toContain('Testing slot');
done();
});
await nextTick();
expect(vm.$el.textContent).toContain('Testing slot');
});
it('updates store when changing radio button', (done) => {
it('updates store when changing radio button', async () => {
vm.$el.querySelector('input').dispatchEvent(new Event('change'));
Vue.nextTick(() => {
expect(store.state.commit.commitAction).toBe('1');
done();
});
await nextTick();
expect(store.state.commit.commitAction).toBe('1');
});
describe('with input', () => {
beforeEach((done) => {
beforeEach(async () => {
vm.$destroy();
const Component = Vue.extend(radioGroup);
@ -82,32 +76,27 @@ describe('IDE commit sidebar radio group', () => {
vm.$mount();
Vue.nextTick(done);
await nextTick();
});
it('renders input box when commitAction matches value', () => {
expect(vm.$el.querySelector('.form-control')).not.toBeNull();
});
it('hides input when commitAction doesnt match value', (done) => {
it('hides input when commitAction doesnt match value', async () => {
store.state.commit.commitAction = '2';
Vue.nextTick(() => {
expect(vm.$el.querySelector('.form-control')).toBeNull();
done();
});
await nextTick();
expect(vm.$el.querySelector('.form-control')).toBeNull();
});
it('updates branch name in store on input', (done) => {
it('updates branch name in store on input', async () => {
const input = vm.$el.querySelector('.form-control');
input.value = 'testing-123';
input.dispatchEvent(new Event('input'));
Vue.nextTick(() => {
expect(store.state.commit.newBranchName).toBe('testing-123');
done();
});
await nextTick();
expect(store.state.commit.newBranchName).toBe('testing-123');
});
it('renders newBranchName if present', () => {

View File

@ -1,4 +1,4 @@
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import successMessage from '~/ide/components/commit_sidebar/success_message.vue';
import { createStore } from '~/ide/stores';
@ -23,13 +23,10 @@ describe('IDE commit panel successful commit state', () => {
vm.$destroy();
});
it('renders last commit message when it exists', (done) => {
it('renders last commit message when it exists', async () => {
vm.$store.state.lastCommitMsg = 'testing commit message';
Vue.nextTick(() => {
expect(vm.$el.textContent).toContain('testing commit message');
done();
});
await nextTick();
expect(vm.$el.textContent).toContain('testing commit message');
});
});

View File

@ -1,5 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
import { nextTick } from 'vue';
import eventHub from '~/ide/eventhub';
import { createRouter } from '~/ide/ide_router';
import service from '~/ide/services';
@ -68,7 +68,7 @@ describe('IDE store file actions', () => {
return store
.dispatch('closeFile', localFile)
.then(Vue.nextTick)
.then(nextTick)
.then(() => {
expect(store.state.openFiles.length).toBe(0);
expect(store.state.changedFiles.length).toBe(1);
@ -83,7 +83,7 @@ describe('IDE store file actions', () => {
return store
.dispatch('closeFile', localFile)
.then(Vue.nextTick)
.then(nextTick)
.then(() => {
expect(router.push).toHaveBeenCalledWith('/project/test/test/tree/main/-/newOpenFile/');
});

View File

@ -1,6 +1,6 @@
import { GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import { mockMilestone } from 'jest/boards/mock_data';
import IssueMilestone from '~/issuable/components/issue_milestone.vue';
@ -19,12 +19,12 @@ describe('IssueMilestoneComponent', () => {
let wrapper;
let vm;
beforeEach((done) => {
beforeEach(async () => {
wrapper = createComponent();
({ vm } = wrapper);
Vue.nextTick(done);
await nextTick();
});
afterEach(() => {
@ -37,7 +37,7 @@ describe('IssueMilestoneComponent', () => {
wrapper.setProps({
milestone: { ...mockMilestone, start_date: '' },
});
await wrapper.vm.$nextTick();
await nextTick();
expect(wrapper.vm.isMilestoneStarted).toBe(false);
});
@ -46,7 +46,7 @@ describe('IssueMilestoneComponent', () => {
await wrapper.setProps({
milestone: { ...mockMilestone, start_date: '1990-07-22' },
});
await wrapper.vm.$nextTick();
await nextTick();
expect(wrapper.vm.isMilestoneStarted).toBe(true);
});
@ -57,7 +57,7 @@ describe('IssueMilestoneComponent', () => {
wrapper.setProps({
milestone: { ...mockMilestone, due_date: '' },
});
await wrapper.vm.$nextTick();
await nextTick();
expect(wrapper.vm.isMilestonePastDue).toBe(false);
});
@ -80,7 +80,7 @@ describe('IssueMilestoneComponent', () => {
wrapper.setProps({
milestone: { ...mockMilestone, due_date: '' },
});
await wrapper.vm.$nextTick();
await nextTick();
expect(wrapper.vm.milestoneDatesAbsolute).toBe('(January 1, 2018)');
});
@ -89,7 +89,7 @@ describe('IssueMilestoneComponent', () => {
wrapper.setProps({
milestone: { ...mockMilestone, start_date: '', due_date: '' },
});
await wrapper.vm.$nextTick();
await nextTick();
expect(wrapper.vm.milestoneDatesAbsolute).toBe('');
});
@ -100,7 +100,7 @@ describe('IssueMilestoneComponent', () => {
wrapper.setProps({
milestone: { ...mockMilestone, due_date: `${new Date().getFullYear() + 10}-01-01` },
});
await wrapper.vm.$nextTick();
await nextTick();
expect(wrapper.vm.milestoneDatesHuman).toContain('years remaining');
});
@ -109,7 +109,7 @@ describe('IssueMilestoneComponent', () => {
wrapper.setProps({
milestone: { ...mockMilestone, start_date: '1990-07-22', due_date: '' },
});
await wrapper.vm.$nextTick();
await nextTick();
expect(wrapper.vm.milestoneDatesHuman).toContain('Started');
});
@ -122,7 +122,7 @@ describe('IssueMilestoneComponent', () => {
due_date: '',
},
});
await wrapper.vm.$nextTick();
await nextTick();
expect(wrapper.vm.milestoneDatesHuman).toContain('Starts');
});
@ -131,7 +131,7 @@ describe('IssueMilestoneComponent', () => {
wrapper.setProps({
milestone: { ...mockMilestone, start_date: '', due_date: '' },
});
await wrapper.vm.$nextTick();
await nextTick();
expect(wrapper.vm.milestoneDatesHuman).toBe('');
});

View File

@ -1,6 +1,6 @@
import { GlAlert, GlLabel } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import { nextTick } from 'vue';
import JiraIssuesImportStatus from '~/issues/list/components/jira_issues_import_status_app.vue';
describe('JiraIssuesImportStatus', () => {
@ -100,7 +100,7 @@ describe('JiraIssuesImportStatus', () => {
});
describe('alert message', () => {
it('is hidden when dismissed', () => {
it('is hidden when dismissed', async () => {
wrapper = mountComponent({
shouldShowInProgressAlert: true,
});
@ -109,9 +109,8 @@ describe('JiraIssuesImportStatus', () => {
findAlert().vm.$emit('dismiss');
return Vue.nextTick(() => {
expect(wrapper.find(GlAlert).exists()).toBe(false);
});
await nextTick();
expect(wrapper.find(GlAlert).exists()).toBe(false);
});
});
});

View File

@ -1,4 +1,4 @@
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import titleComponent from '~/issues/show/components/title.vue';
import eventHub from '~/issues/show/event_hub';
import Store from '~/issues/show/stores';
@ -29,36 +29,33 @@ describe('Title component', () => {
expect(vm.$el.querySelector('.title').innerHTML.trim()).toBe('Testing <img>');
});
it('updates page title when changing titleHtml', () => {
it('updates page title when changing titleHtml', async () => {
const spy = jest.spyOn(vm, 'setPageTitle');
vm.titleHtml = 'test';
return vm.$nextTick().then(() => {
expect(spy).toHaveBeenCalled();
});
await nextTick();
expect(spy).toHaveBeenCalled();
});
it('animates title changes', () => {
it('animates title changes', async () => {
vm.titleHtml = 'test';
return vm
.$nextTick()
.then(() => {
expect(vm.$el.querySelector('.title').classList).toContain('issue-realtime-pre-pulse');
jest.runAllTimers();
return vm.$nextTick();
})
.then(() => {
expect(vm.$el.querySelector('.title').classList).toContain('issue-realtime-trigger-pulse');
});
await nextTick();
expect(vm.$el.querySelector('.title').classList).toContain('issue-realtime-pre-pulse');
jest.runAllTimers();
await nextTick();
expect(vm.$el.querySelector('.title').classList).toContain('issue-realtime-trigger-pulse');
});
it('updates page title after changing title', () => {
it('updates page title after changing title', async () => {
vm.titleHtml = 'changed';
vm.titleText = 'changed';
return vm.$nextTick().then(() => {
expect(document.querySelector('title').textContent.trim()).toContain('changed');
});
await nextTick();
expect(document.querySelector('title').textContent.trim()).toContain('changed');
});
describe('inline edit button', () => {
@ -80,16 +77,15 @@ describe('Title component', () => {
expect(vm.$el.querySelector('.btn-edit')).toBeDefined();
});
it('should trigger open.form event when clicked', () => {
it('should trigger open.form event when clicked', async () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
vm.showInlineEditButton = true;
vm.canUpdate = true;
Vue.nextTick(() => {
vm.$el.querySelector('.btn-edit').click();
await nextTick();
vm.$el.querySelector('.btn-edit').click();
expect(eventHub.$emit).toHaveBeenCalledWith('open.form');
});
expect(eventHub.$emit).toHaveBeenCalledWith('open.form');
});
});
});

View File

@ -1,6 +1,6 @@
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import { nextTick } from 'vue';
import JiraImportApp from '~/jira_import/components/jira_import_app.vue';
import JiraImportForm from '~/jira_import/components/jira_import_form.vue';
import JiraImportProgress from '~/jira_import/components/jira_import_progress.vue';
@ -230,7 +230,7 @@ describe('JiraImportApp', () => {
getFormComponent().vm.$emit('error', 'There was an error');
await Vue.nextTick();
await nextTick();
expect(getAlert().exists()).toBe(true);
});
@ -248,7 +248,7 @@ describe('JiraImportApp', () => {
getAlert().vm.$emit('dismiss');
await Vue.nextTick();
await nextTick();
expect(getAlert().exists()).toBe(false);
});

View File

@ -1,6 +1,6 @@
import { mount, createWrapper } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
import { nextTick } from 'vue';
import { TEST_HOST } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
@ -76,15 +76,14 @@ describe('noteActions', () => {
expect(findUserAccessRoleBadgeText(1)).toBe(props.accessLevel);
});
it('should render contributor badge', () => {
it('should render contributor badge', async () => {
wrapper.setProps({
accessLevel: null,
isContributor: true,
});
return wrapper.vm.$nextTick().then(() => {
expect(findUserAccessRoleBadgeText(1)).toBe('Contributor');
});
await nextTick();
expect(findUserAccessRoleBadgeText(1)).toBe('Contributor');
});
it('should render emoji link', () => {
@ -105,7 +104,7 @@ describe('noteActions', () => {
expect(wrapper.find('.js-btn-copy-note-link').exists()).toBe(true);
});
it('should not show copy link action when `noteUrl` prop is empty', (done) => {
it('should not show copy link action when `noteUrl` prop is empty', async () => {
wrapper.setProps({
...props,
author: {
@ -119,30 +118,23 @@ describe('noteActions', () => {
noteUrl: '',
});
Vue.nextTick()
.then(() => {
expect(wrapper.find('.js-btn-copy-note-link').exists()).toBe(false);
})
.then(done)
.catch(done.fail);
await nextTick();
expect(wrapper.find('.js-btn-copy-note-link').exists()).toBe(false);
});
it('should be possible to delete comment', () => {
expect(wrapper.find('.js-note-delete').exists()).toBe(true);
});
it('closes tooltip when dropdown opens', (done) => {
it('closes tooltip when dropdown opens', async () => {
wrapper.find('.more-actions-toggle').trigger('click');
const rootWrapper = createWrapper(wrapper.vm.$root);
Vue.nextTick()
.then(() => {
const emitted = Object.keys(rootWrapper.emitted());
expect(emitted).toEqual([BV_HIDE_TOOLTIP]);
done();
})
.catch(done.fail);
await nextTick();
const emitted = Object.keys(rootWrapper.emitted());
expect(emitted).toEqual([BV_HIDE_TOOLTIP]);
});
it('should not be possible to assign or unassign the comment author in a merge request', () => {

View File

@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { suggestionCommitMessage } from '~/diffs/store/getters';
@ -46,9 +46,9 @@ describe('issue_note_body component', () => {
});
describe('isEditing', () => {
beforeEach((done) => {
beforeEach(async () => {
vm.isEditing = true;
Vue.nextTick(done);
await nextTick();
});
it('renders edit form', () => {

View File

@ -1,7 +1,7 @@
import { mount, shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import Vue from 'vue';
import { nextTick } from 'vue';
import setWindowLocation from 'helpers/set_window_location_helper';
import { setTestTimeout } from 'helpers/timeout';
import waitForPromises from 'helpers/wait_for_promises';
@ -294,24 +294,22 @@ describe('note_app', () => {
return waitForDiscussionsRequest();
});
it('should render markdown docs url', () => {
it('should render markdown docs url', async () => {
wrapper.find('.js-note-edit').trigger('click');
const { markdownDocsPath } = mockData.notesDataMock;
return Vue.nextTick().then(() => {
expect(wrapper.find(`.edit-note a[href="${markdownDocsPath}"]`).text().trim()).toEqual(
'Markdown is supported',
);
});
await nextTick();
expect(wrapper.find(`.edit-note a[href="${markdownDocsPath}"]`).text().trim()).toEqual(
'Markdown is supported',
);
});
it('should not render quick actions docs url', () => {
it('should not render quick actions docs url', async () => {
wrapper.find('.js-note-edit').trigger('click');
const { quickActionsDocsPath } = mockData.notesDataMock;
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(`.edit-note a[href="${quickActionsDocsPath}"]`).exists()).toBe(false);
});
await nextTick();
expect(wrapper.find(`.edit-note a[href="${quickActionsDocsPath}"]`).exists()).toBe(false);
});
});

View File

@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils';
import { merge } from 'lodash';
import Vue from 'vue';
import { nextTick } from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import deleteAccountModal from '~/profile/account/components/delete_account_modal.vue';
@ -56,7 +56,7 @@ describe('DeleteAccountModal component', () => {
const findModal = () => wrapper.find(GlModalStub);
describe('with password confirmation', () => {
beforeEach((done) => {
beforeEach(async () => {
createWrapper({
propsData: {
confirmWithPassword: true,
@ -65,48 +65,40 @@ describe('DeleteAccountModal component', () => {
vm.isOpen = true;
Vue.nextTick().then(done).catch(done.fail);
await nextTick();
});
it('does not accept empty password', (done) => {
it('does not accept empty password', async () => {
const { form, input } = findElements();
jest.spyOn(form, 'submit').mockImplementation(() => {});
input.value = '';
input.dispatchEvent(new Event('input'));
Vue.nextTick()
.then(() => {
expect(vm.enteredPassword).toBe(input.value);
expect(findModal().attributes('ok-disabled')).toBe('true');
findModal().vm.$emit('primary');
await nextTick();
expect(vm.enteredPassword).toBe(input.value);
expect(findModal().attributes('ok-disabled')).toBe('true');
findModal().vm.$emit('primary');
expect(form.submit).not.toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
expect(form.submit).not.toHaveBeenCalled();
});
it('submits form with password', (done) => {
it('submits form with password', async () => {
const { form, input } = findElements();
jest.spyOn(form, 'submit').mockImplementation(() => {});
input.value = 'anything';
input.dispatchEvent(new Event('input'));
Vue.nextTick()
.then(() => {
expect(vm.enteredPassword).toBe(input.value);
expect(findModal().attributes('ok-disabled')).toBeUndefined();
findModal().vm.$emit('primary');
await nextTick();
expect(vm.enteredPassword).toBe(input.value);
expect(findModal().attributes('ok-disabled')).toBeUndefined();
findModal().vm.$emit('primary');
expect(form.submit).toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
expect(form.submit).toHaveBeenCalled();
});
});
describe('with username confirmation', () => {
beforeEach((done) => {
beforeEach(async () => {
createWrapper({
propsData: {
confirmWithPassword: false,
@ -115,43 +107,35 @@ describe('DeleteAccountModal component', () => {
vm.isOpen = true;
Vue.nextTick().then(done).catch(done.fail);
await nextTick();
});
it('does not accept wrong username', (done) => {
it('does not accept wrong username', async () => {
const { form, input } = findElements();
jest.spyOn(form, 'submit').mockImplementation(() => {});
input.value = 'this is wrong';
input.dispatchEvent(new Event('input'));
Vue.nextTick()
.then(() => {
expect(vm.enteredUsername).toBe(input.value);
expect(findModal().attributes('ok-disabled')).toBe('true');
findModal().vm.$emit('primary');
await nextTick();
expect(vm.enteredUsername).toBe(input.value);
expect(findModal().attributes('ok-disabled')).toBe('true');
findModal().vm.$emit('primary');
expect(form.submit).not.toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
expect(form.submit).not.toHaveBeenCalled();
});
it('submits form with correct username', (done) => {
it('submits form with correct username', async () => {
const { form, input } = findElements();
jest.spyOn(form, 'submit').mockImplementation(() => {});
input.value = username;
input.dispatchEvent(new Event('input'));
Vue.nextTick()
.then(() => {
expect(vm.enteredUsername).toBe(input.value);
expect(findModal().attributes('ok-disabled')).toBeUndefined();
findModal().vm.$emit('primary');
await nextTick();
expect(vm.enteredUsername).toBe(input.value);
expect(findModal().attributes('ok-disabled')).toBeUndefined();
findModal().vm.$emit('primary');
expect(form.submit).toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
expect(form.submit).toHaveBeenCalled();
});
});
});

View File

@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import mountComponent, { mountComponentWithSlots } from 'helpers/vue_mount_component_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import reportSection from '~/reports/components/report_section.vue';
@ -71,16 +71,12 @@ describe('Report section', () => {
const issues = hasIssues ? 'has issues' : 'has no issues';
const open = alwaysOpen ? 'is always open' : 'is not always open';
it(`is ${isCollapsible}, if the report ${issues} and ${open}`, (done) => {
it(`is ${isCollapsible}, if the report ${issues} and ${open}`, async () => {
vm.hasIssues = hasIssues;
vm.alwaysOpen = alwaysOpen;
Vue.nextTick()
.then(() => {
expect(vm.isCollapsible).toBe(isCollapsible);
})
.then(done)
.catch(done.fail);
await nextTick();
expect(vm.isCollapsible).toBe(isCollapsible);
});
});
});
@ -97,16 +93,12 @@ describe('Report section', () => {
const issues = isCollapsed ? 'is collapsed' : 'is not collapsed';
const open = alwaysOpen ? 'is always open' : 'is not always open';
it(`is ${isExpanded}, if the report ${issues} and ${open}`, (done) => {
it(`is ${isExpanded}, if the report ${issues} and ${open}`, async () => {
vm.isCollapsed = isCollapsed;
vm.alwaysOpen = alwaysOpen;
Vue.nextTick()
.then(() => {
expect(vm.isExpanded).toBe(isExpanded);
})
.then(done)
.catch(done.fail);
await nextTick();
expect(vm.isExpanded).toBe(isExpanded);
});
});
});
@ -148,79 +140,55 @@ describe('Report section', () => {
describe('toggleCollapsed', () => {
const hiddenCss = { display: 'none' };
it('toggles issues', (done) => {
it('toggles issues', async () => {
vm.$el.querySelector('button').click();
Vue.nextTick()
.then(() => {
expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss);
expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Collapse');
await nextTick();
expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss);
expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Collapse');
vm.$el.querySelector('button').click();
})
.then(Vue.nextTick)
.then(() => {
expect(vm.$el.querySelector('.js-report-section-container')).toHaveCss(hiddenCss);
expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Expand');
})
.then(done)
.catch(done.fail);
vm.$el.querySelector('button').click();
await nextTick();
expect(vm.$el.querySelector('.js-report-section-container')).toHaveCss(hiddenCss);
expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Expand');
});
it('is always expanded, if always-open is set to true', (done) => {
it('is always expanded, if always-open is set to true', async () => {
vm.alwaysOpen = true;
Vue.nextTick()
.then(() => {
expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss);
expect(vm.$el.querySelector('button')).toBeNull();
})
.then(done)
.catch(done.fail);
await nextTick();
expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss);
expect(vm.$el.querySelector('button')).toBeNull();
});
});
});
describe('snowplow events', () => {
it('does emit an event on issue toggle if the shouldEmitToggleEvent prop does exist', (done) => {
it('does emit an event on issue toggle if the shouldEmitToggleEvent prop does exist', async () => {
createComponent({ hasIssues: true, shouldEmitToggleEvent: true });
expect(wrapper.emitted().toggleEvent).toBeUndefined();
findCollapseButton().trigger('click');
return wrapper.vm
.$nextTick()
.then(() => {
expect(wrapper.emitted().toggleEvent).toHaveLength(1);
})
.then(done)
.catch(done.fail);
await nextTick();
expect(wrapper.emitted().toggleEvent).toHaveLength(1);
});
it('does not emit an event on issue toggle if the shouldEmitToggleEvent prop does not exist', (done) => {
it('does not emit an event on issue toggle if the shouldEmitToggleEvent prop does not exist', async () => {
createComponent({ hasIssues: true });
expect(wrapper.emitted().toggleEvent).toBeUndefined();
findCollapseButton().trigger('click');
return wrapper.vm
.$nextTick()
.then(() => {
expect(wrapper.emitted().toggleEvent).toBeUndefined();
})
.then(done)
.catch(done.fail);
await nextTick();
expect(wrapper.emitted().toggleEvent).toBeUndefined();
});
it('does not emit an event if always-open is set to true', (done) => {
it('does not emit an event if always-open is set to true', async () => {
createComponent({ alwaysOpen: true, hasIssues: true, shouldEmitToggleEvent: true });
wrapper.vm
.$nextTick()
.then(() => {
expect(wrapper.emitted().toggleEvent).toBeUndefined();
})
.then(done)
.catch(done.fail);
await nextTick();
expect(wrapper.emitted().toggleEvent).toBeUndefined();
});
});

View File

@ -1,6 +1,6 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import { nextTick } from 'vue';
import Participants from '~/sidebar/components/participants/participants.vue';
const PARTICIPANT = {
@ -77,7 +77,7 @@ describe('Participants', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
it('when only showing visible participants, shows an avatar only for each participant under the limit', () => {
it('when only showing visible participants, shows an avatar only for each participant under the limit', async () => {
const numberOfLessParticipants = 2;
wrapper = mountComponent({
loading: false,
@ -91,12 +91,11 @@ describe('Participants', () => {
isShowingMoreParticipants: false,
});
return Vue.nextTick().then(() => {
expect(wrapper.findAll('.participants-author')).toHaveLength(numberOfLessParticipants);
});
await nextTick();
expect(wrapper.findAll('.participants-author')).toHaveLength(numberOfLessParticipants);
});
it('when only showing all participants, each has an avatar', () => {
it('when only showing all participants, each has an avatar', async () => {
wrapper = mountComponent({
loading: false,
participants: PARTICIPANT_LIST,
@ -109,9 +108,8 @@ describe('Participants', () => {
isShowingMoreParticipants: true,
});
return Vue.nextTick().then(() => {
expect(wrapper.findAll('.participants-author')).toHaveLength(PARTICIPANT_LIST.length);
});
await nextTick();
expect(wrapper.findAll('.participants-author')).toHaveLength(PARTICIPANT_LIST.length);
});
it('does not have more participants link when they can all be shown', () => {
@ -126,7 +124,7 @@ describe('Participants', () => {
expect(getMoreParticipantsButton().exists()).toBe(false);
});
it('when too many participants, has more participants link to show more', () => {
it('when too many participants, has more participants link to show more', async () => {
wrapper = mountComponent({
loading: false,
participants: PARTICIPANT_LIST,
@ -139,12 +137,11 @@ describe('Participants', () => {
isShowingMoreParticipants: false,
});
return Vue.nextTick().then(() => {
expect(getMoreParticipantsButton().text()).toBe('+ 1 more');
});
await nextTick();
expect(getMoreParticipantsButton().text()).toBe('+ 1 more');
});
it('when too many participants and already showing them, has more participants link to show less', () => {
it('when too many participants and already showing them, has more participants link to show less', async () => {
wrapper = mountComponent({
loading: false,
participants: PARTICIPANT_LIST,
@ -157,9 +154,8 @@ describe('Participants', () => {
isShowingMoreParticipants: true,
});
return Vue.nextTick().then(() => {
expect(getMoreParticipantsButton().text()).toBe('- show less');
});
await nextTick();
expect(getMoreParticipantsButton().text()).toBe('- show less');
});
it('clicking more participants link emits event', () => {
@ -176,7 +172,7 @@ describe('Participants', () => {
expect(wrapper.vm.isShowingMoreParticipants).toBe(true);
});
it('clicking on participants icon emits `toggleSidebar` event', () => {
it('clicking on participants icon emits `toggleSidebar` event', async () => {
wrapper = mountComponent({
loading: false,
participants: PARTICIPANT_LIST,
@ -187,11 +183,9 @@ describe('Participants', () => {
wrapper.find('.sidebar-collapsed-icon').trigger('click');
return Vue.nextTick(() => {
expect(spy).toHaveBeenCalledWith('toggleSidebar');
spy.mockRestore();
});
await nextTick();
expect(spy).toHaveBeenCalledWith('toggleSidebar');
spy.mockRestore();
});
});

View File

@ -1,7 +1,7 @@
import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import { within } from '@testing-library/dom';
import { mount, createWrapper } from '@vue/test-utils';
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
@ -82,7 +82,7 @@ describe('~/user_lists/components/user_lists.vue', () => {
factory();
await waitForPromises();
await Vue.nextTick();
await nextTick();
emptyState = wrapper.findComponent(GlEmptyState);
});
@ -130,7 +130,7 @@ describe('~/user_lists/components/user_lists.vue', () => {
factory();
jest.spyOn(store, 'dispatch');
await Vue.nextTick();
await nextTick();
table = wrapper.findComponent(UserListsTable);
});
@ -171,7 +171,7 @@ describe('~/user_lists/components/user_lists.vue', () => {
Api.fetchFeatureFlagUserLists.mockRejectedValue();
factory();
await Vue.nextTick();
await nextTick();
});
it('should render error state', () => {

View File

@ -1,4 +1,4 @@
import Vue from 'vue';
import { nextTick } from 'vue';
import { setHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'helpers/test_constants';
import initVueAlerts from '~/vue_alerts';
@ -75,10 +75,9 @@ describe('VueAlerts', () => {
});
describe('when dismissed', () => {
beforeEach(() => {
beforeEach(async () => {
findAlertDismiss(findAlerts()[0]).click();
return Vue.nextTick();
await nextTick();
});
it('hides the alert', () => {

View File

@ -1,6 +1,6 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import MemoryUsage from '~/vue_merge_request_widget/components/deployment/memory_usage.vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
@ -184,7 +184,7 @@ describe('MemoryUsage', () => {
vm.hasMetrics = false;
vm.loadFailed = false;
Vue.nextTick(() => {
nextTick(() => {
expect(el.querySelector('.js-usage-info.usage-info-loading')).toBeDefined();
expect(el.querySelector('.js-usage-info .usage-info-load-spinner')).toBeDefined();
@ -203,7 +203,7 @@ describe('MemoryUsage', () => {
vm.loadFailed = false;
vm.memoryMetrics = metricsMockData.metrics.memory_values[0].values;
Vue.nextTick(() => {
nextTick(() => {
expect(el.querySelector('.memory-graph-container')).toBeDefined();
expect(el.querySelector('.js-usage-info').innerText).toContain(messages.hasMetrics);
done();
@ -215,7 +215,7 @@ describe('MemoryUsage', () => {
vm.hasMetrics = false;
vm.loadFailed = true;
Vue.nextTick(() => {
nextTick(() => {
expect(el.querySelector('.js-usage-info.usage-info-failed')).toBeDefined();
expect(el.querySelector('.js-usage-info').innerText).toContain(messages.loadFailed);
@ -228,7 +228,7 @@ describe('MemoryUsage', () => {
vm.hasMetrics = false;
vm.loadFailed = false;
Vue.nextTick(() => {
nextTick(() => {
expect(el.querySelector('.js-usage-info.usage-info-unavailable')).toBeDefined();
expect(el.querySelector('.js-usage-info').innerText).toContain(messages.metricsUnavailable);

View File

@ -1,5 +1,5 @@
import { getByRole } from '@testing-library/dom';
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import { OPEN_REVERT_MODAL, OPEN_CHERRY_PICK_MODAL } from '~/projects/commit/constants';
import modalEventHub from '~/projects/commit/event_hub';
@ -200,7 +200,7 @@ describe('MRWidgetMerged', () => {
it('hides button to copy commit SHA if SHA does not exist', (done) => {
vm.mr.mergeCommitSha = null;
Vue.nextTick(() => {
nextTick(() => {
expect(selectors.copyMergeShaButton).toBe(null);
expect(vm.$el.querySelector('.mr-info-list').innerText).not.toContain('with');
done();
@ -216,7 +216,7 @@ describe('MRWidgetMerged', () => {
it('should not show source branch deleted text', (done) => {
vm.mr.sourceBranchRemoved = false;
Vue.nextTick(() => {
nextTick(() => {
expect(vm.$el.innerText).not.toContain('The source branch has been deleted');
done();
});
@ -226,7 +226,7 @@ describe('MRWidgetMerged', () => {
vm.mr.isRemovingSourceBranch = true;
vm.mr.sourceBranchRemoved = false;
Vue.nextTick(() => {
nextTick(() => {
expect(vm.$el.innerText).toContain('The source branch is being deleted');
expect(vm.$el.innerText).not.toContain('The source branch has been deleted');
done();

View File

@ -1,4 +1,4 @@
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import NothingToMerge from '~/vue_merge_request_widget/components/states/nothing_to_merge.vue';
describe('NothingToMerge', () => {
@ -20,7 +20,7 @@ describe('NothingToMerge', () => {
it('should not show new blob link if there is no link available', () => {
vm.mr.newBlobPath = null;
Vue.nextTick(() => {
nextTick(() => {
expect(vm.$el.querySelector('[data-testid="createFileButton"]')).toEqual(null);
});
});

View File

@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import { nextTick } from 'vue';
import { GlSprintf } from '@gitlab/ui';
import simplePoll from '~/lib/utils/simple_poll';
import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue';
@ -196,7 +196,7 @@ describe('ReadyToMerge', () => {
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ isMergingImmediately: true });
await Vue.nextTick();
await nextTick();
expect(wrapper.vm.mergeButtonText).toEqual('Merge in progress');
});
@ -266,7 +266,7 @@ describe('ReadyToMerge', () => {
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ isMakingRequest: true });
await Vue.nextTick();
await nextTick();
expect(wrapper.vm.isMergeButtonDisabled).toBe(true);
});

View File

@ -1,4 +1,4 @@
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import WorkInProgress from '~/vue_merge_request_widget/components/states/work_in_progress.vue';
import toast from '~/vue_shared/plugins/global_toast';
import eventHub from '~/vue_merge_request_widget/event_hub';
@ -94,7 +94,7 @@ describe('Wip', () => {
it('should not show removeWIP button is user cannot update MR', (done) => {
vm.mr.removeWIPPath = '';
Vue.nextTick(() => {
nextTick(() => {
expect(el.querySelector('.js-remove-draft')).toEqual(null);
done();
});

View File

@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
import Vue from 'vue';
import { nextTick } from 'vue';
import ExpandButton from '~/vue_shared/components/expand_button.vue';
const text = {
@ -66,9 +66,9 @@ describe('Expand button', () => {
});
describe('on click', () => {
beforeEach((done) => {
beforeEach(async () => {
expanderPrependEl().trigger('click');
Vue.nextTick(done);
await nextTick();
});
afterEach(() => {
@ -85,7 +85,7 @@ describe('Expand button', () => {
});
describe('when short text is provided', () => {
beforeEach((done) => {
beforeEach(async () => {
factory({
slots: {
expanded: `<p>${text.expanded}</p>`,
@ -94,7 +94,7 @@ describe('Expand button', () => {
});
expanderPrependEl().trigger('click');
Vue.nextTick(done);
await nextTick();
});
it('only renders expanded text', () => {
@ -110,31 +110,29 @@ describe('Expand button', () => {
});
describe('append button', () => {
beforeEach((done) => {
beforeEach(async () => {
expanderPrependEl().trigger('click');
Vue.nextTick(done);
await nextTick();
});
it('clicking hides itself and shows prepend', () => {
it('clicking hides itself and shows prepend', async () => {
expect(expanderAppendEl().isVisible()).toBe(true);
expanderAppendEl().trigger('click');
return wrapper.vm.$nextTick().then(() => {
expect(expanderPrependEl().isVisible()).toBe(true);
});
await nextTick();
expect(expanderPrependEl().isVisible()).toBe(true);
});
it('clicking hides expanded text', () => {
it('clicking hides expanded text', async () => {
expect(wrapper.find(ExpandButton).text().trim()).toBe(text.expanded);
expanderAppendEl().trigger('click');
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(ExpandButton).text().trim()).not.toBe(text.expanded);
});
await nextTick();
expect(wrapper.find(ExpandButton).text().trim()).not.toBe(text.expanded);
});
describe('when short text is provided', () => {
beforeEach((done) => {
beforeEach(async () => {
factory({
slots: {
expanded: `<p>${text.expanded}</p>`,
@ -143,16 +141,15 @@ describe('Expand button', () => {
});
expanderPrependEl().trigger('click');
Vue.nextTick(done);
await nextTick();
});
it('clicking reveals short text', () => {
it('clicking reveals short text', async () => {
expect(wrapper.find(ExpandButton).text().trim()).toBe(text.expanded);
expanderAppendEl().trigger('click');
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(ExpandButton).text().trim()).toBe(text.short);
});
await nextTick();
expect(wrapper.find(ExpandButton).text().trim()).toBe(text.short);
});
});
});

View File

@ -1,4 +1,4 @@
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
@ -17,38 +17,34 @@ describe('GlCountdown', () => {
});
describe('when there is time remaining', () => {
beforeEach((done) => {
beforeEach(async () => {
vm = mountComponent(Component, {
endDateString: '2000-01-01T01:02:03Z',
});
Vue.nextTick().then(done).catch(done.fail);
await nextTick();
});
it('displays remaining time', () => {
expect(vm.$el.textContent).toContain('01:02:03');
});
it('updates remaining time', (done) => {
it('updates remaining time', async () => {
now = '2000-01-01T00:00:01Z';
jest.advanceTimersByTime(1000);
Vue.nextTick()
.then(() => {
expect(vm.$el.textContent).toContain('01:02:02');
done();
})
.catch(done.fail);
await nextTick();
expect(vm.$el.textContent).toContain('01:02:02');
});
});
describe('when there is no time remaining', () => {
beforeEach((done) => {
beforeEach(async () => {
vm = mountComponent(Component, {
endDateString: '1900-01-01T00:00:00Z',
});
Vue.nextTick().then(done).catch(done.fail);
await nextTick();
});
it('displays 00:00:00', () => {

View File

@ -1,4 +1,4 @@
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import SuggestionsComponent from '~/vue_shared/components/markdown/suggestions.vue';
const MOCK_DATA = {
@ -51,7 +51,7 @@ describe('Suggestion component', () => {
let vm;
let diffTable;
beforeEach((done) => {
beforeEach(async () => {
const Component = Vue.extend(SuggestionsComponent);
vm = new Component({
@ -62,7 +62,7 @@ describe('Suggestion component', () => {
jest.spyOn(vm, 'renderSuggestions').mockImplementation(() => {});
vm.renderSuggestions();
Vue.nextTick(done);
await nextTick();
});
describe('mounted', () => {

View File

@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils';
import $ from 'jquery';
import Vue from 'vue';
import { nextTick } from 'vue';
import ResizableChartContainer from '~/vue_shared/components/resizable_chart/resizable_chart_container.vue';
jest.mock('~/lib/utils/common_utils', () => ({
@ -35,7 +35,7 @@ describe('Resizable Chart Container', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('updates the slot width and height props', () => {
it('updates the slot width and height props', async () => {
const width = 1920;
const height = 1080;
@ -44,13 +44,12 @@ describe('Resizable Chart Container', () => {
$(document).trigger('content.resize');
return Vue.nextTick().then(() => {
const widthNode = wrapper.find('.slot > .width');
const heightNode = wrapper.find('.slot > .height');
await nextTick();
const widthNode = wrapper.find('.slot > .width');
const heightNode = wrapper.find('.slot > .height');
expect(parseInt(widthNode.text(), 10)).toEqual(width);
expect(parseInt(heightNode.text(), 10)).toEqual(height);
});
expect(parseInt(widthNode.text(), 10)).toEqual(width);
expect(parseInt(heightNode.text(), 10)).toEqual(height);
});
it('calls onResize on manual resize', () => {

View File

@ -894,4 +894,15 @@ RSpec.describe Member do
end
end
end
describe '#set_member_namespace_id' do
let(:group) { create(:group) }
let(:member) { create(:group_member, group: group) }
describe 'on create' do
it 'sets the member_namespace_id' do
expect(member.member_namespace_id).to eq group.id
end
end
end
end

View File

@ -257,4 +257,15 @@ RSpec.describe ProjectMember do
it_behaves_like 'calls AuthorizedProjectUpdate::UserRefreshFromReplicaWorker with a delay to update project authorizations'
end
end
describe '#set_member_namespace_id' do
let(:project) { create(:project) }
let(:member) { create(:project_member, project: project) }
context 'on create' do
it 'sets the member_namespace_id' do
expect(member.member_namespace_id).to eq project.project_namespace_id
end
end
end
end

View File

@ -55,7 +55,7 @@ RSpec.describe StateNote do
it 'contains the expected values' do
expect(subject.author).to eq(author)
expect(subject.created_at).to eq(event.created_at)
expect(subject.note).to eq('resolved the corresponding error and closed the issue.')
expect(subject.note).to eq('resolved the corresponding error and closed the issue')
end
end
@ -65,7 +65,7 @@ RSpec.describe StateNote do
it 'contains the expected values' do
expect(subject.author).to eq(author)
expect(subject.created_at).to eq(event.created_at)
expect(subject.note).to eq('automatically closed this issue because the alert resolved.')
expect(subject.note).to eq('automatically closed this incident because the alert resolved')
end
end
end

View File

@ -118,7 +118,7 @@ RSpec.describe Issues::CloseService do
expect { service.execute(issue) }.to change { issue.notes.count }.by(1)
new_note = issue.notes.last
expect(new_note.note).to eq('changed the status to **Resolved** by closing the incident')
expect(new_note.note).to eq('changed the incident status to **Resolved** by closing the incident')
expect(new_note.author).to eq(user)
end
@ -334,8 +334,12 @@ RSpec.describe Issues::CloseService do
let!(:alert) { create(:alert_management_alert, issue: issue, project: project) }
it 'resolves an alert and sends a system note' do
expect_next_instance_of(SystemNotes::AlertManagementService) do |notes_service|
expect(notes_service).to receive(:closed_alert_issue).with(issue)
expect_any_instance_of(SystemNoteService) do |notes_service|
expect(notes_service).to receive(:change_alert_status).with(
alert,
current_user,
" by closing issue #{issue.to_reference(project)}"
)
end
close_issue

View File

@ -93,11 +93,23 @@ RSpec.describe Projects::ImportExport::ExportService do
end
it 'saves the project in the file system' do
expect(Gitlab::ImportExport::Saver).to receive(:save).with(exportable: project, shared: shared)
expect(Gitlab::ImportExport::Saver).to receive(:save).with(exportable: project, shared: shared).and_return(true)
service.execute
end
context 'when the upload fails' do
before do
expect(Gitlab::ImportExport::Saver).to receive(:save).with(exportable: project, shared: shared).and_return(false)
end
it 'notifies the user of an error' do
expect(service).to receive(:notify_error).and_call_original
expect { service.execute }.to raise_error(Gitlab::ImportExport::Error)
end
end
it 'calls the after export strategy' do
expect(after_export_strategy).to receive(:execute)
@ -107,6 +119,7 @@ RSpec.describe Projects::ImportExport::ExportService do
context 'when after export strategy fails' do
before do
allow(after_export_strategy).to receive(:execute).and_return(false)
expect(Gitlab::ImportExport::Saver).to receive(:save).with(exportable: project, shared: shared).and_return(true)
end
after do

View File

@ -632,18 +632,6 @@ RSpec.describe SystemNoteService do
end
end
describe '.resolve_incident_status' do
let(:incident) { build(:incident, :closed) }
it 'calls IncidentService' do
expect_next_instance_of(SystemNotes::IncidentService) do |service|
expect(service).to receive(:resolve_incident_status)
end
described_class.resolve_incident_status(incident, author)
end
end
describe '.change_incident_status' do
let(:incident) { instance_double('Issue', project: project) }

View File

@ -54,21 +54,7 @@ RSpec.describe ::SystemNotes::AlertManagementService do
end
it 'has the appropriate message' do
expect(subject.note).to eq("created issue #{issue.to_reference(project)} for this alert")
end
end
describe '#closed_alert_issue' do
let_it_be(:issue) { noteable.issue }
subject { described_class.new(noteable: noteable, project: project, author: author).closed_alert_issue(issue) }
it_behaves_like 'a system note' do
let(:action) { 'status' }
end
it 'has the appropriate message' do
expect(subject.note).to eq("changed the status to **Resolved** by closing issue #{issue.to_reference(project)}")
expect(subject.note).to eq("created incident #{issue.to_reference(project)} for this alert")
end
end

View File

@ -57,16 +57,6 @@ RSpec.describe ::SystemNotes::IncidentService do
end
end
describe '#resolve_incident_status' do
subject(:resolve_incident_status) { described_class.new(noteable: noteable, project: project, author: author).resolve_incident_status }
it 'creates a new note about resolved incident', :aggregate_failures do
expect { resolve_incident_status }.to change { noteable.notes.count }.by(1)
expect(noteable.notes.last.note).to eq('changed the status to **Resolved** by closing the incident')
end
end
describe '#change_incident_status' do
let_it_be(:escalation_status) { create(:incident_management_issuable_escalation_status, issue: noteable) }

View File

@ -462,14 +462,6 @@ RSpec.configure do |config|
$stdout = STDOUT
end
config.around(:each, stubbing_settings_source: true) do |example|
original_instance = ::Settings.instance_variable_get(:@instance)
example.run
::Settings.instance_variable_set(:@instance, original_instance)
end
config.disable_monkey_patching!
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
RSpec.configure do |config|
config.around(:each, stub_settings_source: true) do |example|
original_instance = ::Settings.instance_variable_get(:@instance)
example.run
::Settings.instance_variable_set(:@instance, original_instance)
end
end