Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
4223ed2e83
commit
2db9c1eee2
53 changed files with 666 additions and 358 deletions
|
@ -68,6 +68,7 @@ export default {
|
|||
v-gl-modal.deploy-freeze-modal
|
||||
icon="pencil"
|
||||
data-testid="edit-deploy-freeze"
|
||||
:aria-label="__('Edit deploy freeze')"
|
||||
@click="setFreezePeriod(item)"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
<script>
|
||||
import { GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui';
|
||||
import permissionsQuery from 'shared_queries/design_management/design_permissions.query.graphql';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
import { DESIGNS_ROUTE_NAME } from '../../router/constants';
|
||||
import DeleteButton from '../delete_button.vue';
|
||||
import DesignNavigation from './design_navigation.vue';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
downloadButtonLabel: s__('DesignManagement|Download design'),
|
||||
},
|
||||
components: {
|
||||
GlButton,
|
||||
GlIcon,
|
||||
|
@ -119,7 +122,8 @@ export default {
|
|||
v-gl-tooltip.bottom
|
||||
:href="image"
|
||||
icon="download"
|
||||
:title="s__('DesignManagement|Download design')"
|
||||
:title="$options.i18n.downloadButtonLabel"
|
||||
:aria-label="$options.i18n.downloadButtonLabel"
|
||||
/>
|
||||
<delete-button
|
||||
v-if="isLatestVersion && canDeleteDesign"
|
||||
|
|
|
@ -49,6 +49,7 @@ export default {
|
|||
mixins: [glFeatureFlagsMixin()],
|
||||
i18n: {
|
||||
...DIFF_FILE_HEADER,
|
||||
compareButtonLabel: s__('Compare submodule commit revisions'),
|
||||
},
|
||||
props: {
|
||||
discussionPath: {
|
||||
|
@ -192,6 +193,9 @@ export default {
|
|||
isReviewable() {
|
||||
return reviewable(this.diffFile);
|
||||
},
|
||||
externalUrlLabel() {
|
||||
return sprintf(__('View on %{url}'), { url: this.diffFile.formatted_external_url });
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', [
|
||||
|
@ -352,7 +356,8 @@ export default {
|
|||
ref="externalLink"
|
||||
v-gl-tooltip.hover
|
||||
:href="diffFile.external_url"
|
||||
:title="`View on ${diffFile.formatted_external_url}`"
|
||||
:title="externalUrlLabel"
|
||||
:aria-label="externalUrlLabel"
|
||||
target="_blank"
|
||||
data-track-event="click_toggle_external_button"
|
||||
data-track-label="diff_toggle_external_button"
|
||||
|
@ -444,7 +449,8 @@ export default {
|
|||
v-gl-tooltip.hover
|
||||
v-safe-html="submoduleDiffCompareLinkText"
|
||||
class="submodule-compare"
|
||||
:title="s__('Compare submodule commit revisions')"
|
||||
:title="$options.i18n.compareButtonLabel"
|
||||
:aria-label="$options.i18n.compareButtonLabel"
|
||||
:href="diffFile.submodule_compare.url"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -7,6 +7,8 @@ import { labelForStrategy } from '../utils';
|
|||
|
||||
export default {
|
||||
i18n: {
|
||||
deleteLabel: __('Delete'),
|
||||
editLabel: __('Edit'),
|
||||
toggleLabel: __('Feature flag status'),
|
||||
},
|
||||
components: {
|
||||
|
@ -215,19 +217,21 @@ export default {
|
|||
<div class="table-action-buttons btn-group">
|
||||
<template v-if="featureFlag.edit_path">
|
||||
<gl-button
|
||||
v-gl-tooltip.hover.bottom="__('Edit')"
|
||||
v-gl-tooltip.hover.bottom="$options.i18n.editLabel"
|
||||
class="js-feature-flag-edit-button"
|
||||
icon="pencil"
|
||||
:aria-label="$options.i18n.editLabel"
|
||||
:href="featureFlag.edit_path"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="featureFlag.destroy_path">
|
||||
<gl-button
|
||||
v-gl-tooltip.hover.bottom="__('Delete')"
|
||||
v-gl-tooltip.hover.bottom="$options.i18n.deleteLabel"
|
||||
class="js-feature-flag-delete-button"
|
||||
variant="danger"
|
||||
icon="remove"
|
||||
:disabled="!canDeleteFlag(featureFlag)"
|
||||
:aria-label="$options.i18n.deleteLabel"
|
||||
@click="setDeleteModalData(featureFlag)"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -30,6 +30,7 @@ import Strategy from './strategy.vue';
|
|||
|
||||
export default {
|
||||
i18n: {
|
||||
removeLabel: s__('FeatureFlags|Remove'),
|
||||
statusLabel: s__('FeatureFlags|Status'),
|
||||
},
|
||||
components: {
|
||||
|
@ -507,7 +508,8 @@ export default {
|
|||
<gl-button
|
||||
v-if="!isAllEnvironment(scope.environmentScope) && canUpdateScope(scope)"
|
||||
v-gl-tooltip
|
||||
:title="s__('FeatureFlags|Remove')"
|
||||
:title="$options.i18n.removeLabel"
|
||||
:aria-label="$options.i18n.removeLabel"
|
||||
class="js-delete-scope btn-transparent pr-3 pl-3"
|
||||
icon="clear"
|
||||
data-testid="feature-flag-delete"
|
||||
|
|
|
@ -165,6 +165,7 @@ export default {
|
|||
data-testid="delete-strategy-button"
|
||||
variant="danger"
|
||||
icon="remove"
|
||||
:aria-label="__('Delete')"
|
||||
@click="$emit('delete')"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -100,6 +100,7 @@ export default {
|
|||
category="secondary"
|
||||
variant="danger"
|
||||
icon="remove"
|
||||
:aria-label="$options.modal.actionPrimary.text"
|
||||
data-testid="delete-user-list"
|
||||
@click="confirmDeleteList(list)"
|
||||
/>
|
||||
|
|
|
@ -13,6 +13,10 @@ import CsvExportModal from './csv_export_modal.vue';
|
|||
import CsvImportModal from './csv_import_modal.vue';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
exportAsCsvButtonText: __('Export as CSV'),
|
||||
importIssuesText: __('Import issues'),
|
||||
},
|
||||
name: 'CsvImportExportButtons',
|
||||
components: {
|
||||
GlButtonGroup,
|
||||
|
@ -57,16 +61,15 @@ export default {
|
|||
return `${this.issuableType}-import-modal`;
|
||||
},
|
||||
importButtonText() {
|
||||
return this.showLabel ? this.$options.importIssuesText : null;
|
||||
return this.showLabel ? this.$options.i18n.importIssuesText : null;
|
||||
},
|
||||
importButtonTooltipText() {
|
||||
return this.showLabel ? null : this.$options.importIssuesText;
|
||||
return this.showLabel ? null : this.$options.i18n.importIssuesText;
|
||||
},
|
||||
importButtonIcon() {
|
||||
return this.showLabel ? null : 'import';
|
||||
},
|
||||
},
|
||||
importIssuesText: __('Import issues'),
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -75,9 +78,10 @@ export default {
|
|||
<gl-button-group>
|
||||
<gl-button
|
||||
v-if="showExportButton"
|
||||
v-gl-tooltip.hover="__('Export as CSV')"
|
||||
v-gl-tooltip.hover="$options.i18n.exportAsCsvButtonText"
|
||||
v-gl-modal="exportModalId"
|
||||
icon="export"
|
||||
:aria-label="$options.i18n.exportAsCsvButtonText"
|
||||
data-qa-selector="export_as_csv_button"
|
||||
data-testid="export-csv-button"
|
||||
/>
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
<script>
|
||||
import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui';
|
||||
import { numberToHumanSize } from '~/lib/utils/number_utils';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
eraseLogButtonLabel: s__('Job|Erase job log'),
|
||||
scrollToBottomButtonLabel: s__('Job|Scroll to bottom'),
|
||||
scrollToTopButtonLabel: s__('Job|Scroll to top'),
|
||||
showRawButtonLabel: s__('Job|Show complete raw'),
|
||||
},
|
||||
components: {
|
||||
GlLink,
|
||||
GlButton,
|
||||
|
@ -82,7 +88,8 @@ export default {
|
|||
<gl-button
|
||||
v-if="rawPath"
|
||||
v-gl-tooltip.body
|
||||
:title="s__('Job|Show complete raw')"
|
||||
:title="$options.i18n.showRawButtonLabel"
|
||||
:aria-label="$options.i18n.showRawButtonLabel"
|
||||
:href="rawPath"
|
||||
data-testid="job-raw-link-controller"
|
||||
icon="doc-text"
|
||||
|
@ -91,7 +98,8 @@ export default {
|
|||
<gl-button
|
||||
v-if="erasePath"
|
||||
v-gl-tooltip.body
|
||||
:title="s__('Job|Erase job log')"
|
||||
:title="$options.i18n.eraseLogButtonLabel"
|
||||
:aria-label="$options.i18n.eraseLogButtonLabel"
|
||||
:href="erasePath"
|
||||
:data-confirm="__('Are you sure you want to erase this build?')"
|
||||
class="gl-ml-3"
|
||||
|
@ -102,23 +110,25 @@ export default {
|
|||
<!-- eo links -->
|
||||
|
||||
<!-- scroll buttons -->
|
||||
<div v-gl-tooltip :title="s__('Job|Scroll to top')" class="gl-ml-3">
|
||||
<div v-gl-tooltip :title="$options.i18n.scrollToTopButtonLabel" class="gl-ml-3">
|
||||
<gl-button
|
||||
:disabled="isScrollTopDisabled"
|
||||
class="btn-scroll"
|
||||
data-testid="job-controller-scroll-top"
|
||||
icon="scroll_up"
|
||||
:aria-label="$options.i18n.scrollToTopButtonLabel"
|
||||
@click="handleScrollToTop"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-gl-tooltip :title="s__('Job|Scroll to bottom')" class="gl-ml-3">
|
||||
<div v-gl-tooltip :title="$options.i18n.scrollToBottomButtonLabel" class="gl-ml-3">
|
||||
<gl-button
|
||||
:disabled="isScrollBottomDisabled"
|
||||
class="js-scroll-bottom btn-scroll"
|
||||
data-testid="job-controller-scroll-bottom"
|
||||
icon="scroll_down"
|
||||
:class="{ animate: isScrollingDown }"
|
||||
:aria-label="$options.i18n.scrollToBottomButtonLabel"
|
||||
@click="handleScrollToBottom"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
GlTooltipDirective,
|
||||
} from '@gitlab/ui';
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
import { s__ } from '~/locale';
|
||||
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
|
||||
import { timeRanges } from '~/vue_shared/constants';
|
||||
import DashboardPanel from './dashboard_panel.vue';
|
||||
|
@ -24,6 +25,9 @@ metrics:
|
|||
`;
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
refreshButtonLabel: s__('Metrics|Refresh Prometheus data'),
|
||||
},
|
||||
components: {
|
||||
GlCard,
|
||||
GlForm,
|
||||
|
@ -191,7 +195,8 @@ export default {
|
|||
v-gl-tooltip
|
||||
data-testid="previewRefreshButton"
|
||||
icon="retry"
|
||||
:title="s__('Metrics|Refresh Prometheus data')"
|
||||
:title="$options.i18n.refreshButtonLabel"
|
||||
:aria-label="$options.i18n.refreshButtonLabel"
|
||||
@click="onRefresh"
|
||||
/>
|
||||
<dashboard-panel :graph-data="panelPreviewGraphData" />
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<script>
|
||||
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { n__ } from '~/locale';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
components: {
|
||||
GlButton,
|
||||
},
|
||||
props: {
|
||||
commentsCount: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
tooltipText() {
|
||||
return n__('%d comment on this commit', '%d comments on this commit', this.commentsCount);
|
||||
},
|
||||
showCommentButton() {
|
||||
return this.commentsCount > 0;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
v-if="showCommentButton"
|
||||
v-gl-tooltip
|
||||
class="gl-display-none gl-sm-display-inline-block"
|
||||
tabindex="0"
|
||||
:title="tooltipText"
|
||||
data-testid="comment-button-wrapper"
|
||||
>
|
||||
<gl-button icon="comment" class="gl-mr-3" disabled>
|
||||
{{ commentsCount }}
|
||||
</gl-button>
|
||||
</span>
|
||||
</template>
|
|
@ -1,9 +1,11 @@
|
|||
import initCherryPickCommitModal from './init_cherry_pick_commit_modal';
|
||||
import initCommitCommentsButton from './init_commit_comments_button';
|
||||
import initCommitOptionsDropdown from './init_commit_options_dropdown';
|
||||
import initRevertCommitModal from './init_revert_commit_modal';
|
||||
|
||||
export default () => {
|
||||
initRevertCommitModal();
|
||||
initCherryPickCommitModal();
|
||||
initCommitCommentsButton();
|
||||
initCommitOptionsDropdown();
|
||||
};
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import Vue from 'vue';
|
||||
import CommitCommentsButton from './components/commit_comments_button.vue';
|
||||
|
||||
export default function initCommitCommentsButton() {
|
||||
const el = document.querySelector('#js-commit-comments-button');
|
||||
|
||||
if (!el) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { commentsCount } = el.dataset;
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
render: (createElement) =>
|
||||
createElement(CommitCommentsButton, { props: { commentsCount: Number(commentsCount) } }),
|
||||
});
|
||||
}
|
|
@ -28,6 +28,7 @@ import {
|
|||
import $ from 'jquery';
|
||||
import { mapGetters, mapActions, mapState } from 'vuex';
|
||||
import descriptionVersionHistoryMixin from 'ee_else_ce/notes/mixins/description_version_history';
|
||||
import { __ } from '~/locale';
|
||||
import initMRPopovers from '~/mr_popover/';
|
||||
import noteHeader from '~/notes/components/note_header.vue';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
|
@ -37,6 +38,9 @@ import TimelineEntryItem from './timeline_entry_item.vue';
|
|||
const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
deleteButtonLabel: __('Remove description history'),
|
||||
},
|
||||
name: 'SystemNote',
|
||||
components: {
|
||||
GlIcon,
|
||||
|
@ -139,7 +143,8 @@ export default {
|
|||
<gl-button
|
||||
v-if="displayDeleteButton"
|
||||
v-gl-tooltip
|
||||
:title="__('Remove description history')"
|
||||
:title="$options.i18n.deleteButtonLabel"
|
||||
:aria-label="$options.i18n.deleteButtonLabel"
|
||||
variant="default"
|
||||
category="tertiary"
|
||||
icon="remove"
|
||||
|
|
|
@ -164,6 +164,7 @@ export default {
|
|||
variant="link"
|
||||
icon="close"
|
||||
class="gl-mr-2 gl-w-auto! gl-p-2!"
|
||||
:aria-label="__('Close')"
|
||||
@click.prevent="handleDropdownCloseClick"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -5,32 +5,29 @@
|
|||
= content_for :meta_tags do
|
||||
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues")
|
||||
|
||||
- if group_issues_count(state: 'all') == 0
|
||||
= render 'shared/empty_states/issues', project_select_button: true
|
||||
.top-area
|
||||
= render 'shared/issuable/nav', type: :issues
|
||||
.nav-controls
|
||||
= render 'shared/issuable/feed_buttons'
|
||||
|
||||
- if @can_bulk_update
|
||||
= render_if_exists 'shared/issuable/bulk_update_button', type: :issues
|
||||
|
||||
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", type: :issues, with_feature_enabled: 'issues', with_shared: false, include_projects_in_subgroups: true
|
||||
|
||||
= render 'shared/issuable/search_bar', type: :issues
|
||||
|
||||
- if @can_bulk_update
|
||||
= render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues
|
||||
|
||||
- if Feature.enabled?(:vue_issuables_list, @group) && @issues.to_a.any?
|
||||
- if use_startup_call?
|
||||
- add_page_startup_api_call(api_v4_groups_issues_path(id: @group.id, params: startup_call_params))
|
||||
.js-issuables-list{ data: { endpoint: expose_url(api_v4_groups_issues_path(id: @group.id)),
|
||||
'can-bulk-edit': @can_bulk_update.to_json,
|
||||
'empty-state-meta': { svg_path: image_path('illustrations/issues.svg') },
|
||||
'sort-key': @sort,
|
||||
type: 'issues',
|
||||
'scoped-labels-available': scoped_labels_available?(@group).to_json } }
|
||||
- else
|
||||
.top-area
|
||||
= render 'shared/issuable/nav', type: :issues
|
||||
.nav-controls
|
||||
= render 'shared/issuable/feed_buttons'
|
||||
|
||||
- if @can_bulk_update
|
||||
= render_if_exists 'shared/issuable/bulk_update_button', type: :issues
|
||||
|
||||
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", type: :issues, with_feature_enabled: 'issues', with_shared: false, include_projects_in_subgroups: true
|
||||
|
||||
= render 'shared/issuable/search_bar', type: :issues
|
||||
|
||||
- if @can_bulk_update
|
||||
= render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues
|
||||
|
||||
- if Feature.enabled?(:vue_issuables_list, @group)
|
||||
- if use_startup_call?
|
||||
- add_page_startup_api_call(api_v4_groups_issues_path(id: @group.id, params: startup_call_params))
|
||||
.js-issuables-list{ data: { endpoint: expose_url(api_v4_groups_issues_path(id: @group.id)),
|
||||
'can-bulk-edit': @can_bulk_update.to_json,
|
||||
'empty-state-meta': { svg_path: image_path('illustrations/issues.svg') },
|
||||
'sort-key': @sort,
|
||||
type: 'issues',
|
||||
'scoped-labels-available': scoped_labels_available?(@group).to_json } }
|
||||
- else
|
||||
= render 'shared/issues'
|
||||
= render 'shared/issues', project_select_button: true
|
||||
|
|
|
@ -18,10 +18,7 @@
|
|||
= commit_committer_link(@commit, avatar: true, size: 24)
|
||||
#{time_ago_with_tooltip(@commit.committed_date)}
|
||||
|
||||
- if defined?(@notes_count) && @notes_count > 0
|
||||
%span.btn.gl-button.btn-default.disabled.gl-button.btn-icon.d-none.d-sm-inline.gl-mr-3.has-tooltip{ title: n_("%d comment on this commit", "%d comments on this commit", @notes_count) % @notes_count }
|
||||
= sprite_icon('comment')
|
||||
= @notes_count
|
||||
#js-commit-comments-button{ data: { comments_count: @notes_count.to_i } }
|
||||
= link_to _('Browse files'), project_tree_path(@project, @commit), class: "btn gl-button btn-default gl-mr-3 gl-xs-w-full gl-xs-mb-3"
|
||||
#js-commit-options-dropdown{ data: commit_options_dropdown_data(@project, @commit) }
|
||||
|
||||
|
|
|
@ -3,4 +3,5 @@
|
|||
= render partial: 'projects/issues/issue', collection: @issues
|
||||
= paginate @issues, theme: "gitlab"
|
||||
- else
|
||||
= render 'shared/empty_states/issues'
|
||||
- project_select_button = local_assigns.fetch(:project_select_button, false)
|
||||
= render 'shared/empty_states/issues', project_select_button: project_select_button
|
||||
|
|
|
@ -50,16 +50,16 @@
|
|||
.milestone-actions.d-flex.justify-content-sm-start.justify-content-md-end
|
||||
- if @project # if in milestones list on project level
|
||||
- if can_admin_group_milestones?
|
||||
%button.js-promote-project-milestone-button.btn.gl-button.btn-default-tertiary.btn-sm.btn-grouped.has-tooltip{ title: s_('Milestones|Promote to Group Milestone'),
|
||||
%button.js-promote-project-milestone-button.btn.gl-button.btn-icon.btn-default-tertiary.btn-sm.has-tooltip{ title: s_('Milestones|Promote to Group Milestone'),
|
||||
disabled: true,
|
||||
type: 'button',
|
||||
data: { url: promote_project_milestone_path(milestone.project, milestone),
|
||||
milestone_title: milestone.title,
|
||||
group_name: @project.group.name } }
|
||||
= sprite_icon('level-up', size: 14)
|
||||
= sprite_icon('level-up', size: 14, css_class: 'gl-button-icon gl-icon')
|
||||
|
||||
- if can?(current_user, :admin_milestone, milestone)
|
||||
- if milestone.closed?
|
||||
= link_to s_('Milestones|Reopen Milestone'), milestone_path(milestone, milestone: { state_event: :activate }), method: :put, class: "btn gl-button btn-sm btn-grouped"
|
||||
= link_to s_('Milestones|Reopen Milestone'), milestone_path(milestone, milestone: { state_event: :activate }), method: :put, class: "btn gl-button btn-sm gl-ml-3"
|
||||
- else
|
||||
= link_to s_('Milestones|Close Milestone'), milestone_path(milestone, milestone: { state_event: :close }), method: :put, class: "btn gl-button btn-warning-secondary btn-sm btn-grouped btn-close"
|
||||
= link_to s_('Milestones|Close Milestone'), milestone_path(milestone, milestone: { state_event: :close }), method: :put, class: "btn gl-button btn-default btn-default-secondary btn-sm gl-ml-3"
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Covert has-tooltip on commit page to pajamas
|
||||
merge_request: 57858
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add aria labels to icon-only buttons
|
||||
merge_request: 57610
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Replace deprecated Close Milestone button on list view
|
||||
merge_request: 57871
|
||||
author:
|
||||
type: changed
|
|
@ -35,7 +35,7 @@ There are two places defined variables can be used. On the:
|
|||
| `cache:key` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
|
||||
| `artifacts:name` | yes | Runner | The variable expansion is made by GitLab Runner's shell environment |
|
||||
| `script`, `before_script`, `after_script` | yes | Script execution shell | The variable expansion is made by the [execution shell environment](#execution-shell-environment) |
|
||||
| `only:variables:[]`, `except:variables:[]` | no | n/a | The variable must be in the form of `$variable`. Not supported are the following:<br/><br/>- Variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`).<br/>- Any other variables related to environment (currently only `CI_ENVIRONMENT_URL`).<br/>- [Persisted variables](#persisted-variables). |
|
||||
| `only:variables:[]`, `except:variables:[]`, `rules:if` | no | n/a | The variable must be in the form of `$variable`. Not supported are the following:<br/><br/>- Variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`).<br/>- Any other variables related to environment (currently only `CI_ENVIRONMENT_URL`).<br/>- [Persisted variables](#persisted-variables). |
|
||||
|
||||
### `config.toml` file
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ const createStore = () => new Vuex.Store({
|
|||
// Notice that we are forcing all references to this module to use the same single instance of the store.
|
||||
// We are also creating the store at import-time and there is nothing which can automatically dispose of it.
|
||||
//
|
||||
// As an alternative, we should simply export the `createStore` and let the client manage the
|
||||
// As an alternative, we should export the `createStore` and let the client manage the
|
||||
// lifecycle and instance of the store.
|
||||
export default createStore();
|
||||
```
|
||||
|
@ -129,7 +129,7 @@ Here are some ills that Singletons often produce:
|
|||
|
||||
This is because of the limitations of languages like Java where everything has to be wrapped
|
||||
in a class. In JavaScript we have things like object and function literals where we can solve
|
||||
many problems with a module that simply exports utility functions.
|
||||
many problems with a module that exports utility functions.
|
||||
|
||||
### When could the Singleton pattern be actually appropriate?**
|
||||
|
||||
|
|
|
@ -254,7 +254,7 @@ end
|
|||
|
||||
**Creating a new table with a foreign key:**
|
||||
|
||||
We can simply wrap the `create_table` method with `with_lock_retries`:
|
||||
We can wrap the `create_table` method with `with_lock_retries`:
|
||||
|
||||
```ruby
|
||||
def up
|
||||
|
@ -715,7 +715,7 @@ the `DROP TABLE` statement is likely to stall concurrent traffic until it fails
|
|||
Table **has no records** (feature was never in use) and **no foreign
|
||||
keys**:
|
||||
|
||||
- Simply use the `drop_table` method in your migration.
|
||||
- Use the `drop_table` method in your migration.
|
||||
|
||||
```ruby
|
||||
def change
|
||||
|
|
|
@ -74,7 +74,7 @@ different columns set) in the same table.
|
|||
|
||||
## The Solution
|
||||
|
||||
Fortunately there is a very simple solution to these problems: simply use a
|
||||
Fortunately there is a very simple solution to these problems: use a
|
||||
separate table for every type you would otherwise store in the same table. Using
|
||||
a separate table allows you to use everything a database may provide to ensure
|
||||
consistency and query data efficiently, without any additional application logic
|
||||
|
|
|
@ -223,4 +223,4 @@ When importing, GitLab would execute the following command, passing the `import_
|
|||
git clone file://git:/tmp/lol
|
||||
```
|
||||
|
||||
Git would simply ignore the `git:` part, interpret the path as `file:///tmp/lol`, and import the repository into the new project. This action could potentially give the attacker access to any repository in the system, whether private or not.
|
||||
Git ignores the `git:` part, interpret the path as `file:///tmp/lol`, and imports the repository into the new project. This action could potentially give the attacker access to any repository in the system, whether private or not.
|
||||
|
|
|
@ -551,7 +551,7 @@ does not account for weights.
|
|||
|
||||
As we are [moving towards using `sidekiq-cluster` in
|
||||
Free](https://gitlab.com/gitlab-org/gitlab/-/issues/34396), newly-added
|
||||
workers do not need to have weights specified. They can simply use the
|
||||
workers do not need to have weights specified. They can use the
|
||||
default weight, which is 1.
|
||||
|
||||
## Worker context
|
||||
|
|
|
@ -313,9 +313,9 @@ Project.from("(#{union.to_sql}) projects")
|
|||
|
||||
## Ordering by Creation Date
|
||||
|
||||
When ordering records based on the time they were created you can simply order
|
||||
When ordering records based on the time they were created, you can order
|
||||
by the `id` column instead of ordering by `created_at`. Because IDs are always
|
||||
unique and incremented in the order that rows are created this will produce the
|
||||
unique and incremented in the order that rows are created, doing so will produce the
|
||||
exact same results. This also means there's no need to add an index on
|
||||
`created_at` to ensure consistent performance as `id` is already indexed by
|
||||
default.
|
||||
|
|
|
@ -159,9 +159,9 @@ See [Review Apps](../review_apps.md) for more details about Review Apps.
|
|||
## How do I run the tests?
|
||||
|
||||
If you are not [testing code in a merge request](#testing-code-in-merge-requests),
|
||||
there are two main options for running the tests. If you simply want to run
|
||||
the existing tests against a live GitLab instance or against a pre-built Docker image
|
||||
you can use the [GitLab QA orchestrator](https://gitlab.com/gitlab-org/gitlab-qa/tree/master/README.md). See also [examples
|
||||
there are two main options for running the tests. If you want to run
|
||||
the existing tests against a live GitLab instance or against a pre-built Docker image,
|
||||
use the [GitLab QA orchestrator](https://gitlab.com/gitlab-org/gitlab-qa/tree/master/README.md). See also [examples
|
||||
of the test scenarios you can run via the orchestrator](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md#examples).
|
||||
|
||||
On the other hand, if you would like to run against a local development GitLab
|
||||
|
|
|
@ -122,7 +122,7 @@ avoid confusion or make the code more readable. For example, if a page object is
|
|||
named `New`, it could be confusing to name the block argument `new` because that
|
||||
is used to instantiate objects, so `new_page` would be acceptable.
|
||||
|
||||
We chose not to simply use `page` because that would shadow the
|
||||
We chose not to use `page` because that would shadow the
|
||||
Capybara DSL, potentially leading to confusion and bugs.
|
||||
|
||||
### Examples
|
||||
|
|
|
@ -755,7 +755,7 @@ If a manual mock is needed for a CE module, place it in `spec/frontend/mocks/ce`
|
|||
any behavior, only provides a nice es6 compatible wrapper.
|
||||
- [`__mocks__/monaco-editor/index.js`](https://gitlab.com/gitlab-org/gitlab/blob/b7f914cddec9fc5971238cdf12766e79fa1629d7/spec/frontend/__mocks__/monaco-editor/index.js) -
|
||||
This mock is helpful because the Monaco package is completely incompatible in a Jest environment. In fact, webpack requires a special loader to make it work. This mock
|
||||
simply makes this package consumable by Jest.
|
||||
makes this package consumable by Jest.
|
||||
|
||||
### Keep mocks light
|
||||
|
||||
|
|
|
@ -280,7 +280,7 @@ FROM users
|
|||
WHERE twitter != '';
|
||||
```
|
||||
|
||||
This query simply counts the number of users that have a Twitter profile set.
|
||||
This query counts the number of users that have a Twitter profile set.
|
||||
Let's run this using `EXPLAIN (ANALYZE, BUFFERS)`:
|
||||
|
||||
```sql
|
||||
|
@ -388,7 +388,7 @@ we created the index:
|
|||
CREATE INDEX CONCURRENTLY twitter_test ON users (twitter);
|
||||
```
|
||||
|
||||
We simply told PostgreSQL to index all possible values of the `twitter` column,
|
||||
We told PostgreSQL to index all possible values of the `twitter` column,
|
||||
even empty strings. Our query in turn uses `WHERE twitter != ''`. This means
|
||||
that the index does improve things, as we don't need to do a sequential scan,
|
||||
but we may still encounter empty strings. This means PostgreSQL _has_ to apply a
|
||||
|
|
|
@ -679,7 +679,7 @@ Sidekiq processes](../administration/operations/extra_sidekiq_processes.md).
|
|||
|
||||
Whenever a change or deletion is made to an indexed GitLab object (a merge request description is changed, a file is deleted from the master branch in a repository, a project is deleted, etc), a document in the index is deleted. However, since these are "soft" deletes, the overall number of "deleted documents", and therefore wasted space, increases. Elasticsearch does intelligent merging of segments in order to remove these deleted documents. However, depending on the amount and type of activity in your GitLab installation, it's possible to see as much as 50% wasted space in the index.
|
||||
|
||||
In general, we recommend simply letting Elasticsearch merge and reclaim space automatically, with the default settings. From [Lucene's Handling of Deleted Documents](https://www.elastic.co/blog/lucenes-handling-of-deleted-documents "Lucene's Handling of Deleted Documents"), _"Overall, besides perhaps decreasing the maximum segment size, it is best to leave Lucene's defaults as-is and not fret too much about when deletes are reclaimed."_
|
||||
In general, we recommend letting Elasticsearch merge and reclaim space automatically, with the default settings. From [Lucene's Handling of Deleted Documents](https://www.elastic.co/blog/lucenes-handling-of-deleted-documents "Lucene's Handling of Deleted Documents"), _"Overall, besides perhaps decreasing the maximum segment size, it is best to leave Lucene's defaults as-is and not fret too much about when deletes are reclaimed."_
|
||||
|
||||
However, some larger installations may wish to tune the merge policy settings:
|
||||
|
||||
|
@ -719,8 +719,7 @@ data).
|
|||
The use of Elasticsearch in GitLab is only ever as a secondary data store.
|
||||
This means that all of the data stored in Elasticsearch can always be derived
|
||||
again from other data sources, specifically PostgreSQL and Gitaly. Therefore, if
|
||||
the Elasticsearch data store is ever corrupted for whatever reason, you can
|
||||
simply reindex everything from scratch.
|
||||
the Elasticsearch data store is ever corrupted for whatever reason, you can reindex everything from scratch.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
@ -916,7 +915,7 @@ In GitLab 13.9, a change was made where [binary file names are being indexed](ht
|
|||
### Last resort to recreate an index
|
||||
|
||||
There may be cases where somehow data never got indexed and it's not in the
|
||||
queue, or the index is somehow in a state where migrations just simply cannot
|
||||
queue, or the index is somehow in a state where migrations just cannot
|
||||
proceed. It is always best to try to troubleshoot the root cause of the problem
|
||||
using the above [troubleshooting](#troubleshooting) steps.
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ You can take action on Sentry Errors from within the GitLab UI.
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/39665) in GitLab 12.7.
|
||||
|
||||
From within the [Error Details](#error-details) page you can ignore a Sentry error by simply clicking the **Ignore** button near the top of the page.
|
||||
From within the [Error Details](#error-details) page you can ignore a Sentry error by clicking the **Ignore** button near the top of the page.
|
||||
|
||||
Ignoring an error prevents it from appearing in the [Error Tracking List](#error-tracking-list), and silences notifications that were set up within Sentry.
|
||||
|
||||
|
|
|
@ -379,7 +379,7 @@ it also provides a clear timeline and development structure.
|
|||
|
||||
![Use revert to keep branch flowing](img/revert.png)
|
||||
|
||||
If you want to revert changes introduced in certain `commit-id` you can simply
|
||||
If you want to revert changes introduced in certain `commit-id`, you can
|
||||
revert that `commit-id` (swap additions and deletions) in newly created commit:
|
||||
You can do this with
|
||||
|
||||
|
|
|
@ -66,4 +66,4 @@ Activating a user changes the user's state to active and consumes a
|
|||
[seat](../../subscriptions/self_managed/index.md#billable-users).
|
||||
|
||||
NOTE:
|
||||
A deactivated user can also activate their account themselves by simply logging back in via the UI.
|
||||
A deactivated user can also activate their account themselves by logging back in via the UI.
|
||||
|
|
|
@ -994,7 +994,7 @@ False positives can be handled in two ways:
|
|||
Checks perform testing of a specific type and can be turned on and off for specific configuration
|
||||
profiles. The provided [configuration files](#configuration-files) define several profiles that you
|
||||
can use. The profile definition in the configuration file lists all the checks that are active
|
||||
during a scan. To turn off a specific check, simply remove it from the profile definition in the
|
||||
during a scan. To turn off a specific check, remove it from the profile definition in the
|
||||
configuration file. The profiles are defined in the `Profiles` section of the configuration file.
|
||||
|
||||
Example profile definition:
|
||||
|
|
|
@ -33715,6 +33715,9 @@ msgstr ""
|
|||
msgid "View merge request"
|
||||
msgstr ""
|
||||
|
||||
msgid "View on %{url}"
|
||||
msgstr ""
|
||||
|
||||
msgid "View open merge request"
|
||||
msgstr ""
|
||||
|
||||
|
|
2
qa/qa.rb
2
qa/qa.rb
|
@ -578,7 +578,9 @@ module QA
|
|||
autoload :LoopRunner, 'qa/specs/loop_runner'
|
||||
|
||||
module Helpers
|
||||
autoload :ContextSelector, 'qa/specs/helpers/context_selector'
|
||||
autoload :Quarantine, 'qa/specs/helpers/quarantine'
|
||||
autoload :RSpec, 'qa/specs/helpers/rspec'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
require 'gitlab/qa'
|
||||
require 'uri'
|
||||
require 'active_support/core_ext/object/blank'
|
||||
|
||||
module QA
|
||||
module Runtime
|
||||
|
@ -24,48 +25,6 @@ module QA
|
|||
SUPPORTED_FEATURES
|
||||
end
|
||||
|
||||
def context_matches?(*options)
|
||||
return false unless Runtime::Scenario.attributes[:gitlab_address]
|
||||
|
||||
opts = {}
|
||||
opts[:domain] = '.+'
|
||||
opts[:tld] = '.com'
|
||||
|
||||
uri = URI(Runtime::Scenario.gitlab_address)
|
||||
|
||||
options.each do |option|
|
||||
opts[:domain] = 'gitlab' if option == :production
|
||||
|
||||
if option.is_a?(Hash) && !option[:pipeline].nil? && !ci_project_name.nil?
|
||||
return pipeline_matches?(option[:pipeline])
|
||||
|
||||
elsif option.is_a?(Hash) && !option[:subdomain].nil?
|
||||
opts.merge!(option)
|
||||
|
||||
opts[:subdomain] = case option[:subdomain]
|
||||
when Array
|
||||
"(#{option[:subdomain].join("|")})."
|
||||
when Regexp
|
||||
option[:subdomain]
|
||||
else
|
||||
"(#{option[:subdomain]})."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
uri.host.match?(/^#{opts[:subdomain]}#{opts[:domain]}#{opts[:tld]}$/)
|
||||
end
|
||||
|
||||
alias_method :dot_com?, :context_matches?
|
||||
|
||||
def pipeline_matches?(pipeline_to_run_in)
|
||||
Array(pipeline_to_run_in).any? { |pipeline| pipeline.to_s.casecmp?(pipeline_from_project_name) }
|
||||
end
|
||||
|
||||
def pipeline_from_project_name
|
||||
ci_project_name.to_s.start_with?('gitlab-qa') ? Runtime::Env.default_branch : ci_project_name
|
||||
end
|
||||
|
||||
def additional_repository_storage
|
||||
ENV['QA_ADDITIONAL_REPOSITORY_STORAGE']
|
||||
end
|
||||
|
@ -82,6 +41,10 @@ module QA
|
|||
ENV['CI_JOB_URL']
|
||||
end
|
||||
|
||||
def ci_job_name
|
||||
ENV['CI_JOB_NAME']
|
||||
end
|
||||
|
||||
def ci_project_name
|
||||
ENV['CI_PROJECT_NAME']
|
||||
end
|
||||
|
|
|
@ -181,7 +181,7 @@ module QA
|
|||
end
|
||||
|
||||
def verify_storage_move(source_storage, destination_storage, repo_type: :project)
|
||||
return if QA::Runtime::Env.dot_com?
|
||||
return if Specs::Helpers::ContextSelector.dot_com?
|
||||
|
||||
repo_path = verify_storage_move_from_gitaly(source_storage[:name], repo_type: repo_type)
|
||||
|
||||
|
|
86
qa/qa/specs/helpers/context_selector.rb
Normal file
86
qa/qa/specs/helpers/context_selector.rb
Normal file
|
@ -0,0 +1,86 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rspec/core'
|
||||
|
||||
module QA
|
||||
module Specs
|
||||
module Helpers
|
||||
module ContextSelector
|
||||
extend self
|
||||
|
||||
def configure_rspec
|
||||
::RSpec.configure do |config|
|
||||
config.before do |example|
|
||||
if example.metadata.key?(:only)
|
||||
skip('Test is not compatible with this environment or pipeline') unless ContextSelector.context_matches?(example.metadata[:only])
|
||||
elsif example.metadata.key?(:exclude)
|
||||
skip('Test is excluded in this job') if ContextSelector.exclude?(example.metadata[:exclude])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def exclude?(*options)
|
||||
return false unless Runtime::Env.ci_job_name.present?
|
||||
|
||||
context_matches?(*options)
|
||||
end
|
||||
|
||||
def context_matches?(*options)
|
||||
return false unless Runtime::Scenario.attributes[:gitlab_address]
|
||||
|
||||
opts = {}
|
||||
opts[:domain] = '.+'
|
||||
opts[:tld] = '.com'
|
||||
|
||||
uri = URI(Runtime::Scenario.gitlab_address)
|
||||
|
||||
options.each do |option|
|
||||
opts[:domain] = 'gitlab' if option == :production
|
||||
|
||||
next unless option.is_a?(Hash)
|
||||
|
||||
if option[:pipeline].present? && Runtime::Env.ci_project_name.present?
|
||||
return pipeline_matches?(option[:pipeline])
|
||||
|
||||
elsif option[:job].present?
|
||||
return job_matches?(option[:job])
|
||||
|
||||
elsif option[:subdomain].present?
|
||||
opts.merge!(option)
|
||||
|
||||
opts[:subdomain] = case option[:subdomain]
|
||||
when Array
|
||||
"(#{option[:subdomain].join("|")})."
|
||||
when Regexp
|
||||
option[:subdomain]
|
||||
else
|
||||
"(#{option[:subdomain]})."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
uri.host.match?(/^#{opts[:subdomain]}#{opts[:domain]}#{opts[:tld]}$/)
|
||||
end
|
||||
|
||||
alias_method :dot_com?, :context_matches?
|
||||
|
||||
def job_matches?(job_patterns)
|
||||
Array(job_patterns).any? do |job|
|
||||
pattern = job.is_a?(Regexp) ? job : Regexp.new(job)
|
||||
pattern = Regexp.new(pattern.source, pattern.options | Regexp::IGNORECASE)
|
||||
pattern =~ Runtime::Env.ci_job_name
|
||||
end
|
||||
end
|
||||
|
||||
def pipeline_matches?(pipeline_to_run_in)
|
||||
Array(pipeline_to_run_in).any? { |pipeline| pipeline.to_s.casecmp?(pipeline_from_project_name(Runtime::Env.ci_project_name)) }
|
||||
end
|
||||
|
||||
def pipeline_from_project_name(project_name)
|
||||
project_name.to_s.start_with?('gitlab-qa') ? Runtime::Env.default_branch : project_name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,22 +6,18 @@ module QA
|
|||
module Specs
|
||||
module Helpers
|
||||
module Quarantine
|
||||
include RSpec::Core::Pending
|
||||
include ::RSpec::Core::Pending
|
||||
|
||||
extend self
|
||||
|
||||
def configure_rspec
|
||||
RSpec.configure do |config|
|
||||
::RSpec.configure do |config|
|
||||
config.before(:context, :quarantine) do
|
||||
Quarantine.skip_or_run_quarantined_contexts(config.inclusion_filter.rules, self.class)
|
||||
end
|
||||
|
||||
config.before do |example|
|
||||
Quarantine.skip_or_run_quarantined_tests_or_contexts(config.inclusion_filter.rules, example)
|
||||
|
||||
if example.metadata.key?(:only)
|
||||
skip('Test is not compatible with this environment or pipeline') unless Runtime::Env.context_matches?(example.metadata[:only])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -55,7 +51,7 @@ module QA
|
|||
if quarantine_tag.is_a?(Hash) && quarantine_tag&.key?(:only)
|
||||
# If the :quarantine hash contains :only, we respect that.
|
||||
# For instance `quarantine: { only: { subdomain: :staging } }` will only quarantine the test when it runs against staging.
|
||||
return unless Runtime::Env.context_matches?(quarantine_tag[:only])
|
||||
return unless ContextSelector.context_matches?(quarantine_tag[:only])
|
||||
end
|
||||
|
||||
skip(quarantine_message(quarantine_tag))
|
||||
|
|
30
qa/qa/specs/helpers/rspec.rb
Normal file
30
qa/qa/specs/helpers/rspec.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rspec/core'
|
||||
|
||||
module QA
|
||||
module Specs
|
||||
module Helpers
|
||||
module RSpec
|
||||
# We need a reporter for internal tests that's different from the reporter for
|
||||
# external tests otherwise the results will be mixed up. We don't care about
|
||||
# most reporting, but we do want to know if a test fails
|
||||
class RaiseOnFailuresReporter < ::RSpec::Core::NullReporter
|
||||
def self.example_failed(example)
|
||||
raise example.exception
|
||||
end
|
||||
end
|
||||
|
||||
# We use an example group wrapper to prevent the state of internal tests
|
||||
# expanding into the global state
|
||||
# See: https://github.com/rspec/rspec-core/issues/2603
|
||||
def describe_successfully(*args, &describe_body)
|
||||
example_group = RSpec.describe(*args, &describe_body)
|
||||
ran_successfully = example_group.run RaiseOnFailuresReporter
|
||||
expect(ran_successfully).to eq true
|
||||
example_group
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -44,7 +44,7 @@ module QA
|
|||
|
||||
tags_for_rspec.push(%w[--tag ~skip_signup_disabled]) if QA::Runtime::Env.signup_disabled?
|
||||
|
||||
tags_for_rspec.push(%w[--tag ~skip_live_env]) if QA::Runtime::Env.dot_com?
|
||||
tags_for_rspec.push(%w[--tag ~skip_live_env]) if QA::Specs::Helpers::ContextSelector.dot_com?
|
||||
|
||||
QA::Runtime::Env.supported_features.each_key do |key|
|
||||
tags_for_rspec.push(%W[--tag ~requires_#{key}]) unless QA::Runtime::Env.can_test? key
|
||||
|
|
|
@ -341,56 +341,4 @@ RSpec.describe QA::Runtime::Env do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.context_matches?' do
|
||||
it 'returns true when url has .com' do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
|
||||
|
||||
expect(described_class.dot_com?).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false when url does not have .com' do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.test")
|
||||
|
||||
expect(described_class.dot_com?).to be_falsey
|
||||
end
|
||||
|
||||
context 'with arguments' do
|
||||
it 'returns true when :subdomain is set' do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
|
||||
|
||||
expect(described_class.dot_com?(subdomain: :staging)).to be_truthy
|
||||
end
|
||||
|
||||
it 'matches multiple subdomains' do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
|
||||
|
||||
expect(described_class.context_matches?(subdomain: [:release, :staging])).to be_truthy
|
||||
expect(described_class.context_matches?(:production, subdomain: [:release, :staging])).to be_truthy
|
||||
end
|
||||
|
||||
it 'matches :production' do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.com/")
|
||||
|
||||
expect(described_class.context_matches?(:production)).to be_truthy
|
||||
end
|
||||
|
||||
it 'doesnt match with mismatching switches' do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.test')
|
||||
|
||||
aggregate_failures do
|
||||
expect(described_class.context_matches?(tld: '.net')).to be_falsey
|
||||
expect(described_class.context_matches?(:production)).to be_falsey
|
||||
expect(described_class.context_matches?(subdomain: [:staging])).to be_falsey
|
||||
expect(described_class.context_matches?(domain: 'example')).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns false for mismatching' do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
|
||||
|
||||
expect(described_class.context_matches?(:production)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,6 +22,7 @@ RSpec.configure do |config|
|
|||
config.include ::Matchers
|
||||
|
||||
QA::Specs::Helpers::Quarantine.configure_rspec
|
||||
QA::Specs::Helpers::ContextSelector.configure_rspec
|
||||
|
||||
config.before do |example|
|
||||
QA::Runtime::Logger.debug("\nStarting test: #{example.full_description}\n")
|
||||
|
|
294
qa/spec/specs/helpers/context_selector_spec.rb
Normal file
294
qa/spec/specs/helpers/context_selector_spec.rb
Normal file
|
@ -0,0 +1,294 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rspec/core/sandbox'
|
||||
|
||||
RSpec.configure do |c|
|
||||
c.around do |ex|
|
||||
RSpec::Core::Sandbox.sandboxed do |config|
|
||||
# If there is an example-within-an-example, we want to make sure the inner example
|
||||
# does not get a reference to the outer example (the real spec) if it calls
|
||||
# something like `pending`
|
||||
config.before(:context) { RSpec.current_example = nil }
|
||||
|
||||
config.color_mode = :off
|
||||
|
||||
ex.run
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe QA::Specs::Helpers::ContextSelector do
|
||||
include Helpers::StubENV
|
||||
include QA::Specs::Helpers::RSpec
|
||||
|
||||
before do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, 'https://staging.gitlab.com')
|
||||
described_class.configure_rspec
|
||||
end
|
||||
|
||||
describe '.context_matches?' do
|
||||
it 'returns true when url has .com' do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
|
||||
|
||||
expect(described_class.dot_com?).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false when url does not have .com' do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.test")
|
||||
|
||||
expect(described_class.dot_com?).to be_falsey
|
||||
end
|
||||
|
||||
context 'with arguments' do
|
||||
it 'returns true when :subdomain is set' do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
|
||||
|
||||
expect(described_class.dot_com?(subdomain: :staging)).to be_truthy
|
||||
end
|
||||
|
||||
it 'matches multiple subdomains' do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
|
||||
|
||||
expect(described_class.context_matches?(subdomain: [:release, :staging])).to be_truthy
|
||||
expect(described_class.context_matches?(:production, subdomain: [:release, :staging])).to be_truthy
|
||||
end
|
||||
|
||||
it 'matches :production' do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.com/")
|
||||
|
||||
expect(described_class.context_matches?(:production)).to be_truthy
|
||||
end
|
||||
|
||||
it 'doesnt match with mismatching switches' do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.test')
|
||||
|
||||
aggregate_failures do
|
||||
expect(described_class.context_matches?(tld: '.net')).to be_falsey
|
||||
expect(described_class.context_matches?(:production)).to be_falsey
|
||||
expect(described_class.context_matches?(subdomain: [:staging])).to be_falsey
|
||||
expect(described_class.context_matches?(domain: 'example')).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns false for mismatching' do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
|
||||
|
||||
expect(described_class.context_matches?(:production)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe 'description and context blocks' do
|
||||
context 'with environment set' do
|
||||
it 'can apply to contexts or descriptions' do
|
||||
group = describe_successfully 'Runs in staging', only: { subdomain: :staging } do
|
||||
it('runs in staging') {}
|
||||
end
|
||||
|
||||
expect(group.examples[0].execution_result.status).to eq(:passed)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with different environment set' do
|
||||
before do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.com')
|
||||
described_class.configure_rspec
|
||||
end
|
||||
|
||||
it 'does not run against production' do
|
||||
group = describe_successfully 'Runs in staging', :something, only: { subdomain: :staging } do
|
||||
it('runs in staging') {}
|
||||
end
|
||||
|
||||
expect(group.examples[0].execution_result.status).to eq(:pending)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'runs only in staging' do
|
||||
group = describe_successfully do
|
||||
it('runs in staging', only: { subdomain: :staging }) {}
|
||||
it('doesnt run in staging', only: :production) {}
|
||||
it('runs in staging also', only: { subdomain: %i[release staging] }) {}
|
||||
it('runs in any env') {}
|
||||
end
|
||||
|
||||
expect(group.examples[0].execution_result.status).to eq(:passed)
|
||||
expect(group.examples[1].execution_result.status).to eq(:pending)
|
||||
expect(group.examples[2].execution_result.status).to eq(:passed)
|
||||
expect(group.examples[3].execution_result.status).to eq(:passed)
|
||||
end
|
||||
|
||||
context 'custom env' do
|
||||
before do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, 'https://release.gitlab.net')
|
||||
end
|
||||
|
||||
it 'runs on a custom environment' do
|
||||
group = describe_successfully do
|
||||
it('runs on release gitlab net', only: { tld: '.net', subdomain: :release, domain: 'gitlab' }) {}
|
||||
it('does not run on release', only: :production) {}
|
||||
end
|
||||
|
||||
expect(group.examples.first.execution_result.status).to eq(:passed)
|
||||
expect(group.examples.last.execution_result.status).to eq(:pending)
|
||||
end
|
||||
end
|
||||
|
||||
context 'production' do
|
||||
before do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.com/')
|
||||
end
|
||||
|
||||
it 'runs on production' do
|
||||
group = describe_successfully do
|
||||
it('runs on prod', only: :production) {}
|
||||
it('does not run in prod', only: { subdomain: :staging }) {}
|
||||
it('runs in prod and staging', only: { subdomain: /(staging.)?/, domain: 'gitlab' }) {}
|
||||
end
|
||||
|
||||
expect(group.examples[0].execution_result.status).to eq(:passed)
|
||||
expect(group.examples[1].execution_result.status).to eq(:pending)
|
||||
expect(group.examples[2].execution_result.status).to eq(:passed)
|
||||
end
|
||||
end
|
||||
|
||||
it 'outputs a message for invalid environments' do
|
||||
group = describe_successfully do
|
||||
it('will skip', only: :production) {}
|
||||
end
|
||||
|
||||
expect(group.examples.first.execution_result.pending_message).to match(/[Tt]est.*not compatible.*environment/)
|
||||
end
|
||||
|
||||
context 'with pipeline constraints' do
|
||||
context 'without CI_PROJECT_NAME set' do
|
||||
before do
|
||||
stub_env('CI_PROJECT_NAME', nil)
|
||||
described_class.configure_rspec
|
||||
end
|
||||
|
||||
it 'runs on any pipeline' do
|
||||
group = describe_successfully do
|
||||
it('runs given a single named pipeline', only: { pipeline: :nightly }) {}
|
||||
it('runs given an array of pipelines', only: { pipeline: [:canary, :not_nightly] }) {}
|
||||
end
|
||||
|
||||
aggregate_failures do
|
||||
expect(group.examples[0].execution_result.status).to eq(:passed)
|
||||
expect(group.examples[1].execution_result.status).to eq(:passed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a pipeline triggered from the default branch runs in gitlab-qa' do
|
||||
before do
|
||||
stub_env('CI_PROJECT_NAME', 'gitlab-qa')
|
||||
described_class.configure_rspec
|
||||
end
|
||||
|
||||
it 'runs on default branch pipelines' do
|
||||
group = describe_successfully do
|
||||
it('runs on master pipeline given a single pipeline', only: { pipeline: :master }) {}
|
||||
it('runs in master given an array of pipelines', only: { pipeline: [:canary, :master] }) {}
|
||||
it('does not run in non-default pipelines', only: { pipeline: [:nightly, :not_nightly, :not_master] }) {}
|
||||
end
|
||||
|
||||
aggregate_failures do
|
||||
expect(group.examples[0].execution_result.status).to eq(:passed)
|
||||
expect(group.examples[1].execution_result.status).to eq(:passed)
|
||||
expect(group.examples[2].execution_result.status).to eq(:pending)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with CI_PROJECT_NAME set' do
|
||||
before do
|
||||
stub_env('CI_PROJECT_NAME', 'NIGHTLY')
|
||||
described_class.configure_rspec
|
||||
end
|
||||
|
||||
it 'runs on designated pipeline' do
|
||||
group = describe_successfully do
|
||||
it('runs on nightly', only: { pipeline: :nightly }) {}
|
||||
it('does not run in not_nightly', only: { pipeline: :not_nightly }) {}
|
||||
it('runs on nightly given an array', only: { pipeline: [:canary, :nightly] }) {}
|
||||
it('does not run in not_nightly given an array', only: { pipeline: [:not_nightly, :canary] }) {}
|
||||
end
|
||||
|
||||
aggregate_failures do
|
||||
expect(group.examples[0].execution_result.status).to eq(:passed)
|
||||
expect(group.examples[1].execution_result.status).to eq(:pending)
|
||||
expect(group.examples[2].execution_result.status).to eq(:passed)
|
||||
expect(group.examples[3].execution_result.status).to eq(:pending)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when excluding contexts' do
|
||||
context 'with job constraints' do
|
||||
context 'without CI_JOB_NAME set' do
|
||||
before do
|
||||
stub_env('CI_JOB_NAME', nil)
|
||||
described_class.configure_rspec
|
||||
end
|
||||
|
||||
it 'runs in any job' do
|
||||
group = describe_successfully do
|
||||
it('runs given a single named job', exclude: { job: 'ee:instance-image' }) {}
|
||||
it('runs given a single regex pattern', exclude: { job: '.*:instance-image' }) {}
|
||||
it('runs given an array of jobs', exclude: { job: %w[ee:instance-image qa-schedules-browser_ui-3_create] }) {}
|
||||
it('runs given an array of regex patterns', exclude: { job: %w[ee:.* qa-schedules-browser_ui.*] }) {}
|
||||
it('runs given a mix of strings and regex patterns', exclude: { job: %w[ee:instance-image qa-schedules-browser_ui.*] }) {}
|
||||
end
|
||||
|
||||
aggregate_failures do
|
||||
group.examples.each do |example|
|
||||
expect(example.execution_result.status).to eq(:passed)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with CI_JOB_NAME set' do
|
||||
before do
|
||||
stub_env('CI_JOB_NAME', 'ee:instance-image')
|
||||
described_class.configure_rspec
|
||||
end
|
||||
|
||||
it 'does not run in the specified job' do
|
||||
group = describe_successfully do
|
||||
it('skips given a single named job', exclude: { job: 'ee:instance-image' }) {}
|
||||
it('skips given a single regex pattern', exclude: { job: '.*:instance-image' }) {}
|
||||
it('skips given an array of jobs', exclude: { job: %w[ee:instance-image qa-schedules-browser_ui-3_create] }) {}
|
||||
it('skips given an array of regex patterns', exclude: { job: %w[ee:.* qa-schedules-browser_ui.*] }) {}
|
||||
it('skips given a mix of strings and regex patterns', exclude: { job: %w[ee:instance-image qa-schedules-browser_ui.*] }) {}
|
||||
end
|
||||
|
||||
aggregate_failures do
|
||||
group.examples.each do |example|
|
||||
expect(example.execution_result.status).to eq(:pending)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'runs in jobs that do not match' do
|
||||
group = describe_successfully do
|
||||
it('runs given a single named job', exclude: { job: 'ce:instance-image' }) {}
|
||||
it('runs given a single regex pattern', exclude: { job: '.*:instance-image-quarantine' }) {}
|
||||
it('runs given an array of jobs', exclude: { job: %w[ce:instance-image qa-schedules-browser_ui-3_create] }) {}
|
||||
it('runs given an array of regex patterns', exclude: { job: %w[ce:.* qa-schedules-browser_ui.*] }) {}
|
||||
it('runs given a mix of strings and regex patterns', exclude: { job: %w[ce:instance-image qa-schedules-browser_ui.*] }) {}
|
||||
end
|
||||
|
||||
aggregate_failures do
|
||||
group.examples.each do |example|
|
||||
expect(example.execution_result.status).to eq(:passed)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,25 +2,6 @@
|
|||
|
||||
require 'rspec/core/sandbox'
|
||||
|
||||
# We need a reporter for internal tests that's different from the reporter for
|
||||
# external tests otherwise the results will be mixed up. We don't care about
|
||||
# most reporting, but we do want to know if a test fails
|
||||
class RaiseOnFailuresReporter < RSpec::Core::NullReporter
|
||||
def self.example_failed(example)
|
||||
raise example.exception
|
||||
end
|
||||
end
|
||||
|
||||
# We use an example group wrapper to prevent the state of internal tests
|
||||
# expanding into the global state
|
||||
# See: https://github.com/rspec/rspec-core/issues/2603
|
||||
def describe_successfully(*args, &describe_body)
|
||||
example_group = RSpec.describe(*args, &describe_body)
|
||||
ran_successfully = example_group.run RaiseOnFailuresReporter
|
||||
expect(ran_successfully).to eq true
|
||||
example_group
|
||||
end
|
||||
|
||||
RSpec.configure do |c|
|
||||
c.around do |ex|
|
||||
RSpec::Core::Sandbox.sandboxed do |config|
|
||||
|
@ -38,6 +19,7 @@ end
|
|||
|
||||
RSpec.describe QA::Specs::Helpers::Quarantine do
|
||||
include Helpers::StubENV
|
||||
include QA::Specs::Helpers::RSpec
|
||||
|
||||
describe '.skip_or_run_quarantined_contexts' do
|
||||
context 'with no tag focused' do
|
||||
|
@ -336,159 +318,4 @@ RSpec.describe QA::Specs::Helpers::Quarantine do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'running against specific environments or pipelines' do
|
||||
before do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, 'https://staging.gitlab.com')
|
||||
described_class.configure_rspec
|
||||
end
|
||||
|
||||
describe 'description and context blocks' do
|
||||
context 'with environment set' do
|
||||
it 'can apply to contexts or descriptions' do
|
||||
group = describe_successfully 'Runs in staging', only: { subdomain: :staging } do
|
||||
it('runs in staging') {}
|
||||
end
|
||||
|
||||
expect(group.examples[0].execution_result.status).to eq(:passed)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with different environment set' do
|
||||
before do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.com')
|
||||
described_class.configure_rspec
|
||||
end
|
||||
|
||||
it 'does not run against production' do
|
||||
group = describe_successfully 'Runs in staging', :something, only: { subdomain: :staging } do
|
||||
it('runs in staging') {}
|
||||
end
|
||||
|
||||
expect(group.examples[0].execution_result.status).to eq(:pending)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'runs only in staging' do
|
||||
group = describe_successfully do
|
||||
it('runs in staging', only: { subdomain: :staging }) {}
|
||||
it('doesnt run in staging', only: :production) {}
|
||||
it('runs in staging also', only: { subdomain: %i[release staging] }) {}
|
||||
it('runs in any env') {}
|
||||
end
|
||||
|
||||
expect(group.examples[0].execution_result.status).to eq(:passed)
|
||||
expect(group.examples[1].execution_result.status).to eq(:pending)
|
||||
expect(group.examples[2].execution_result.status).to eq(:passed)
|
||||
expect(group.examples[3].execution_result.status).to eq(:passed)
|
||||
end
|
||||
|
||||
context 'custom env' do
|
||||
before do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, 'https://release.gitlab.net')
|
||||
end
|
||||
|
||||
it 'runs on a custom environment' do
|
||||
group = describe_successfully do
|
||||
it('runs on release gitlab net', only: { tld: '.net', subdomain: :release, domain: 'gitlab' }) {}
|
||||
it('does not run on release', only: :production) {}
|
||||
end
|
||||
|
||||
expect(group.examples.first.execution_result.status).to eq(:passed)
|
||||
expect(group.examples.last.execution_result.status).to eq(:pending)
|
||||
end
|
||||
end
|
||||
|
||||
context 'production' do
|
||||
before do
|
||||
QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.com/')
|
||||
end
|
||||
|
||||
it 'runs on production' do
|
||||
group = describe_successfully do
|
||||
it('runs on prod', only: :production) {}
|
||||
it('does not run in prod', only: { subdomain: :staging }) {}
|
||||
it('runs in prod and staging', only: { subdomain: /(staging.)?/, domain: 'gitlab' }) {}
|
||||
end
|
||||
|
||||
expect(group.examples[0].execution_result.status).to eq(:passed)
|
||||
expect(group.examples[1].execution_result.status).to eq(:pending)
|
||||
expect(group.examples[2].execution_result.status).to eq(:passed)
|
||||
end
|
||||
end
|
||||
|
||||
it 'outputs a message for invalid environments' do
|
||||
group = describe_successfully do
|
||||
it('will skip', only: :production) {}
|
||||
end
|
||||
|
||||
expect(group.examples.first.execution_result.pending_message).to match(/[Tt]est.*not compatible.*environment/)
|
||||
end
|
||||
|
||||
context 'with pipeline constraints' do
|
||||
context 'without CI_PROJECT_NAME set' do
|
||||
before do
|
||||
stub_env('CI_PROJECT_NAME', nil)
|
||||
described_class.configure_rspec
|
||||
end
|
||||
|
||||
it 'runs on any pipeline' do
|
||||
group = describe_successfully do
|
||||
it('runs given a single named pipeline', only: { pipeline: :nightly }) {}
|
||||
it('runs given an array of pipelines', only: { pipeline: [:canary, :not_nightly] }) {}
|
||||
end
|
||||
|
||||
aggregate_failures do
|
||||
expect(group.examples[0].execution_result.status).to eq(:passed)
|
||||
expect(group.examples[1].execution_result.status).to eq(:passed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a pipeline triggered from the default branch runs in gitlab-qa' do
|
||||
before do
|
||||
stub_env('CI_PROJECT_NAME', 'gitlab-qa')
|
||||
described_class.configure_rspec
|
||||
end
|
||||
|
||||
it 'runs on default branch pipelines' do
|
||||
group = describe_successfully do
|
||||
it('runs on master pipeline given a single pipeline', only: { pipeline: :master }) {}
|
||||
it('runs in master given an array of pipelines', only: { pipeline: [:canary, :master] }) {}
|
||||
it('does not run in non-default pipelines', only: { pipeline: [:nightly, :not_nightly, :not_master] }) {}
|
||||
end
|
||||
|
||||
aggregate_failures do
|
||||
expect(group.examples[0].execution_result.status).to eq(:passed)
|
||||
expect(group.examples[1].execution_result.status).to eq(:passed)
|
||||
expect(group.examples[2].execution_result.status).to eq(:pending)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with CI_PROJECT_NAME set' do
|
||||
before do
|
||||
stub_env('CI_PROJECT_NAME', 'NIGHTLY')
|
||||
described_class.configure_rspec
|
||||
end
|
||||
|
||||
it 'runs on designated pipeline' do
|
||||
group = describe_successfully do
|
||||
it('runs on nightly', only: { pipeline: :nightly }) {}
|
||||
it('does not run in not_nightly', only: { pipeline: :not_nightly }) {}
|
||||
it('runs on nightly given an array', only: { pipeline: [:canary, :nightly] }) {}
|
||||
it('does not run in not_nightly given an array', only: { pipeline: [:not_nightly, :canary] }) {}
|
||||
end
|
||||
|
||||
aggregate_failures do
|
||||
expect(group.examples[0].execution_result.status).to eq(:passed)
|
||||
expect(group.examples[1].execution_result.status).to eq(:pending)
|
||||
expect(group.examples[2].execution_result.status).to eq(:passed)
|
||||
expect(group.examples[3].execution_result.status).to eq(:pending)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -108,7 +108,7 @@ RSpec.describe 'Group issues page' do
|
|||
|
||||
it 'shows projects only with issues feature enabled', :js do
|
||||
find('.empty-state .js-lazy-loaded')
|
||||
find('.new-project-item-link').click
|
||||
find('.empty-state .new-project-item-link').click
|
||||
|
||||
page.within('.select2-results') do
|
||||
expect(page).to have_content(project.full_name)
|
||||
|
|
|
@ -41,6 +41,7 @@ exports[`Design management toolbar component renders design and updated data 1`]
|
|||
/>
|
||||
|
||||
<gl-button-stub
|
||||
aria-label="Download design"
|
||||
buttontextclasses=""
|
||||
category="primary"
|
||||
href="/-/designs/306/7f747adcd4693afadbe968d7ba7d983349b9012d"
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import CommitCommentsButton from '~/projects/commit/components/commit_comments_button.vue';
|
||||
|
||||
describe('CommitCommentsButton', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = extendedWrapper(
|
||||
shallowMount(CommitCommentsButton, {
|
||||
propsData: {
|
||||
commentsCount: 1,
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const tooltip = () => wrapper.findByTestId('comment-button-wrapper');
|
||||
|
||||
describe('Comment Button', () => {
|
||||
it('has proper tooltip and button attributes for 1 comment', () => {
|
||||
createComponent();
|
||||
|
||||
expect(tooltip().attributes('title')).toBe('1 comment on this commit');
|
||||
expect(tooltip().text()).toBe('1');
|
||||
});
|
||||
|
||||
it('has proper tooltip and button attributes for multiple comments', () => {
|
||||
createComponent({ commentsCount: 2 });
|
||||
|
||||
expect(tooltip().attributes('title')).toBe('2 comments on this commit');
|
||||
expect(tooltip().text()).toBe('2');
|
||||
});
|
||||
|
||||
it('does not show when there are no comments', () => {
|
||||
createComponent({ commentsCount: 0 });
|
||||
|
||||
expect(tooltip().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue