Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-11-04 00:07:28 +00:00
parent cd353f0da2
commit 04b866f03b
66 changed files with 717 additions and 483 deletions

View File

@ -1,43 +0,0 @@
# This configuration was generated by
# `haml-lint --auto-gen-config`
# on 2023-10-30 15:10:05 +0100 using Haml-Lint version 0.40.1.
# The point is for the user to remove these configuration records
# one by one as the lints are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of Haml-Lint, may require this file to be generated again.
linters:
# Offense count: 37
DocumentationLinks:
exclude:
- "app/views/admin/application_settings/_localization.html.haml"
- "app/views/profiles/two_factor_auths/show.html.haml"
- "app/views/projects/settings/merge_requests/_merge_request_merge_commit_template.html.haml"
- "app/views/projects/settings/merge_requests/_merge_request_merge_suggestions_settings.html.haml"
- "app/views/projects/settings/merge_requests/_merge_request_squash_commit_template.html.haml"
- "app/views/projects/usage_quotas/index.html.haml"
- "app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml"
- "app/views/shared/_registration_features_discovery_message.html.haml"
- "app/views/shared/_service_ping_consent.html.haml"
- "app/views/shared/deploy_tokens/_form.html.haml"
- "app/views/shared/deploy_tokens/_table.html.haml"
- "app/views/shared/empty_states/_snippets.html.haml"
- "app/views/shared/web_hooks/_form.html.haml"
- "ee/app/views/admin/application_settings/_custom_templates_form.html.haml"
- "ee/app/views/admin/application_settings/_elasticsearch_form.html.haml"
- "ee/app/views/admin/application_settings/_microsoft_application.haml"
- "ee/app/views/admin/application_settings/_templates.html.haml"
- "ee/app/views/admin/push_rules/_merge_request_approvals.html.haml"
- "ee/app/views/admin/push_rules/_merge_request_approvals_fields.html.haml"
- "ee/app/views/compliance_management/compliance_framework/_project_settings.html.haml"
- "ee/app/views/groups/_custom_project_templates_setting.html.haml"
- "ee/app/views/groups/_templates_setting.html.haml"
- "ee/app/views/profiles/preferences/_code_suggestions_settings_self_assignment.html.haml"
- "ee/app/views/projects/settings/ci_cd/_auto_rollback.html.haml"
- "ee/app/views/projects/settings/ci_cd/_pipeline_subscriptions.html.haml"
- "ee/app/views/projects/settings/ci_cd/_protected_environments.html.haml"
- "ee/app/views/projects/settings/merge_requests/_merge_request_approvals_settings.html.haml"
- "ee/app/views/projects/settings/merge_requests/_target_branch_rules_settings.html.haml"
- "ee/app/views/shared/_new_user_signups_cap_reached_alert.html.haml"
- "ee/app/views/shared/promotions/_promote_mobile_devops.html.haml"

View File

@ -1,12 +1,18 @@
import { Configuration, WatchApi, EVENT_DATA } from '@gitlab/cluster-client';
import axios from '~/lib/utils/axios_utils';
import {
HELM_RELEASES_RESOURCE_TYPE,
KUSTOMIZATIONS_RESOURCE_TYPE,
} from '~/environments/constants';
import fluxKustomizationStatusQuery from '../queries/flux_kustomization_status.query.graphql';
import fluxHelmReleaseStatusQuery from '../queries/flux_helm_release_status.query.graphql';
const helmReleasesApiVersion = 'helm.toolkit.fluxcd.io/v2beta1';
const kustomizationsApiVersion = 'kustomize.toolkit.fluxcd.io/v1beta1';
const helmReleaseField = 'fluxHelmReleaseStatus';
const kustomizationField = 'fluxKustomizationStatus';
const handleClusterError = (err) => {
const error = err?.response?.data?.message ? new Error(err.response.data.message) : err;
throw error;
@ -22,14 +28,57 @@ const buildFluxResourceUrl = ({
return `${basePath}/apis/${apiVersion}/namespaces/${namespace}/${resourceType}/${environmentName}`;
};
const getFluxResourceStatus = (configuration, url) => {
const { headers } = configuration;
const buildFluxResourceWatchPath = ({ namespace, apiVersion, resourceType }) => {
return `/apis/${apiVersion}/namespaces/${namespace}/${resourceType}`;
};
const watchFluxResource = ({ watchPath, resourceName, query, variables, field, client }) => {
const config = new Configuration(variables.configuration);
const watcherApi = new WatchApi(config);
const fieldSelector = `metadata.name=${decodeURIComponent(resourceName)}`;
watcherApi
.subscribeToStream(watchPath, { watch: true, fieldSelector })
.then((watcher) => {
let result = [];
watcher.on(EVENT_DATA, (data) => {
result = data[0]?.status?.conditions;
client.writeQuery({
query,
variables,
data: { [field]: result },
});
});
})
.catch((err) => {
handleClusterError(err);
});
};
const getFluxResourceStatus = ({ url, watchPath, query, variables, field, client }) => {
const { headers } = variables.configuration;
const withCredentials = true;
return axios
.get(url, { withCredentials, headers })
.then((res) => {
return res?.data?.status?.conditions || [];
const fluxData = res?.data;
const resourceName = fluxData?.metadata?.name;
if (gon.features?.k8sWatchApi && resourceName) {
watchFluxResource({
watchPath,
resourceName,
query,
variables,
field,
client,
});
}
return fluxData?.status?.conditions || [];
})
.catch((err) => {
handleClusterError(err);
@ -62,7 +111,16 @@ const getFluxResources = (configuration, url) => {
};
export default {
fluxKustomizationStatus(_, { configuration, namespace, environmentName, fluxResourcePath = '' }) {
fluxKustomizationStatus(
_,
{ configuration, namespace, environmentName, fluxResourcePath = '' },
{ client },
) {
const watchPath = buildFluxResourceWatchPath({
namespace,
apiVersion: kustomizationsApiVersion,
resourceType: KUSTOMIZATIONS_RESOURCE_TYPE,
});
let url;
if (fluxResourcePath) {
@ -76,9 +134,25 @@ export default {
environmentName,
});
}
return getFluxResourceStatus(configuration, url);
return getFluxResourceStatus({
url,
watchPath,
query: fluxKustomizationStatusQuery,
variables: { configuration, namespace, environmentName, fluxResourcePath },
field: kustomizationField,
client,
});
},
fluxHelmReleaseStatus(_, { configuration, namespace, environmentName, fluxResourcePath }) {
fluxHelmReleaseStatus(
_,
{ configuration, namespace, environmentName, fluxResourcePath },
{ client },
) {
const watchPath = buildFluxResourceWatchPath({
namespace,
apiVersion: helmReleasesApiVersion,
resourceType: HELM_RELEASES_RESOURCE_TYPE,
});
let url;
if (fluxResourcePath) {
@ -92,7 +166,14 @@ export default {
environmentName,
});
}
return getFluxResourceStatus(configuration, url);
return getFluxResourceStatus({
url,
watchPath,
query: fluxHelmReleaseStatusQuery,
variables: { configuration, namespace, environmentName, fluxResourcePath },
field: helmReleaseField,
client,
});
},
fluxKustomizations(_, { configuration, namespace }) {
const url = buildFluxResourceUrl({

View File

@ -5,6 +5,10 @@ import { initProfileTabs } from '~/profile';
import UserTabs from './user_tabs';
function initUserProfile(action) {
// TODO: Remove both Vue and legacy JS tabs code/feature flag uses with the
// removal of the old navigation.
// See https://gitlab.com/groups/gitlab-org/-/epics/11875.
if (gon.features?.profileTabsVue) {
initProfileTabs();
} else {

View File

@ -1,3 +1,6 @@
// TODO: Remove this with the removal of the old navigation.
// See https://gitlab.com/groups/gitlab-org/-/epics/11875.
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import $ from 'jquery';
import Activities from '~/activities';

View File

@ -51,7 +51,6 @@ export default {
lineHighlighter: new LineHighlighter(),
blameData: [],
renderedChunks: [],
overlappingBlameRequested: false,
};
},
computed: {
@ -72,6 +71,7 @@ export default {
showBlame: {
handler(shouldShow) {
toggleBlameClasses(this.blameData, shouldShow);
this.requestBlameInfo(this.renderedChunks[0]);
},
immediate: true,
},
@ -92,32 +92,36 @@ export default {
this.selectLine();
},
methods: {
async handleChunkAppear(chunkIndex) {
const chunk = this.chunks[chunkIndex];
async handleChunkAppear(chunkIndex, handleOverlappingChunk = true) {
if (!this.renderedChunks.includes(chunkIndex)) {
this.renderedChunks.push(chunkIndex);
await this.requestBlameInfo(chunkIndex);
const { data } = await this.$apollo.query({
query: blameDataQuery,
variables: {
fullPath: this.projectPath,
filePath: this.blob.path,
fromLine: chunk.startingFrom + 1,
toLine: chunk.startingFrom + chunk.totalLines,
},
});
const blob = data?.project?.repository?.blobs?.nodes[0];
const blameGroups = blob?.blame?.groups;
if (blameGroups) this.blameData.push(...blameGroups);
if (chunkIndex > 0 && !this.overlappingBlameRequested) {
if (chunkIndex > 0 && handleOverlappingChunk) {
// request the blame information for overlapping chunk incase it is visible in the DOM
this.handleChunkAppear(chunkIndex - 1);
this.overlappingBlameRequested = true;
this.handleChunkAppear(chunkIndex - 1, false);
}
}
},
async requestBlameInfo(chunkIndex) {
const chunk = this.chunks[chunkIndex];
if (!this.showBlame || !chunk) return;
const { data } = await this.$apollo.query({
query: blameDataQuery,
variables: {
fullPath: this.projectPath,
filePath: this.blob.path,
fromLine: chunk.startingFrom + 1,
toLine: chunk.startingFrom + chunk.totalLines,
},
});
const blob = data?.project?.repository?.blobs?.nodes[0];
const blameGroups = blob?.blame?.groups;
const isDuplicate = this.blameData.includes(blameGroups[0]);
if (blameGroups && !isDuplicate) this.blameData.push(...blameGroups);
},
async selectLine() {
await this.$nextTick();
this.lineHighlighter.highlightHash(this.$route.hash);

View File

@ -10,7 +10,7 @@ const findLineContentElement = (lineNumber) => document.getElementById(`LC${line
export const calculateBlameOffset = (lineNumber) => {
if (lineNumber === 1) return '0px';
const blobViewerOffset = document.querySelector(VIEWER_SELECTOR)?.getBoundingClientRect().top;
const lineContentOffset = findLineContentElement(lineNumber).getBoundingClientRect().top;
const lineContentOffset = findLineContentElement(lineNumber)?.getBoundingClientRect().top;
return `${lineContentOffset - blobViewerOffset}px`;
};

View File

@ -4,12 +4,6 @@ html {
&.touch .tooltip {
display: none !important;
}
@include media-breakpoint-up(sm) {
&.logged-out-marketing-header {
--header-height: 72px;
}
}
}
body {

View File

@ -318,7 +318,6 @@ module ApplicationHelper
class_names << 'with-header' if !show_super_sidebar? || !current_user
class_names << 'with-top-bar' if show_super_sidebar? && !@hide_top_bar_padding
class_names << system_message_class
class_names << 'logged-out-marketing-header' if !current_user && ::Gitlab.com? && !show_super_sidebar?
class_names
end

View File

@ -80,9 +80,7 @@ module NavHelper
def show_super_sidebar?(user = current_user)
# The new sidebar is not enabled for anonymous use
# Once we enable the new sidebar by default, this
# should return true
return Feature.enabled?(:super_sidebar_logged_out) unless user
return true unless user
# Users who get the new nav unless they explicitly
# opt-out via the toggle

View File

@ -28,3 +28,5 @@ module Ci
end
end
end
Ci::DestroyPipelineService.prepend_mod

View File

@ -11,7 +11,7 @@
.form-group
= f.label :time_tracking, _('Time tracking'), class: 'label-bold'
- time_tracking_help_link = help_page_path('user/project/time_tracking.md')
- time_tracking_help_link = help_page_path('user/project/time_tracking')
- time_tracking_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: time_tracking_help_link }
= f.gitlab_ui_checkbox_component :time_tracking_limit_to_hours, _('Limit display of time tracking units to hours.'), help_text: _('Display time tracking in issues in total hours only. %{link_start}What is time tracking?%{link_end}').html_safe % { link_start: time_tracking_help_link_start, link_end: '</a>'.html_safe }

View File

@ -41,7 +41,7 @@
alert_options: { class: 'gl-mb-3' },
dismissible: false) do |c|
- c.with_body do
= link_to _('Try the troubleshooting steps here.'), help_page_path('user/profile/account/two_factor_authentication.md', anchor: 'troubleshooting'), target: '_blank', rel: 'noopener noreferrer'
= link_to _('Try the troubleshooting steps here.'), help_page_path('user/profile/account/two_factor_authentication', anchor: 'troubleshooting'), target: '_blank', rel: 'noopener noreferrer'
- if current_password_required?
.form-group
@ -130,7 +130,7 @@
alert_options: { class: 'gl-mb-3' },
dismissible: false) do |c|
- c.with_body do
= link_to _('Try the troubleshooting steps here.'), help_page_path('user/profile/account/two_factor_authentication.md', anchor: 'troubleshooting'), target: '_blank', rel: 'noopener noreferrer'
= link_to _('Try the troubleshooting steps here.'), help_page_path('user/profile/account/two_factor_authentication', anchor: 'troubleshooting'), target: '_blank', rel: 'noopener noreferrer'
.js-manage-two-factor-form{ data: { current_password_required: current_password_required?.to_s, profile_two_factor_auth_path: profile_two_factor_auth_path, profile_two_factor_auth_method: 'delete', codes_profile_two_factor_auth_path: codes_profile_two_factor_auth_path, codes_profile_two_factor_auth_method: 'post' } }
- else
%p

View File

@ -9,5 +9,5 @@
%p.form-text.text-muted
= s_('ProjectSettings|Leave empty to use default template.')
= sprintf(s_('ProjectSettings|Maximum %{maxLength} characters.'), { maxLength: Project::MAX_COMMIT_TEMPLATE_LENGTH })
- link = link_to('', help_page_path('user/project/merge_requests/commit_templates.md'), target: '_blank', rel: 'noopener noreferrer')
- link = link_to('', help_page_path('user/project/merge_requests/commit_templates'), target: '_blank', rel: 'noopener noreferrer')
= safe_format(s_('ProjectSettings|%{link_start}What variables can I use?%{link_end}'), tag_pair(link, :link_start, :link_end))

View File

@ -9,5 +9,5 @@
%p.form-text.text-muted
= s_('ProjectSettings|Leave empty to use default template.')
= sprintf(s_('ProjectSettings|Maximum %{maxLength} characters.'), { maxLength: Project::MAX_SUGGESTIONS_TEMPLATE_LENGTH })
- link = link_to('', help_page_path('user/project/merge_requests/reviews/suggestions.md', anchor: 'configure-the-commit-message-for-applied-suggestions'), target: '_blank', rel: 'noopener noreferrer')
- link = link_to('', help_page_path('user/project/merge_requests/reviews/suggestions', anchor: 'configure-the-commit-message-for-applied-suggestions'), target: '_blank', rel: 'noopener noreferrer')
= safe_format(s_('ProjectSettings|%{link_start}What variables can I use?%{link_end}'), tag_pair(link, :link_start, :link_end))

View File

@ -9,5 +9,5 @@
%p.form-text.text-muted
= s_('ProjectSettings|Leave empty to use default template.')
= sprintf(s_('ProjectSettings|Maximum %{maxLength} characters.'), { maxLength: Project::MAX_COMMIT_TEMPLATE_LENGTH })
- link = link_to('', help_page_path('user/project/merge_requests/commit_templates.md'), target: '_blank', rel: 'noopener noreferrer')
- link = link_to('', help_page_path('user/project/merge_requests/commit_templates'), target: '_blank', rel: 'noopener noreferrer')
= safe_format(s_('ProjectSettings|%{link_start}What variables can I use?%{link_end}'), tag_pair(link, :link_start, :link_end))

View File

@ -14,7 +14,7 @@
.col-sm-12
%p.gl-text-secondary
= s_('UsageQuota|Usage of project resources across the %{strong_start}%{project_name}%{strong_end} project').html_safe % { strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe, project_name: @project.name } + '.'
%a{ href: help_page_path('user/usage_quotas.md'), target: '_blank', rel: 'noopener noreferrer' }
%a{ href: help_page_path('user/usage_quotas'), target: '_blank', rel: 'noopener noreferrer' }
= s_('UsageQuota|Learn more about usage quotas') + '.'
= gl_tabs_nav({ id: 'js-project-usage-quotas-tabs' }) do

View File

@ -9,4 +9,4 @@
= _('Container registry is not enabled on this GitLab instance. Ask an administrator to enable it in order for Auto DevOps to work.')
- c.with_actions do
= link_button_to _('Settings'), project_settings_ci_cd_path(project), class: 'alert-link', variant: :confirm
= link_button_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank', class: 'alert-link gl-ml-3'
= link_button_to _('More information'), help_page_path('topics/autodevops/index'), target: '_blank', class: 'alert-link gl-ml-3'

View File

@ -1,5 +1,5 @@
- feature_title = local_assigns.fetch(:feature_title, s_('RegistrationFeatures|use this feature'))
- registration_features_docs_path = help_page_path('administration/settings/usage_statistics.md', anchor: 'registration-features-program')
- registration_features_docs_path = help_page_path('administration/settings/usage_statistics', anchor: 'registration-features-program')
- registration_features_link_start = '<a href="%{url}" target="_blank">'.html_safe % { url: registration_features_docs_path }
%div

View File

@ -1,7 +1,7 @@
- if session[:ask_for_usage_stats_consent]
= render Pajamas::AlertComponent.new(alert_options: { class: 'service-ping-consent-message' }) do |c|
- c.with_body do
- docs_link = link_to '', help_page_path('administration/settings/usage_statistics.md'), class: 'gl-link'
- docs_link = link_to '', help_page_path('administration/settings/usage_statistics'), class: 'gl-link'
- settings_link = link_to '', metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings'), class: 'gl-link'
= safe_format s_('ServicePing|To help improve GitLab, we would like to periodically %{link_start}collect usage information%{link_end}.'), tag_pair(docs_link, :link_start, :link_end)
= safe_format s_('ServicePing|This can be changed at any time in %{link_start}your settings%{link_end}.'), tag_pair(settings_link, :link_start, :link_end)

View File

@ -1,5 +1,5 @@
%p
- link = link_to('', help_page_path('user/project/deploy_tokens/index.md'), target: '_blank', rel: 'noopener noreferrer')
- link = link_to('', help_page_path('user/project/deploy_tokens/index'), target: '_blank', rel: 'noopener noreferrer')
= safe_format(s_('DeployTokens|Create a new deploy token for all projects in this group. %{link_start}What are deploy tokens?%{link_end}'), tag_pair(link, :link_start, :link_end))
= gitlab_ui_form_for token, url: create_deploy_token_path(group_or_project, anchor: 'js-deploy-tokens'), method: :post, remote: true do |f|

View File

@ -16,7 +16,7 @@
packages_registry_enabled: packages_registry_enabled?(group_or_project),
create_new_token_path: create_deploy_token_path(group_or_project),
token_type: group_or_project.is_a?(Group) ? 'group' : 'project',
deploy_tokens_help_url: help_page_path('user/project/deploy_tokens/index.md')
deploy_tokens_help_url: help_page_path('user/project/deploy_tokens/index')
}
}
- if active_tokens.present?

View File

@ -13,6 +13,6 @@
.gl-mt-3<
- if button_path
= link_button_to s_('SnippetsEmptyState|New snippet'), button_path, title: s_('SnippetsEmptyState|New snippet'), id: 'new_snippet_link', data: { testid: 'create-first-snippet-link' }, variant: :confirm
= link_button_to s_('SnippetsEmptyState|Documentation'), help_page_path('user/snippets.md'), title: s_('SnippetsEmptyState|Documentation')
= link_button_to s_('SnippetsEmptyState|Documentation'), help_page_path('user/snippets'), title: s_('SnippetsEmptyState|Documentation')
- else
%h4.gl-text-center= s_('SnippetsEmptyState|There are no snippets to show.')

View File

@ -66,7 +66,7 @@
help_text: s_('Webhooks|A release is created, updated, or deleted.')
- if Feature.enabled?(:emoji_webhooks, hook.parent)
%li.gl-pb-5
- emoji_help_link = link_to s_('Which emoji events trigger webhooks'), help_page_path('user/project/integrations/webhook_events.md', anchor: 'emoji-events')
- emoji_help_link = link_to s_('Which emoji events trigger webhooks'), help_page_path('user/project/integrations/webhook_events', anchor: 'emoji-events')
= form.gitlab_ui_checkbox_component :emoji_events,
integration_webhook_event_human_name(:emoji_events),
help_text: s_('Webhooks|An emoji is awarded or revoked. %{help_link}?').html_safe % { help_link: emoji_help_link }

View File

@ -114,6 +114,8 @@
%p.profile-user-bio.gl-mb-3
= @user.bio
-# TODO: Remove this with the removal of the old navigation.
-# See https://gitlab.com/groups/gitlab-org/-/epics/11875.
- if !profile_tabs.empty? && !Feature.enabled?(:profile_tabs_vue, current_user)
.scrolling-tabs-container{ class: [('gl-display-none' if show_super_sidebar?)] }
%button.fade-left{ type: 'button', title: _('Scroll left'), 'aria-label': _('Scroll left') }

View File

@ -1,8 +0,0 @@
---
name: super_sidebar_logged_out
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127756
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/419936
milestone: '16.3'
type: development
group: group::foundations
default_enabled: false

View File

@ -172,6 +172,7 @@ Audit event types belong to the following product categories.
| [`ci_variable_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91983) | Triggered when a CI variable is created at a project level| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.2](https://gitlab.com/gitlab-org/gitlab/-/issues/363090) |
| [`ci_variable_deleted`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91983) | Triggered when a project's CI variable is deleted| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.2](https://gitlab.com/gitlab-org/gitlab/-/issues/363090) |
| [`ci_variable_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91983) | Triggered when a project's CI variable is updated| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.2](https://gitlab.com/gitlab-org/gitlab/-/issues/363090) |
| [`destroy_pipeline`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135255) | Event triggered when a pipeline is deleted| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.6](https://gitlab.com/gitlab-org/gitlab/-/issues/339041) |
### Deployment management

View File

@ -64,7 +64,7 @@ On GitLab.com, this feature is not available.
View a dashboard to see the status of any connected clusters.
If the `k8s_watch_api` feature flag is enabled, the status of your
pods updates in real time.
pods and Flux reconciliation updates in real time.
To view a configured dashboard:

View File

@ -17,6 +17,9 @@ module.exports = (path, options = {}) => {
moduleNameMapper: extModuleNameMapper = {},
moduleNameMapperEE: extModuleNameMapperEE = {},
moduleNameMapperJH: extModuleNameMapperJH = {},
roots: extRoots = [],
rootsEE: extRootsEE = [],
rootsJH: extRootsJH = [],
} = options;
const reporters = ['default'];
@ -266,5 +269,11 @@ module.exports = (path, options = {}) => {
'<rootDir>/spec/frontend/__helpers__/html_string_serializer.js',
'<rootDir>/spec/frontend/__helpers__/clean_html_element_serializer.js',
],
roots: [
'<rootDir>/app/assets/javascripts/',
...extRoots,
...(IS_EE ? ['<rootDir>/ee/app/assets/javascripts/', ...extRootsEE] : []),
...(IS_JH ? ['<rootDir>/jh/app/assets/javascripts/', ...extRootsJH] : []),
],
};
};

View File

@ -23,6 +23,10 @@ module.exports = {
moduleNameMapperJH: {
'^jh_else_ce_test_helpers(/.*)$': '<rootDir>/jh/spec/frontend_integration/test_helpers$1',
},
// We need to include spec/frontend in `roots` for the __mocks__ to be found
roots: ['<rootDir>/spec/frontend_integration/', '<rootDir>/spec/frontend/'],
rootsEE: ['<rootDir>/ee/spec/frontend_integration/'],
rootsJH: ['<rootDir>/jh/spec/frontend_integration/'],
}),
fakeTimers: {
enableGlobally: false,

View File

@ -9,6 +9,10 @@ if (IS_JH && fs.existsSync('./jh/jest.config.js')) {
module.exports = require('./jh/jest.config');
} else {
module.exports = {
...baseConfig('spec/frontend'),
...baseConfig('spec/frontend', {
roots: ['<rootDir>/spec/frontend'],
rootsEE: ['<rootDir>/ee/spec/frontend'],
rootsJH: ['<rootDir>/jh/spec/frontend'],
}),
};
}

8
jest.config.scripts.js Normal file
View File

@ -0,0 +1,8 @@
const baseConfig = require('./jest.config.base');
module.exports = {
...baseConfig('spec/frontend', {
roots: ['<rootDir>/scripts/lib', '<rootDir>/spec/frontend'],
}),
testMatch: [],
};

View File

@ -18,7 +18,7 @@ module Sidebars
override :extra_container_html_options
def extra_container_html_options
{
class: 'shortcuts-pipelines rspec-link-pipelines'
class: 'shortcuts-pipelines'
}
end

View File

@ -22,7 +22,7 @@ module Sidebars
override :extra_container_html_options
def extra_container_html_options
{
class: 'shortcuts-project rspec-project-link'
class: 'shortcuts-project'
}
end

View File

@ -50388,7 +50388,7 @@ msgstr ""
msgid "Tracing|Time range"
msgstr ""
msgid "Tracing|Toggle children spans"
msgid "Tracing|Toggle child spans"
msgstr ""
msgid "Tracing|Trace ID"

View File

@ -16,6 +16,7 @@
"jest:ci:predictive": "jest --config jest.config.js --ci --coverage --findRelatedTests $(cat $RSPEC_CHANGED_FILES_PATH) $(cat $RSPEC_MATCHING_JS_FILES_PATH) --passWithNoTests --testSequencer ./scripts/frontend/parallel_ci_sequencer.js",
"jest:contract": "PACT_DO_NOT_TRACK=true jest --config jest.config.contract.js --runInBand",
"jest:integration": "jest --config jest.config.integration.js",
"jest:scripts": "jest --config jest.config.scripts.js",
"jest:quarantine": "grep -r 'quarantine:' spec/frontend ee/spec/frontend",
"lint:eslint": "node scripts/frontend/eslint.js",
"lint:eslint:fix": "node scripts/frontend/eslint.js --fix",

View File

@ -289,7 +289,7 @@ module Glfm
wysiwyg_html_and_json_tempfile_path = Dir::Tmpname.create(WYSIWYG_HTML_AND_JSON_TEMPFILE_BASENAME) {}
ENV['OUTPUT_WYSIWYG_HTML_AND_JSON_TEMPFILE_PATH'] = wysiwyg_html_and_json_tempfile_path
cmd = "yarn jest --testMatch '**/render_wysiwyg_html_and_json.js' #{__dir__}/render_wysiwyg_html_and_json.js"
cmd = "yarn jest:scripts #{__dir__}/render_wysiwyg_html_and_json.js"
run_external_cmd(cmd)
output("Reading generated WYSIWYG HTML and prosemirror JSON from tempfile " \

View File

@ -56,10 +56,10 @@ gitlab:
# Based on https://console.cloud.google.com/monitoring/metrics-explorer;duration=P14D?pageState=%7B%22xyChart%22:%7B%22constantLines%22:%5B%5D,%22dataSets%22:%5B%7B%22plotType%22:%22LINE%22,%22targetAxis%22:%22Y1%22,%22timeSeriesFilter%22:%7B%22aggregations%22:%5B%7B%22crossSeriesReducer%22:%22REDUCE_NONE%22,%22groupByFields%22:%5B%5D,%22perSeriesAligner%22:%22ALIGN_RATE%22%7D,%7B%22crossSeriesReducer%22:%22REDUCE_NONE%22,%22groupByFields%22:%5B%5D,%22perSeriesAligner%22:%22ALIGN_MEAN%22%7D%5D,%22apiSource%22:%22DEFAULT_CLOUD%22,%22crossSeriesReducer%22:%22REDUCE_NONE%22,%22filter%22:%22metric.type%3D%5C%22kubernetes.io%2Fcontainer%2Fcpu%2Fcore_usage_time%5C%22%20resource.type%3D%5C%22k8s_container%5C%22%20resource.label.%5C%22container_name%5C%22%3D%5C%22gitlab-shell%5C%22%22,%22groupByFields%22:%5B%5D,%22minAlignmentPeriod%22:%2260s%22,%22perSeriesAligner%22:%22ALIGN_RATE%22,%22secondaryCrossSeriesReducer%22:%22REDUCE_NONE%22,%22secondaryGroupByFields%22:%5B%5D%7D%7D%5D,%22options%22:%7B%22mode%22:%22STATS%22%7D,%22y1Axis%22:%7B%22label%22:%22%22,%22scale%22:%22LINEAR%22%7D%7D%7D&project=gitlab-review-apps
cpu: 12m
# Based on https://console.cloud.google.com/monitoring/metrics-explorer;duration=P14D?pageState=%7B%22xyChart%22:%7B%22constantLines%22:%5B%5D,%22dataSets%22:%5B%7B%22plotType%22:%22LINE%22,%22targetAxis%22:%22Y1%22,%22timeSeriesFilter%22:%7B%22aggregations%22:%5B%7B%22crossSeriesReducer%22:%22REDUCE_NONE%22,%22groupByFields%22:%5B%5D,%22perSeriesAligner%22:%22ALIGN_MEAN%22%7D%5D,%22apiSource%22:%22DEFAULT_CLOUD%22,%22crossSeriesReducer%22:%22REDUCE_NONE%22,%22filter%22:%22metric.type%3D%5C%22kubernetes.io%2Fcontainer%2Fmemory%2Fused_bytes%5C%22%20resource.type%3D%5C%22k8s_container%5C%22%20resource.label.%5C%22container_name%5C%22%3D%5C%22gitlab-shell%5C%22%22,%22groupByFields%22:%5B%5D,%22minAlignmentPeriod%22:%2260s%22,%22perSeriesAligner%22:%22ALIGN_MEAN%22%7D%7D%5D,%22options%22:%7B%22mode%22:%22STATS%22%7D,%22y1Axis%22:%7B%22label%22:%22%22,%22scale%22:%22LINEAR%22%7D%7D%7D&project=gitlab-review-apps
memory: 20Mi
memory: 50Mi
limits:
cpu: 90m
memory: 40Mi
cpu: 24m
memory: 100Mi
minReplicas: 1
maxReplicas: 1
hpa:

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe 'Contributions Calendar', :js, feature_category: :user_profile do
include MobileHelpers
let(:user) { create(:user, :no_super_sidebar) }
let(:user) { create(:user) }
let(:contributed_project) { create(:project, :public, :repository) }
let(:issue_note) { create(:note, project: contributed_project) }
@ -83,7 +83,6 @@ RSpec.describe 'Contributions Calendar', :js, feature_category: :user_profile do
shared_context 'when user page is visited' do
before do
visit user.username
page.click_link('Overview')
wait_for_requests
end
end

View File

@ -4,39 +4,19 @@ require 'spec_helper'
RSpec.describe 'Contextual sidebar', :js, feature_category: :remote_development do
context 'when context is a project' do
let_it_be(:user) { create(:user, :no_super_sidebar) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, namespace: user.namespace) }
before do
sign_in(user)
visit project_path(project)
end
context 'when analyzing the menu' do
before do
visit project_path(project)
end
it 'shows flyout menu on other section on hover' do
expect(page).not_to have_link('Pipelines', href: project_pipelines_path(project))
it 'shows flyout navs when collapsed or expanded apart from on the active item when expanded', :aggregate_failures do
expect(page).not_to have_selector('.js-sidebar-collapsed')
find('.rspec-link-pipelines').hover
expect(page).to have_selector('.is-showing-fly-out')
find('.rspec-project-link').hover
expect(page).not_to have_selector('.is-showing-fly-out')
find('.rspec-toggle-sidebar').click
find('.rspec-link-pipelines').hover
expect(page).to have_selector('.is-showing-fly-out')
find('.rspec-project-link').hover
expect(page).to have_selector('.is-showing-fly-out')
end
find_button('Build').hover
expect(page).to have_link('Pipelines', href: project_pipelines_path(project))
end
end
end

View File

@ -1,43 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'The group dashboard', :js, feature_category: :groups_and_projects do
include ExternalAuthorizationServiceHelpers
include Features::TopNavSpecHelpers
let(:user) { create(:user, :no_super_sidebar) }
before do
sign_in user
end
describe 'The top navigation' do
it 'has all the expected links' do
visit dashboard_groups_path
open_top_nav
within_top_nav do
expect(page).to have_button('Projects')
expect(page).to have_button('Groups')
expect(page).to have_link('Your work')
expect(page).to have_link('Explore')
end
end
it 'hides some links when an external authorization service is enabled' do
enable_external_authorization_service_check
visit dashboard_groups_path
open_top_nav
within_top_nav do
expect(page).to have_button('Projects')
expect(page).to have_button('Groups')
expect(page).to have_link('Your work')
expect(page).to have_link('Explore')
end
end
end
end

View File

@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'Navigation bar counter', :use_clean_rails_memory_store_caching, feature_category: :team_planning do
let(:user) { create(:user, :no_super_sidebar) }
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
let(:issue) { create(:issue, project: project) }
let(:merge_request) { create(:merge_request, source_project: project) }
@ -17,33 +17,29 @@ RSpec.describe 'Navigation bar counter', :use_clean_rails_memory_store_caching,
it 'reflects dashboard issues count', :js do
visit issues_path
expect_counters('issues', '1', n_("%d assigned issue", "%d assigned issues", 1) % 1)
expect_issue_count(1)
issue.update!(assignees: [])
Users::AssignedIssuesCountService.new(current_user: user).delete_cache
user.invalidate_cache_counts
travel_to(3.minutes.from_now) do
visit issues_path
visit issues_path
expect_counters('issues', '0', n_("%d assigned issue", "%d assigned issues", 0) % 0)
end
expect_issue_count(0)
end
it 'reflects dashboard merge requests count', :js do
visit merge_requests_path
expect_counters('merge_requests', '1', n_("%d merge request", "%d merge requests", 1) % 1)
expect_merge_request_count(1)
merge_request.update!(assignees: [])
user.invalidate_cache_counts
travel_to(3.minutes.from_now) do
visit merge_requests_path
visit merge_requests_path
expect_counters('merge_requests', '0', n_("%d merge request", "%d merge requests", 0) % 0)
end
expect_merge_request_count(0)
end
def issues_path
@ -54,11 +50,21 @@ RSpec.describe 'Navigation bar counter', :use_clean_rails_memory_store_caching,
merge_requests_dashboard_path(assignee_username: user.username)
end
def expect_counters(issuable_type, count, badge_label)
def expect_issue_count(count)
dashboard_count = find('.gl-tabs-nav li a.active')
expect(dashboard_count).to have_content(count)
expect(page).to have_css(".dashboard-shortcuts-#{issuable_type}", visible: :all, text: count)
expect(page).to have_css("span[aria-label='#{badge_label}']", visible: :all, text: count)
within_testid('super-sidebar') do
expect(page).to have_link("Issues #{count}")
end
end
def expect_merge_request_count(count)
dashboard_count = find('.gl-tabs-nav li a.active')
expect(dashboard_count).to have_content(count)
within_testid('super-sidebar') do
expect(page).to have_button("Merge requests #{count}")
end
end
end

View File

@ -55,7 +55,7 @@ RSpec.describe 'Dashboard snippets', :js, feature_category: :source_code_managem
it 'shows documentation button in main comment area' do
parent_element = page.find('.row.empty-state')
expect(parent_element).to have_link('Documentation', href: help_page_path('user/snippets.md'))
expect(parent_element).to have_link('Documentation', href: help_page_path('user/snippets'))
end
end

View File

@ -3,9 +3,7 @@
require 'spec_helper'
RSpec.describe 'Global search', :js, feature_category: :global_search do
include AfterNextHelpers
let_it_be(:user) { create(:user, :no_super_sidebar) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, namespace: user.namespace) }
before do
@ -18,17 +16,18 @@ RSpec.describe 'Global search', :js, feature_category: :global_search do
visit dashboard_projects_path
end
it 'renders updated search bar' do
expect(page).to have_no_selector('.search-form')
expect(page).to have_selector('#js-header-search')
it 'renders search button' do
expect(page).to have_button('Search or go to…')
end
it 'focuses search input when shortcut "s" is pressed' do
expect(page).not_to have_selector('#search:focus')
it 'opens search modal when shortcut "s" is pressed' do
search_selector = 'input[type="search"]:focus'
expect(page).not_to have_selector(search_selector)
find('body').native.send_key('s')
expect(page).to have_selector('#search:focus')
expect(page).to have_selector(search_selector)
end
end
end

View File

@ -26,8 +26,10 @@ RSpec.describe 'Group merge requests page', feature_category: :code_review_workf
expect(page).not_to have_content(issuable_archived.title)
end
it 'ignores archived merge request count badges in navbar' do
expect(first(:link, text: 'Merge requests').find('.badge').text).to eq("1")
it 'ignores archived merge request count badges in navbar', :js do
within_testid('super-sidebar') do
expect(find_link(text: 'Merge requests').find('.badge').text).to eq("1")
end
end
it 'ignores archived merge request count badges in state-filters' do

View File

@ -2,9 +2,9 @@
require 'spec_helper'
RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures, feature_category: :shared do
RSpec.describe 'Monitor dropdown sidebar', :js, feature_category: :shared do
let_it_be_with_reload(:project) { create(:project, :internal, :repository) }
let_it_be(:user) { create(:user, :no_super_sidebar) }
let_it_be(:user) { create(:user) }
let(:role) { nil }
@ -13,7 +13,7 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures, feature_category
sign_in(user)
end
shared_examples 'shows Monitor menu based on the access level' do
shared_examples 'shows common Monitor menu item based on the access level' do
using RSpec::Parameterized::TableSyntax
let(:enabled) { Featurable::PRIVATE }
@ -30,10 +30,14 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures, feature_category
visit project_issues_path(project)
if render
expect(page).to have_selector('a.shortcuts-monitor', text: 'Monitor')
else
expect(page).not_to have_selector('a.shortcuts-monitor')
click_button('Monitor')
within_testid('super-sidebar') do
if render
expect(page).to have_link('Incidents')
else
expect(page).not_to have_link('Incidents', visible: :all)
end
end
end
end
@ -44,32 +48,35 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures, feature_category
before do
project.project_feature.update_attribute(:monitor_access_level, access_level)
visit project_issues_path(project)
click_button('Monitor')
end
it 'has the correct `Monitor` menu items', :aggregate_failures do
visit project_issues_path(project)
expect(page).to have_selector('a.shortcuts-monitor', text: 'Monitor')
it 'has the correct `Monitor` and `Operate` menu items' do
expect(page).to have_link('Incidents', href: project_incidents_path(project))
click_button('Operate')
expect(page).to have_link('Environments', href: project_environments_path(project))
expect(page).not_to have_link('Alerts', href: project_alert_management_index_path(project))
expect(page).not_to have_link('Error Tracking', href: project_error_tracking_index_path(project))
expect(page).not_to have_link('Kubernetes', href: project_clusters_path(project))
expect(page).not_to have_link('Alerts', href: project_alert_management_index_path(project), visible: :all)
expect(page).not_to have_link('Error Tracking', href: project_error_tracking_index_path(project), visible: :all)
expect(page).not_to have_link('Kubernetes clusters', href: project_clusters_path(project), visible: :all)
end
context 'when monitor project feature is PRIVATE' do
let(:access_level) { ProjectFeature::PRIVATE }
it 'does not show the `Monitor` menu' do
expect(page).not_to have_selector('a.shortcuts-monitor')
it 'does not show common items of the `Monitor` menu' do
expect(page).not_to have_link('Error Tracking', href: project_incidents_path(project), visible: :all)
end
end
context 'when monitor project feature is DISABLED' do
let(:access_level) { ProjectFeature::DISABLED }
it 'does not show the `Monitor` menu' do
expect(page).not_to have_selector('a.shortcuts-monitor')
it 'does not show the `Incidents` menu' do
expect(page).not_to have_link('Error Tracking', href: project_incidents_path(project), visible: :all)
end
end
end
@ -77,63 +84,86 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures, feature_category
context 'when user has guest role' do
let(:role) { :guest }
it 'has the correct `Monitor` menu items' do
it 'has the correct `Monitor` and `Operate` menu items' do
visit project_issues_path(project)
expect(page).to have_selector('a.shortcuts-monitor', text: 'Monitor')
click_button('Monitor')
expect(page).to have_link('Incidents', href: project_incidents_path(project))
click_button('Operate')
expect(page).to have_link('Environments', href: project_environments_path(project))
expect(page).not_to have_link('Alerts', href: project_alert_management_index_path(project))
expect(page).not_to have_link('Error Tracking', href: project_error_tracking_index_path(project))
expect(page).not_to have_link('Kubernetes', href: project_clusters_path(project))
expect(page).not_to have_link('Alerts', href: project_alert_management_index_path(project), visible: :all)
expect(page).not_to have_link('Error Tracking', href: project_error_tracking_index_path(project), visible: :all)
expect(page).not_to have_link('Kubernetes clusters', href: project_clusters_path(project), visible: :all)
end
it_behaves_like 'shows Monitor menu based on the access level'
it_behaves_like 'shows common Monitor menu item based on the access level'
end
context 'when user has reporter role' do
let(:role) { :reporter }
it 'has the correct `Monitor` menu items' do
it 'has the correct `Monitor` and `Operate` menu items' do
visit project_issues_path(project)
click_button('Monitor')
expect(page).to have_link('Incidents', href: project_incidents_path(project))
expect(page).to have_link('Environments', href: project_environments_path(project))
expect(page).to have_link('Error Tracking', href: project_error_tracking_index_path(project))
expect(page).not_to have_link('Alerts', href: project_alert_management_index_path(project))
expect(page).not_to have_link('Kubernetes', href: project_clusters_path(project))
click_button('Operate')
expect(page).to have_link('Environments', href: project_environments_path(project))
expect(page).not_to have_link('Alerts', href: project_alert_management_index_path(project), visible: :all)
expect(page).not_to have_link('Kubernetes clusters', href: project_clusters_path(project), visible: :all)
end
it_behaves_like 'shows Monitor menu based on the access level'
it_behaves_like 'shows common Monitor menu item based on the access level'
end
context 'when user has developer role' do
let(:role) { :developer }
it 'has the correct `Monitor` menu items' do
it 'has the correct `Monitor` and `Operate` menu items' do
visit project_issues_path(project)
click_button('Monitor')
expect(page).to have_link('Alerts', href: project_alert_management_index_path(project))
expect(page).to have_link('Incidents', href: project_incidents_path(project))
expect(page).to have_link('Environments', href: project_environments_path(project))
expect(page).to have_link('Error Tracking', href: project_error_tracking_index_path(project))
expect(page).to have_link('Kubernetes', href: project_clusters_path(project))
click_button('Operate')
expect(page).to have_link('Environments', href: project_environments_path(project))
expect(page).to have_link('Kubernetes clusters', href: project_clusters_path(project))
end
it_behaves_like 'shows Monitor menu based on the access level'
it_behaves_like 'shows common Monitor menu item based on the access level'
end
context 'when user has maintainer role' do
let(:role) { :maintainer }
it 'has the correct `Monitor` menu items' do
it 'has the correct `Monitor` and `Operate` menu items' do
visit project_issues_path(project)
click_button('Monitor')
expect(page).to have_link('Alerts', href: project_alert_management_index_path(project))
expect(page).to have_link('Incidents', href: project_incidents_path(project))
expect(page).to have_link('Environments', href: project_environments_path(project))
expect(page).to have_link('Error Tracking', href: project_error_tracking_index_path(project))
expect(page).to have_link('Kubernetes', href: project_clusters_path(project))
click_button('Operate')
expect(page).to have_link('Environments', href: project_environments_path(project))
expect(page).to have_link('Kubernetes clusters', href: project_clusters_path(project))
end
it_behaves_like 'shows Monitor menu based on the access level'
it_behaves_like 'shows common Monitor menu item based on the access level'
end
end

View File

@ -59,7 +59,7 @@ RSpec.describe 'Two factor auths', feature_category: :user_profile do
fill_in 'pin_code', with: '123'
click_button 'Register with two-factor app'
expect(page).to have_link('Try the troubleshooting steps here.', href: help_page_path('user/profile/account/two_factor_authentication.md', anchor: 'troubleshooting'))
expect(page).to have_link('Try the troubleshooting steps here.', href: help_page_path('user/profile/account/two_factor_authentication', anchor: 'troubleshooting'))
end
end

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe 'User find project file', feature_category: :groups_and_projects do
include ListboxHelpers
let(:user) { create :user, :no_super_sidebar }
let(:user) { create :user }
let(:project) { create :project, :repository }
before do
@ -15,29 +15,25 @@ RSpec.describe 'User find project file', feature_category: :groups_and_projects
visit project_tree_path(project, project.repository.root_ref)
end
def active_main_tab
find('.sidebar-top-level-items > li.active')
end
def find_file(text)
fill_in 'file_find', with: text
end
def ref_selector_dropdown
find('.gl-button-text')
find('.ref-selector .gl-button-text')
end
it 'navigates to find file by shortcut', :js do
find('body').native.send_key('t')
expect(active_main_tab).to have_content('Repository')
expect(page).to have_active_sub_navigation('Repository')
expect(page).to have_selector('.file-finder-holder', count: 1)
end
it 'navigates to find file' do
it 'navigates to find file', :js do
click_link 'Find file'
expect(active_main_tab).to have_content('Repository')
expect(page).to have_active_sub_navigation('Repository')
expect(page).to have_selector('.file-finder-holder', count: 1)
end

View File

@ -8,7 +8,7 @@ RSpec.describe 'listing forks of a project', feature_category: :groups_and_proje
let(:source) { create(:project, :public, :repository) }
let!(:fork) { fork_project(source, nil, repository: true) }
let(:user) { create(:user, :no_super_sidebar) }
let(:user) { create(:user) }
before do
source.add_maintainer(user)

View File

@ -11,7 +11,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :continuous_integration do
let(:expected_detached_mr_tag) { 'merge request' }
context 'when user is logged in' do
let(:user) { create(:user, :no_super_sidebar) }
let(:user) { create(:user) }
before do
sign_in(user)
@ -650,7 +650,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :continuous_integration do
# header
expect(page).to have_text("##{pipeline.id}")
expect(page).to have_link(pipeline.user.name, href: user_path(pipeline.user))
expect(page).to have_link(pipeline.user.name, href: /#{user_path(pipeline.user)}$/)
# stages
expect(page).to have_text('build')

View File

@ -3,35 +3,62 @@
require 'spec_helper'
RSpec.describe 'Projects > Snippets > Project snippet', :js, feature_category: :source_code_management do
let_it_be(:user) { create(:user) }
let_it_be(:author) { create(:author) }
let_it_be(:project) do
create(:project, creator: user).tap do |p|
p.add_maintainer(user)
create(:project, :public, creator: author).tap do |p|
p.add_maintainer(author)
end
end
let_it_be(:snippet) { create(:project_snippet, :repository, project: project, author: user) }
let_it_be(:snippet) { create(:project_snippet, :public, :repository, project: project, author: author) }
let(:anchor) { nil }
let(:file_path) { 'files/ruby/popen.rb' }
def visit_page
visit project_snippet_path(project, snippet, anchor: anchor)
end
before do
sign_in(user)
# rubocop: disable RSpec/AnyInstanceOf -- TODO: The usage of let_it_be forces us
allow_any_instance_of(Snippet).to receive(:blobs)
.and_return([snippet.repository.blob_at('master', file_path)])
# rubocop: enable RSpec/AnyInstanceOf
end
it_behaves_like 'show and render proper snippet blob' do
let(:anchor) { nil }
context 'when signed in' do
before do
sign_in(user)
visit_page
end
subject do
visit project_snippet_path(project, snippet, anchor: anchor)
context 'as project member' do
let(:user) { author }
wait_for_requests
it_behaves_like 'show and render proper snippet blob'
it_behaves_like 'does show New Snippet button'
end
context 'as external user' do
let_it_be(:user) { create(:user, :external) }
it_behaves_like 'show and render proper snippet blob'
it_behaves_like 'does not show New Snippet button'
end
context 'as another user' do
let_it_be(:user) { create(:user) }
it_behaves_like 'show and render proper snippet blob'
it_behaves_like 'does not show New Snippet button'
end
end
# it_behaves_like 'showing user status' do
# This will be handled in https://gitlab.com/gitlab-org/gitlab/-/issues/262394
context 'when unauthenticated' do
before do
visit_page
end
it_behaves_like 'does not show New Snippet button' do
let(:file_path) { 'files/ruby/popen.rb' }
subject { visit project_snippet_path(project, snippet) }
it_behaves_like 'show and render proper snippet blob'
it_behaves_like 'does not show New Snippet button'
end
end

View File

@ -5,8 +5,8 @@ require 'spec_helper'
RSpec.describe 'Work item', :js, feature_category: :team_planning do
include ListboxHelpers
let_it_be_with_reload(:user) { create(:user, :no_super_sidebar) }
let_it_be_with_reload(:user2) { create(:user, :no_super_sidebar, name: 'John') }
let_it_be_with_reload(:user) { create(:user) }
let_it_be_with_reload(:user2) { create(:user, name: 'John') }
let_it_be(:project) { create(:project, :public) }
let_it_be(:work_item) { create(:work_item, project: project) }

View File

@ -6,8 +6,8 @@ RSpec.describe 'User uses header search field', :js, :disable_rate_limiter, feat
include FilteredSearchHelpers
let_it_be(:project) { create(:project, :repository) }
let_it_be(:reporter) { create(:user, :no_super_sidebar) }
let_it_be(:developer) { create(:user, :no_super_sidebar) }
let_it_be(:reporter) { create(:user) }
let_it_be(:developer) { create(:user) }
let(:user) { reporter }
@ -46,31 +46,34 @@ RSpec.describe 'User uses header search field', :js, :disable_rate_limiter, feat
context 'when using the keyboard shortcut' do
before do
find('#search')
find('body').native.send_keys('s')
wait_for_all_requests
end
it 'shows the category search dropdown', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/250285' do
expect(page).to have_selector('.dropdown-header', text: /#{scope_name}/i)
it 'shows the search modal' do
expect(page).to have_selector(search_modal_results, visible: :visible)
end
end
context 'when clicking the search field' do
context 'when clicking the search button' do
before do
page.find('#search').click
within_testid('super-sidebar') do
click_button "Search or go to…"
end
wait_for_all_requests
end
it 'shows category search dropdown', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/250285' do
expect(page).to have_selector('.dropdown-header', text: /#{scope_name}/i)
it 'shows search scope badge' do
fill_in 'search', with: 'text'
within('#super-sidebar-search-modal') do
expect(page).to have_selector('.search-scope-help', text: scope_name)
end
end
context 'when clicking issues', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332317' do
let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
it 'shows assigned issues' do
find('[data-testid="header-search-dropdown-menu"]').click_link('Issues assigned to me')
find(search_modal_results).click_link('Issues assigned to me')
expect(page).to have_selector('.issues-list .issue')
expect_tokens([assignee_token(user.name)])
@ -78,7 +81,7 @@ RSpec.describe 'User uses header search field', :js, :disable_rate_limiter, feat
end
it 'shows created issues' do
find('[data-testid="header-search-dropdown-menu"]').click_link("Issues I've created")
find(search_modal_results).click_link("Issues I've created")
expect(page).to have_selector('.issues-list .issue')
expect_tokens([author_token(user.name)])
@ -90,7 +93,7 @@ RSpec.describe 'User uses header search field', :js, :disable_rate_limiter, feat
let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignees: [user]) }
it 'shows assigned merge requests' do
find('[data-testid="header-search-dropdown-menu"]').click_link('Merge requests assigned to me')
find(search_modal_results).click_link('Merge requests assigned to me')
expect(page).to have_selector('.mr-list .merge-request')
expect_tokens([assignee_token(user.name)])
@ -98,7 +101,7 @@ RSpec.describe 'User uses header search field', :js, :disable_rate_limiter, feat
end
it 'shows created merge requests' do
find('[data-testid="header-search-dropdown-menu"]').click_link("Merge requests I've created")
find(search_modal_results).click_link("Merge requests I've created")
expect(page).to have_selector('.mr-list .merge-request')
expect_tokens([author_token(user.name)])
@ -119,7 +122,7 @@ RSpec.describe 'User uses header search field', :js, :disable_rate_limiter, feat
context 'when user is in a global scope' do
include_examples 'search field examples' do
let(:url) { root_path }
let(:scope_name) { 'All GitLab' }
let(:scope_name) { 'in all GitLab' }
end
it 'displays search options', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/251076' do
@ -136,11 +139,13 @@ RSpec.describe 'User uses header search field', :js, :disable_rate_limiter, feat
end
it 'displays result counts for all categories' do
expect(page).to have_content('Projects 1')
expect(page).to have_content('Issues 1')
expect(page).to have_content('Merge requests 0')
expect(page).to have_content('Milestones 0')
expect(page).to have_content('Users 0')
within_testid('super-sidebar') do
expect(page).to have_link('Projects 1')
expect(page).to have_link('Issues 1')
expect(page).to have_link('Merge requests 0')
expect(page).to have_link('Milestones 0')
expect(page).to have_link('Users 0')
end
end
end
end
@ -162,9 +167,8 @@ RSpec.describe 'User uses header search field', :js, :disable_rate_limiter, feat
it 'displays search options' do
fill_in_search('test')
expect(page).to have_selector(scoped_search_link('test', search_code: true))
expect(page).to have_selector(scoped_search_link('test', group_id: group.id, search_code: true))
expect(page).to have_selector(scoped_search_link('test', project_id: project.id, group_id: group.id, search_code: true))
expect(page).to have_selector(scoped_search_link('test', search_code: true))
end
end
@ -176,26 +180,25 @@ RSpec.describe 'User uses header search field', :js, :disable_rate_limiter, feat
it 'displays search options' do
fill_in_search('test')
sleep 0.5
expect(page).to have_selector(scoped_search_link('test', search_code: true, repository_ref: 'master'))
expect(page).not_to have_selector(scoped_search_link('test', search_code: true, group_id: project.namespace_id, repository_ref: 'master'))
expect(page).to have_selector(scoped_search_link('test', search_code: true, project_id: project.id, repository_ref: 'master'))
expect(page).to have_selector(scoped_search_link('test', search_code: true, repository_ref: 'master'))
end
it 'displays a link to project merge requests' do
fill_in_search('Merge')
within(dashboard_search_options_popup_menu) do
expect(page).to have_text('Merge requests')
within(search_modal_results) do
expect(page).to have_link('Merge requests')
end
end
it 'does not display a link to project feature flags' do
fill_in_search('Feature')
within(dashboard_search_options_popup_menu) do
expect(page).to have_text('Feature in all GitLab')
expect(page).to have_no_text('Feature Flags')
within(search_modal_results) do
expect(page).to have_link('in all GitLab Feature')
expect(page).not_to have_link('Feature Flags')
end
end
@ -205,8 +208,8 @@ RSpec.describe 'User uses header search field', :js, :disable_rate_limiter, feat
it 'displays a link to project feature flags' do
fill_in_search('Feature')
within(dashboard_search_options_popup_menu) do
expect(page).to have_text('Feature Flags')
within(search_modal_results) do
expect(page).to have_link('Feature Flags')
end
end
end
@ -228,8 +231,8 @@ RSpec.describe 'User uses header search field', :js, :disable_rate_limiter, feat
it 'displays search options' do
fill_in_search('test')
expect(page).to have_selector(scoped_search_link('test'))
expect(page).to have_selector(scoped_search_link('test', group_id: group.id))
expect(page).not_to have_selector(scoped_search_link('test', project_id: project.id))
end
end
@ -253,7 +256,6 @@ RSpec.describe 'User uses header search field', :js, :disable_rate_limiter, feat
fill_in_search('test')
expect(page).to have_selector(scoped_search_link('test'))
expect(page).to have_selector(scoped_search_link('test', group_id: subgroup.id))
expect(page).not_to have_selector(scoped_search_link('test', project_id: project.id))
end
end
@ -268,10 +270,10 @@ RSpec.describe 'User uses header search field', :js, :disable_rate_limiter, feat
href.concat("&search_code=true") if search_code
href.concat("&repository_ref=#{repository_ref}") if repository_ref
"[data-testid='header-search-dropdown-menu'] a[href='#{href}']"
".global-search-results a[href='#{href}']"
end
def dashboard_search_options_popup_menu
"[data-testid='header-search-dropdown-menu'] .header-search-dropdown-content"
def search_modal_results
".global-search-results"
end
end

View File

@ -3,45 +3,63 @@
require 'spec_helper'
RSpec.describe 'Snippet', :js, feature_category: :source_code_management do
let_it_be(:user) { create(:user) }
let_it_be(:snippet) { create(:personal_snippet, :public, :repository, author: user) }
let_it_be(:owner) { create(:user) }
let_it_be(:snippet) { create(:personal_snippet, :public, :repository, author: owner) }
let(:anchor) { nil }
let(:file_path) { 'files/ruby/popen.rb' }
it_behaves_like 'show and render proper snippet blob' do
let(:anchor) { nil }
before do
# rubocop: disable RSpec/AnyInstanceOf -- TODO: The usage of let_it_be forces us
allow_any_instance_of(Snippet).to receive(:blobs)
.and_return([snippet.repository.blob_at('master', file_path)])
# rubocop: enable RSpec/AnyInstanceOf
end
subject do
visit snippet_path(snippet, anchor: anchor)
def visit_page
visit snippet_path(snippet, anchor: anchor)
end
wait_for_requests
context 'when signed in' do
before do
sign_in(user)
visit_page
end
context 'as the snippet owner' do
let(:user) { owner }
it_behaves_like 'show and render proper snippet blob'
it_behaves_like 'does show New Snippet button'
it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :dashboard_snippets_path, :snippets
end
context 'as external user' do
let_it_be(:user) { create(:user, :external) }
it_behaves_like 'show and render proper snippet blob'
it_behaves_like 'does not show New Snippet button'
it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :dashboard_snippets_path, :snippets
end
context 'as another user' do
let_it_be(:user) { create(:user) }
it_behaves_like 'show and render proper snippet blob'
it_behaves_like 'does show New Snippet button'
it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :dashboard_snippets_path, :snippets
end
end
# it_behaves_like 'showing user status' do
# This will be handled in https://gitlab.com/gitlab-org/gitlab/-/issues/262394
it_behaves_like 'does not show New Snippet button' do
let(:file_path) { 'files/ruby/popen.rb' }
subject { visit snippet_path(snippet) }
end
it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :dashboard_snippets_path, :snippets
context 'when unauthenticated' do
it 'shows the "Explore" sidebar' do
visit snippet_path(snippet)
before do
visit_page
end
it_behaves_like 'show and render proper snippet blob'
it_behaves_like 'does not show New Snippet button'
it 'shows the "Explore" sidebar' do
expect(page).to have_css('#super-sidebar-context-header', text: 'Explore')
end
end
context 'when authenticated as a different user' do
let_it_be(:different_user) { create(:user, :no_super_sidebar) }
before do
sign_in(different_user)
end
it_behaves_like 'a "Your work" page with sidebar and breadcrumbs', :dashboard_snippets_path, :snippets
end
end

View File

@ -1,4 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
import { WatchApi } from '@gitlab/cluster-client';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK, HTTP_STATUS_UNAUTHORIZED } from '~/lib/utils/http_status';
import { resolvers } from '~/environments/graphql/resolvers';
@ -27,114 +28,306 @@ describe('~/frontend/environments/graphql/resolvers', () => {
});
describe('fluxKustomizationStatus', () => {
const client = { writeQuery: jest.fn() };
const endpoint = `${configuration.basePath}/apis/kustomize.toolkit.fluxcd.io/v1beta1/namespaces/${namespace}/kustomizations/${environmentName}`;
const fluxResourcePath =
'kustomize.toolkit.fluxcd.io/v1beta1/namespaces/my-namespace/kustomizations/app';
const endpointWithFluxResourcePath = `${configuration.basePath}/apis/${fluxResourcePath}`;
it('should request Flux Kustomizations for the provided namespace via the Kubernetes API if the fluxResourcePath is not specified', async () => {
mock
.onGet(endpoint, { withCredentials: true, headers: configuration.baseOptions.headers })
.reply(HTTP_STATUS_OK, {
status: { conditions: fluxKustomizationsMock },
describe('when k8sWatchApi feature is disabled', () => {
it('should request Flux Kustomizations for the provided namespace via the Kubernetes API if the fluxResourcePath is not specified', async () => {
mock
.onGet(endpoint, { withCredentials: true, headers: configuration.baseOptions.headers })
.reply(HTTP_STATUS_OK, {
status: { conditions: fluxKustomizationsMock },
});
const fluxKustomizationStatus = await mockResolvers.Query.fluxKustomizationStatus(
null,
{
configuration,
namespace,
environmentName,
},
{ client },
);
expect(fluxKustomizationStatus).toEqual(fluxKustomizationsMock);
});
it('should request Flux Kustomization for the provided fluxResourcePath via the Kubernetes API', async () => {
mock
.onGet(endpointWithFluxResourcePath, {
withCredentials: true,
headers: configuration.baseOptions.headers,
})
.reply(HTTP_STATUS_OK, {
status: { conditions: fluxKustomizationsMock },
});
const fluxKustomizationStatus = await mockResolvers.Query.fluxKustomizationStatus(
null,
{
configuration,
namespace,
environmentName,
fluxResourcePath,
},
{ client },
);
expect(fluxKustomizationStatus).toEqual(fluxKustomizationsMock);
});
it('should throw an error if the API call fails', async () => {
const apiError = 'Invalid credentials';
mock
.onGet(endpoint, { withCredentials: true, headers: configuration.base })
.reply(HTTP_STATUS_UNAUTHORIZED, { message: apiError });
const fluxKustomizationsError = mockResolvers.Query.fluxKustomizationStatus(
null,
{
configuration,
namespace,
environmentName,
},
{ client },
);
await expect(fluxKustomizationsError).rejects.toThrow(apiError);
});
});
describe('when k8sWatchApi feature is enabled', () => {
const mockWatcher = WatchApi.prototype;
const mockKustomizationStatusFn = jest.fn().mockImplementation(() => {
return Promise.resolve(mockWatcher);
});
const mockOnDataFn = jest.fn().mockImplementation((eventName, callback) => {
if (eventName === 'data') {
callback(fluxKustomizationsMock);
}
});
const resourceName = 'custom-resource';
beforeEach(() => {
gon.features = { k8sWatchApi: true };
jest.spyOn(mockWatcher, 'subscribeToStream').mockImplementation(mockKustomizationStatusFn);
jest.spyOn(mockWatcher, 'on').mockImplementation(mockOnDataFn);
});
describe('when the Kustomization data is present', () => {
beforeEach(() => {
mock
.onGet(endpoint, { withCredentials: true, headers: configuration.baseOptions.headers })
.reply(HTTP_STATUS_OK, {
metadata: { name: resourceName },
status: { conditions: fluxKustomizationsMock },
});
});
it('should watch Kustomization by the metadata name from the cluster_client library when the data is present', async () => {
await mockResolvers.Query.fluxKustomizationStatus(
null,
{
configuration,
namespace,
environmentName,
},
{ client },
);
expect(mockKustomizationStatusFn).toHaveBeenCalledWith(
`/apis/kustomize.toolkit.fluxcd.io/v1beta1/namespaces/${namespace}/kustomizations`,
{
watch: true,
fieldSelector: `metadata.name=${decodeURIComponent(resourceName)}`,
},
);
});
const fluxKustomizationStatus = await mockResolvers.Query.fluxKustomizationStatus(null, {
configuration,
namespace,
environmentName,
});
it('should return data when received from the library', async () => {
const kustomizationStatus = await mockResolvers.Query.fluxKustomizationStatus(
null,
{
configuration,
namespace,
environmentName,
},
{ client },
);
expect(fluxKustomizationStatus).toEqual(fluxKustomizationsMock);
});
it('should request Flux Kustomization for the provided fluxResourcePath via the Kubernetes API', async () => {
mock
.onGet(endpointWithFluxResourcePath, {
withCredentials: true,
headers: configuration.baseOptions.headers,
})
.reply(HTTP_STATUS_OK, {
status: { conditions: fluxKustomizationsMock },
expect(kustomizationStatus).toEqual(fluxKustomizationsMock);
});
const fluxKustomizationStatus = await mockResolvers.Query.fluxKustomizationStatus(null, {
configuration,
namespace,
environmentName,
fluxResourcePath,
});
expect(fluxKustomizationStatus).toEqual(fluxKustomizationsMock);
});
it('should throw an error if the API call fails', async () => {
const apiError = 'Invalid credentials';
mock
.onGet(endpoint, { withCredentials: true, headers: configuration.base })
.reply(HTTP_STATUS_UNAUTHORIZED, { message: apiError });
it('should not watch Kustomization by the metadata name from the cluster_client library when the data is not present', async () => {
mock
.onGet(endpoint, { withCredentials: true, headers: configuration.baseOptions.headers })
.reply(HTTP_STATUS_OK, {});
const fluxKustomizationsError = mockResolvers.Query.fluxKustomizationStatus(null, {
configuration,
namespace,
environmentName,
await mockResolvers.Query.fluxKustomizationStatus(
null,
{
configuration,
namespace,
environmentName,
},
{ client },
);
expect(mockKustomizationStatusFn).not.toHaveBeenCalled();
});
await expect(fluxKustomizationsError).rejects.toThrow(apiError);
});
});
describe('fluxHelmReleaseStatus', () => {
const client = { writeQuery: jest.fn() };
const endpoint = `${configuration.basePath}/apis/helm.toolkit.fluxcd.io/v2beta1/namespaces/${namespace}/helmreleases/${environmentName}`;
const fluxResourcePath =
'helm.toolkit.fluxcd.io/v2beta1/namespaces/my-namespace/helmreleases/app';
const endpointWithFluxResourcePath = `${configuration.basePath}/apis/${fluxResourcePath}`;
it('should request Flux Helm Releases via the Kubernetes API', async () => {
mock
.onGet(endpoint, { withCredentials: true, headers: configuration.baseOptions.headers })
.reply(HTTP_STATUS_OK, {
status: { conditions: fluxKustomizationsMock },
describe('when k8sWatchApi feature is disabled', () => {
it('should request Flux Helm Releases via the Kubernetes API', async () => {
mock
.onGet(endpoint, { withCredentials: true, headers: configuration.baseOptions.headers })
.reply(HTTP_STATUS_OK, {
status: { conditions: fluxKustomizationsMock },
});
const fluxHelmReleaseStatus = await mockResolvers.Query.fluxHelmReleaseStatus(
null,
{
configuration,
namespace,
environmentName,
},
{ client },
);
expect(fluxHelmReleaseStatus).toEqual(fluxKustomizationsMock);
});
it('should request Flux HelmRelease for the provided fluxResourcePath via the Kubernetes API', async () => {
mock
.onGet(endpointWithFluxResourcePath, {
withCredentials: true,
headers: configuration.baseOptions.headers,
})
.reply(HTTP_STATUS_OK, {
status: { conditions: fluxKustomizationsMock },
});
const fluxHelmReleaseStatus = await mockResolvers.Query.fluxHelmReleaseStatus(
null,
{
configuration,
namespace,
environmentName,
fluxResourcePath,
},
{ client },
);
expect(fluxHelmReleaseStatus).toEqual(fluxKustomizationsMock);
});
it('should throw an error if the API call fails', async () => {
const apiError = 'Invalid credentials';
mock
.onGet(endpoint, { withCredentials: true, headers: configuration.base })
.reply(HTTP_STATUS_UNAUTHORIZED, { message: apiError });
const fluxHelmReleasesError = mockResolvers.Query.fluxHelmReleaseStatus(
null,
{
configuration,
namespace,
environmentName,
},
{ client },
);
await expect(fluxHelmReleasesError).rejects.toThrow(apiError);
});
});
describe('when k8sWatchApi feature is enabled', () => {
const mockWatcher = WatchApi.prototype;
const mockHelmReleaseStatusFn = jest.fn().mockImplementation(() => {
return Promise.resolve(mockWatcher);
});
const mockOnDataFn = jest.fn().mockImplementation((eventName, callback) => {
if (eventName === 'data') {
callback(fluxKustomizationsMock);
}
});
const resourceName = 'custom-resource';
beforeEach(() => {
gon.features = { k8sWatchApi: true };
jest.spyOn(mockWatcher, 'subscribeToStream').mockImplementation(mockHelmReleaseStatusFn);
jest.spyOn(mockWatcher, 'on').mockImplementation(mockOnDataFn);
});
describe('when the HelmRelease data is present', () => {
beforeEach(() => {
mock
.onGet(endpoint, { withCredentials: true, headers: configuration.baseOptions.headers })
.reply(HTTP_STATUS_OK, {
metadata: { name: resourceName },
status: { conditions: fluxKustomizationsMock },
});
});
it('should watch HelmRelease by the metadata name from the cluster_client library when the data is present', async () => {
await mockResolvers.Query.fluxHelmReleaseStatus(
null,
{
configuration,
namespace,
environmentName,
},
{ client },
);
expect(mockHelmReleaseStatusFn).toHaveBeenCalledWith(
`/apis/helm.toolkit.fluxcd.io/v2beta1/namespaces/${namespace}/helmreleases`,
{
watch: true,
fieldSelector: `metadata.name=${decodeURIComponent(resourceName)}`,
},
);
});
const fluxHelmReleaseStatus = await mockResolvers.Query.fluxHelmReleaseStatus(null, {
configuration,
namespace,
environmentName,
});
it('should return data when received from the library', async () => {
const fluxHelmReleaseStatus = await mockResolvers.Query.fluxHelmReleaseStatus(
null,
{
configuration,
namespace,
environmentName,
},
{ client },
);
expect(fluxHelmReleaseStatus).toEqual(fluxKustomizationsMock);
});
it('should request Flux HelmRelease for the provided fluxResourcePath via the Kubernetes API', async () => {
mock
.onGet(endpointWithFluxResourcePath, {
withCredentials: true,
headers: configuration.baseOptions.headers,
})
.reply(HTTP_STATUS_OK, {
status: { conditions: fluxKustomizationsMock },
expect(fluxHelmReleaseStatus).toEqual(fluxKustomizationsMock);
});
const fluxHelmReleaseStatus = await mockResolvers.Query.fluxHelmReleaseStatus(null, {
configuration,
namespace,
environmentName,
fluxResourcePath,
});
expect(fluxHelmReleaseStatus).toEqual(fluxKustomizationsMock);
});
it('should throw an error if the API call fails', async () => {
const apiError = 'Invalid credentials';
mock
.onGet(endpoint, { withCredentials: true, headers: configuration.base })
.reply(HTTP_STATUS_UNAUTHORIZED, { message: apiError });
it('should not watch Kustomization by the metadata name from the cluster_client library when the data is not present', async () => {
mock
.onGet(endpoint, { withCredentials: true, headers: configuration.baseOptions.headers })
.reply(HTTP_STATUS_OK, {});
const fluxHelmReleasesError = mockResolvers.Query.fluxHelmReleaseStatus(null, {
configuration,
namespace,
environmentName,
await mockResolvers.Query.fluxHelmReleaseStatus(
null,
{
configuration,
namespace,
environmentName,
},
{ client },
);
expect(mockHelmReleaseStatusFn).not.toHaveBeenCalled();
});
await expect(fluxHelmReleasesError).rejects.toThrow(apiError);
});
});
});

View File

@ -133,6 +133,8 @@ describe('Source Viewer component', () => {
expect(blameDataQueryHandlerSuccess).toHaveBeenCalledWith(
expect.objectContaining({ fromLine: 1, toLine: 70 }),
);
expect(findChunks().at(0).props('isHighlighted')).toBe(true);
});
it('does not render a Blame component when `showBlame: false`', async () => {

View File

@ -755,14 +755,6 @@ RSpec.describe ApplicationHelper do
it { is_expected.not_to include('with-top-bar') }
end
end
describe 'logged-out-marketing-header' do
before do
allow(helper).to receive(:current_user).and_return(nil)
end
it { is_expected.not_to include('logged-out-marketing-header') }
end
end
describe '#dispensable_render' do

View File

@ -160,24 +160,6 @@ RSpec.describe NavHelper, feature_category: :navigation do
end
end
shared_examples 'anonymous show_super_sidebar is supposed to' do
before do
stub_feature_flags(super_sidebar_logged_out: feature_flag)
end
context 'when super_sidebar_logged_out feature flag is disabled' do
let(:feature_flag) { false }
specify { expect(subject).to eq false }
end
context 'when super_sidebar_logged_out feature flag is enabled' do
let(:feature_flag) { true }
specify { expect(subject).to eq true }
end
end
context 'without a user' do
context 'with current_user (nil) as a default' do
before do
@ -186,13 +168,13 @@ RSpec.describe NavHelper, feature_category: :navigation do
subject { helper.show_super_sidebar? }
it_behaves_like 'anonymous show_super_sidebar is supposed to'
specify { expect(subject).to eq true }
end
context 'with nil provided as an argument' do
subject { helper.show_super_sidebar?(nil) }
it_behaves_like 'anonymous show_super_sidebar is supposed to'
specify { expect(subject).to eq true }
end
end

View File

@ -23,7 +23,7 @@ RSpec.describe Sidebars::Projects::Menus::ScopeMenu, feature_category: :navigati
describe '#container_html_options' do
subject { described_class.new(context).container_html_options }
specify { is_expected.to match(hash_including(class: 'shortcuts-project rspec-project-link')) }
specify { is_expected.to match(hash_including(class: 'shortcuts-project')) }
end
describe '#extra_nav_link_html_options' do

View File

@ -2,10 +2,10 @@
module SearchHelpers
def fill_in_search(text)
page.within('.header-search') do
find('#search').click
fill_in 'search', with: text
within_testid('super-sidebar') do
click_button "Search or go to…"
end
fill_in 'search', with: text
wait_for_all_requests
end

View File

@ -4011,7 +4011,6 @@
- './spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb'
- './spec/features/projects/show/user_uploads_files_spec.rb'
- './spec/features/projects/snippets/create_snippet_spec.rb'
- './spec/features/projects/snippets/show_spec.rb'
- './spec/features/projects/snippets/user_comments_on_snippet_spec.rb'
- './spec/features/projects/snippets/user_deletes_snippet_spec.rb'
- './spec/features/projects/snippets/user_updates_snippet_spec.rb'
@ -4082,7 +4081,6 @@
- './spec/features/snippets/private_snippets_spec.rb'
- './spec/features/snippets/public_snippets_spec.rb'
- './spec/features/snippets/search_snippets_spec.rb'
- './spec/features/snippets/show_spec.rb'
- './spec/features/snippets/spam_snippets_spec.rb'
- './spec/features/snippets_spec.rb'
- './spec/features/snippets/user_creates_snippet_spec.rb'

View File

@ -4,8 +4,8 @@ RSpec.shared_examples 'project features apply to issuables' do |klass|
let(:described_class) { klass }
let(:group) { create(:group) }
let(:user_in_group) { create(:group_member, :developer, user: create(:user, :no_super_sidebar), group: group ).user }
let(:user_outside_group) { create(:user, :no_super_sidebar) }
let(:user_in_group) { create(:group_member, :developer, user: create(:user), group: group ).user }
let(:user_outside_group) { create(:user) }
let(:project) { create(:project, :public, project_args) }

View File

@ -52,30 +52,24 @@ RSpec.shared_examples 'tabs with counts' do
end
RSpec.shared_examples 'does not show New Snippet button' do
let(:user) { create(:user, :external, :no_super_sidebar) }
specify do
sign_in(user)
subject
wait_for_requests
expect(page).to have_link(text: "$#{snippet.id}")
expect(page).not_to have_link('New snippet')
end
end
RSpec.shared_examples 'show and render proper snippet blob' do
before do
allow_any_instance_of(Snippet).to receive(:blobs).and_return([snippet.repository.blob_at('master', file_path)])
RSpec.shared_examples 'does show New Snippet button' do
specify do
expect(page).to have_link(text: "$#{snippet.id}")
expect(page).to have_link('New snippet')
end
end
RSpec.shared_examples 'show and render proper snippet blob' do
context 'Ruby file' do
let(:file_path) { 'files/ruby/popen.rb' }
it 'displays the blob' do
subject
aggregate_failures do
# shows highlighted Ruby code
expect(page).to have_content("require 'fileutils'")
@ -99,10 +93,6 @@ RSpec.shared_examples 'show and render proper snippet blob' do
let(:file_path) { 'files/markdown/ruby-style-guide.md' }
context 'visiting directly' do
before do
subject
end
it 'displays the blob using the rich viewer' do
aggregate_failures do
# hides the simple viewer
@ -171,8 +161,6 @@ RSpec.shared_examples 'show and render proper snippet blob' do
let(:anchor) { 'LC1' }
it 'displays the blob using the simple viewer' do
subject
aggregate_failures do
# hides the rich viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]')

View File

@ -416,7 +416,7 @@ RSpec.shared_examples 'work items todos' do
expect(page).to have_button s_('WorkItem|Mark as done')
page.within ".header-content span[aria-label='#{_('Todos count')}']" do
within_testid('todos-shortcut-button') do
expect(page).to have_content '1'
end
end
@ -426,7 +426,9 @@ RSpec.shared_examples 'work items todos' do
click_button s_('WorkItem|Mark as done')
expect(page).to have_button s_('WorkItem|Add a to do')
expect(page).to have_selector(".header-content span[aria-label='#{_('Todos count')}']", visible: :hidden)
within_testid('todos-shortcut-button') do
expect(page).to have_content("")
end
end
end

View File

@ -80,7 +80,6 @@ RSpec.describe 'layouts/application' do
before do
allow(view).to receive(:current_user).and_return(nil)
allow(view).to receive(:current_user_mode).and_return(Gitlab::Auth::CurrentUserMode.new(nil))
Feature.enable(:super_sidebar_logged_out)
end
it 'renders the new marketing header for logged-out users' do

View File

@ -5,7 +5,6 @@ require 'spec_helper'
RSpec.describe 'layouts/header/_super_sidebar_logged_out', feature_category: :navigation do
before do
allow(view).to receive(:current_user_mode).and_return(Gitlab::Auth::CurrentUserMode.new(nil))
Feature.enable(:super_sidebar_logged_out)
end
context 'on gitlab.com' do

View File

@ -23,7 +23,7 @@ RSpec.describe 'layouts/nav/sidebar/_project', feature_category: :navigation do
it 'has a link to the project path' do
render
expect(rendered).to have_link(project.name, href: project_path(project), class: %w(shortcuts-project rspec-project-link))
expect(rendered).to have_link(project.name, href: project_path(project), class: 'shortcuts-project')
expect(rendered).to have_selector("[aria-label=\"#{project.name}\"]")
end
end