Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1235828392
commit
8bf623d6ea
|
|
@ -1043,3 +1043,12 @@ Search/AvoidCheckingFinishedOnDeprecatedMigrations:
|
|||
- 'ee/lib/elastic/**/*.rb'
|
||||
- 'ee/lib/gitlab/elastic/**/*.rb'
|
||||
- 'ee/spec/support/helpers/elasticsearch_helpers.rb'
|
||||
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/407233
|
||||
Cop/ExperimentsTestCoverage:
|
||||
Enabled: true
|
||||
Include:
|
||||
- 'app/**/*'
|
||||
- 'lib/**/*'
|
||||
- 'ee/app/**/*'
|
||||
- 'ee/lib/**/*'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
Cop/ExperimentsTestCoverage:
|
||||
Details: grace period
|
||||
Exclude:
|
||||
- 'app/controllers/groups/boards_controller.rb'
|
||||
- 'app/controllers/projects/boards_controller.rb'
|
||||
- 'app/experiments/build_ios_app_guide_email_experiment.rb'
|
||||
- 'ee/app/views/shared/_tier_badge.html.haml.rb'
|
||||
|
|
@ -53,7 +53,7 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<li class="report-block-list-issue gl-p-3!" data-qa-selector="report_item_row">
|
||||
<li class="report-block-list-issue gl-p-3!" data-testid="report-item-row">
|
||||
<component
|
||||
:is="iconComponent"
|
||||
v-if="showReportSectionStatusIcon"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/cons
|
|||
import { MINIMUM_SEARCH_LENGTH } from '~/graphql_shared/constants';
|
||||
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
|
||||
|
||||
// This is added outside the component as each dropdown on the page triggers a query,
|
||||
// so if multiple queries fail, we only show 1 error.
|
||||
const reportNamespaceLoadError = debounce(
|
||||
() =>
|
||||
createAlert({
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
<script>
|
||||
import { GlCollapsibleListbox } from '@gitlab/ui';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { __, s__ } from '~/locale';
|
||||
import { createAlert } from '~/alert';
|
||||
import { truncate } from '~/lib/utils/text_utility';
|
||||
import searchNamespacesWhereUserCanImportProjectsQuery from '~/import_entities/import_projects/graphql/queries/search_namespaces_where_user_can_import_projects.query.graphql';
|
||||
import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import { MINIMUM_SEARCH_LENGTH } from '~/graphql_shared/constants';
|
||||
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
|
||||
|
||||
// This is added outside the component as each dropdown on the page triggers a query,
|
||||
// so if multiple queries fail, we only show 1 error.
|
||||
const reportNamespaceLoadError = debounce(
|
||||
() =>
|
||||
createAlert({
|
||||
message: s__('ImportProjects|Requesting namespaces failed'),
|
||||
}),
|
||||
DEFAULT_DEBOUNCE_AND_THROTTLE_MS,
|
||||
);
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlCollapsibleListbox,
|
||||
},
|
||||
|
||||
props: {
|
||||
selected: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
userNamespace: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
MAX_IMPORT_TARGET_LENGTH: 24,
|
||||
|
||||
data() {
|
||||
return {
|
||||
searchTerm: '',
|
||||
};
|
||||
},
|
||||
|
||||
apollo: {
|
||||
namespaces: {
|
||||
query: searchNamespacesWhereUserCanImportProjectsQuery,
|
||||
variables() {
|
||||
return {
|
||||
search: this.searchTerm,
|
||||
};
|
||||
},
|
||||
skip() {
|
||||
const hasNotEnoughSearchCharacters =
|
||||
this.searchTerm.length > 0 && this.searchTerm.length < MINIMUM_SEARCH_LENGTH;
|
||||
return hasNotEnoughSearchCharacters;
|
||||
},
|
||||
update(data) {
|
||||
return data.currentUser.groups.nodes;
|
||||
},
|
||||
error: reportNamespaceLoadError,
|
||||
debounce: DEBOUNCE_DELAY,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
filteredNamespaces() {
|
||||
return (this.namespaces ?? []).filter((ns) =>
|
||||
ns.fullPath.toLowerCase().includes(this.searchTerm.toLowerCase()),
|
||||
);
|
||||
},
|
||||
|
||||
toggleText() {
|
||||
return truncate(this.selected, this.$options.MAX_IMPORT_TARGET_LENGTH);
|
||||
},
|
||||
|
||||
items() {
|
||||
return [
|
||||
{
|
||||
text: __('Users'),
|
||||
options: [{ text: this.userNamespace, value: this.userNamespace }],
|
||||
},
|
||||
{
|
||||
text: __('Groups'),
|
||||
options: this.filteredNamespaces.map((namespace) => {
|
||||
return { text: namespace.fullPath, value: namespace.fullPath };
|
||||
}),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSelect(value) {
|
||||
this.$emit('select', value);
|
||||
},
|
||||
|
||||
onSearch(value) {
|
||||
this.searchTerm = value.trim();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-collapsible-listbox
|
||||
:items="items"
|
||||
:selected="selected"
|
||||
:toggle-text="toggleText"
|
||||
searchable
|
||||
fluid-width
|
||||
toggle-class="gl-rounded-top-right-none! gl-rounded-bottom-right-none!"
|
||||
data-qa-selector="target_namespace_selector_dropdown"
|
||||
@select="onSelect"
|
||||
@search="onSearch"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -5,9 +5,6 @@ import {
|
|||
GlFormInput,
|
||||
GlButton,
|
||||
GlLink,
|
||||
GlDropdownItem,
|
||||
GlDropdownDivider,
|
||||
GlDropdownSectionHeader,
|
||||
GlTooltip,
|
||||
GlSprintf,
|
||||
GlTooltipDirective,
|
||||
|
|
@ -15,7 +12,7 @@ import {
|
|||
import { mapState, mapGetters, mapActions } from 'vuex';
|
||||
import { __ } from '~/locale';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import ImportGroupDropdown from '../../components/group_dropdown.vue';
|
||||
import ImportTargetDropdown from '../../components/import_target_dropdown.vue';
|
||||
import ImportStatus from '../../components/import_status.vue';
|
||||
import { STATUSES } from '../../constants';
|
||||
import { isProjectImportable, isImporting, isIncompatible, getImportStatus } from '../utils';
|
||||
|
|
@ -23,13 +20,10 @@ import { isProjectImportable, isImporting, isIncompatible, getImportStatus } fro
|
|||
export default {
|
||||
name: 'ProviderRepoTableRow',
|
||||
components: {
|
||||
ImportGroupDropdown,
|
||||
ImportStatus,
|
||||
ImportTargetDropdown,
|
||||
GlFormInput,
|
||||
GlButton,
|
||||
GlDropdownItem,
|
||||
GlDropdownDivider,
|
||||
GlDropdownSectionHeader,
|
||||
GlIcon,
|
||||
GlBadge,
|
||||
GlLink,
|
||||
|
|
@ -151,6 +145,10 @@ export default {
|
|||
});
|
||||
}
|
||||
},
|
||||
|
||||
onSelect(value) {
|
||||
this.updateImportTarget({ targetNamespace: value });
|
||||
},
|
||||
},
|
||||
|
||||
helpUrl: helpPagePath('/user/project/import/github.md'),
|
||||
|
|
@ -188,27 +186,13 @@ export default {
|
|||
<template v-if="repo.importSource.target">{{ repo.importSource.target }}</template>
|
||||
<template v-else-if="isImportNotStarted || isSelectedForReimport">
|
||||
<div class="gl-display-flex gl-align-items-stretch gl-w-full">
|
||||
<import-group-dropdown #default="{ namespaces }" :text="importTarget.targetNamespace">
|
||||
<template v-if="namespaces.length">
|
||||
<gl-dropdown-section-header>{{ __('Groups') }}</gl-dropdown-section-header>
|
||||
<gl-dropdown-item
|
||||
v-for="ns in namespaces"
|
||||
:key="ns.fullPath"
|
||||
data-qa-selector="target_group_dropdown_item"
|
||||
:data-qa-group-name="ns.fullPath"
|
||||
@click="updateImportTarget({ targetNamespace: ns.fullPath })"
|
||||
>
|
||||
{{ ns.fullPath }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-divider />
|
||||
</template>
|
||||
<gl-dropdown-section-header>{{ __('Users') }}</gl-dropdown-section-header>
|
||||
<gl-dropdown-item @click="updateImportTarget({ targetNamespace: userNamespace })">{{
|
||||
userNamespace
|
||||
}}</gl-dropdown-item>
|
||||
</import-group-dropdown>
|
||||
<import-target-dropdown
|
||||
:selected="importTarget.targetNamespace"
|
||||
:user-namespace="userNamespace"
|
||||
@select="onSelect"
|
||||
/>
|
||||
<div
|
||||
class="gl-px-3 gl-display-flex gl-align-items-center gl-border-solid gl-border-0 gl-border-t-1 gl-border-b-1"
|
||||
class="gl-px-3 gl-display-flex gl-align-items-center gl-border-solid gl-border-0 gl-border-t-1 gl-border-b-1 gl-border-gray-400"
|
||||
>
|
||||
/
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -431,6 +431,7 @@ the coverage report itself and verify that:
|
|||
to match the files in your repository.
|
||||
- The pipeline has completed. If the pipeline is [blocked on a manual job](../jobs/job_control.md#types-of-manual-jobs),
|
||||
the pipeline is not considered complete.
|
||||
- The coverage report file does not exceed the [limits](#limits).
|
||||
|
||||
Report artifacts are not downloadable by default. If you want the report to be downloadable
|
||||
from the job details page, add your coverage report to the artifact `paths`:
|
||||
|
|
|
|||
|
|
@ -67,6 +67,12 @@ module QA
|
|||
click_element :admin_overview_groups_link
|
||||
end
|
||||
|
||||
def go_to_security_and_compliance
|
||||
hover_element(:admin_settings_menu_link) do
|
||||
click_element :admin_security_and_compliance_link
|
||||
end
|
||||
end
|
||||
|
||||
def go_to_applications
|
||||
return click_element(:nav_item_link, submenu_item: 'Applications') if Runtime::Env.super_sidebar_enabled?
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ module QA
|
|||
element :import_status_indicator
|
||||
end
|
||||
|
||||
view "app/assets/javascripts/import_entities/components/group_dropdown.vue" do
|
||||
view "app/assets/javascripts/import_entities/components/import_target_dropdown.vue" do
|
||||
element :target_namespace_selector_dropdown
|
||||
end
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ module QA
|
|||
def import!(gh_project_name, target_group_path, project_name)
|
||||
within_element(:project_import_row, source_project: gh_project_name) do
|
||||
click_element(:target_namespace_selector_dropdown)
|
||||
click_element(:target_group_dropdown_item, group_name: target_group_path)
|
||||
click_element(:"listbox-item-#{target_group_path}", wait: 10)
|
||||
fill_element(:project_path_field, project_name)
|
||||
|
||||
retry_until do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
# Check for test coverage for GitLab experiments.
|
||||
class ExperimentsTestCoverage < RuboCop::Cop::Base
|
||||
CLASS_OFFENSE = 'Make sure experiment class has test coverage for all the variants.'
|
||||
BLOCK_OFFENSE = 'Make sure experiment block has test coverage for all the variants.'
|
||||
|
||||
# Validates classes inherited from ApplicationExperiment
|
||||
# These classes are located under app/experiments or ee/app/experiments
|
||||
def on_class(node)
|
||||
return if node.parent_class&.const_name != 'ApplicationExperiment'
|
||||
return if covered_with_tests?(node)
|
||||
|
||||
add_offense(node, message: CLASS_OFFENSE)
|
||||
end
|
||||
|
||||
# Validates experiments block in *.rb and *.haml files:
|
||||
# experiment(:experiment_name) do |e|
|
||||
# e.candidate { 'candidate' }
|
||||
# e.run
|
||||
# end
|
||||
def on_block(node)
|
||||
return if node.method_name != :experiment
|
||||
return if covered_with_tests?(node)
|
||||
|
||||
add_offense(node, message: BLOCK_OFFENSE)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def covered_with_tests?(node)
|
||||
tests_code = test_files_code(node)
|
||||
|
||||
return false if tests_code.blank?
|
||||
return false unless tests_code.match?(stub_experiments_matcher)
|
||||
return false unless tests_code.include?(experiment_name(node))
|
||||
|
||||
experiment_variants(node).map { |variant| tests_code.include?(variant) }.all?(&:present?)
|
||||
end
|
||||
|
||||
def test_files_code(node)
|
||||
# haml-lint add .rb extension to *.haml files
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/415330#caveats
|
||||
test_file_path = filepath(node).gsub('app/', 'spec/').gsub('.rb', '_spec.rb')
|
||||
"#{read_file(test_file_path)}\n#{additional_tests_code(test_file_path)}"
|
||||
end
|
||||
|
||||
def additional_tests_code(test_file_path)
|
||||
# rubocop:disable Gitlab/NoCodeCoverageComment
|
||||
# :nocov: File paths stubed in tests
|
||||
if test_file_path.include?('/controllers/')
|
||||
read_file(test_file_path.gsub('/controllers/', '/requests/'))
|
||||
elsif test_file_path.include?('/lib/api/')
|
||||
read_file(test_file_path.gsub('/lib/', '/spec/requests/'))
|
||||
end
|
||||
# :nocov:
|
||||
# rubocop:enable Gitlab/NoCodeCoverageComment
|
||||
end
|
||||
|
||||
def read_file(file_path)
|
||||
File.exist?(file_path) ? File.new(file_path).read : ''
|
||||
end
|
||||
|
||||
def experiment_name(node)
|
||||
if node.is_a?(RuboCop::AST::ClassNode)
|
||||
File.basename(filepath(node), '_experiment.rb')
|
||||
else
|
||||
block_node_value(node)
|
||||
end
|
||||
end
|
||||
|
||||
def experiment_variants(node)
|
||||
node.body.children.filter_map do |child|
|
||||
next unless child.is_a?(RuboCop::AST::SendNode) || child.is_a?(RuboCop::AST::BlockNode)
|
||||
|
||||
extract_variant(child)
|
||||
end
|
||||
end
|
||||
|
||||
def extract_variant(node)
|
||||
# control enabled by default for tests
|
||||
case node.method_name
|
||||
when :candidate then 'candidate'
|
||||
when :variant then variant_name(node)
|
||||
end
|
||||
end
|
||||
|
||||
def variant_name(node)
|
||||
return send_node_value(node) if node.is_a?(RuboCop::AST::SendNode)
|
||||
|
||||
block_node_value(node)
|
||||
end
|
||||
|
||||
def block_node_value(node)
|
||||
send_node_value(node.children[0])
|
||||
end
|
||||
|
||||
def send_node_value(node)
|
||||
node.children[2].value.to_s
|
||||
end
|
||||
|
||||
def filepath(node)
|
||||
node.location.expression.source_buffer.name
|
||||
end
|
||||
|
||||
def stub_experiments_matcher
|
||||
# validates test files contains uncommented stub_experiments(...
|
||||
/^([^#]|\s*|\w*)stub_experiments\(/
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import { GlCollapsibleListbox } from '@gitlab/ui';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
||||
import ImportTargetDropdown from '~/import_entities/components/import_target_dropdown.vue';
|
||||
import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import searchNamespacesWhereUserCanImportProjectsQuery from '~/import_entities/import_projects/graphql/queries/search_namespaces_where_user_can_import_projects.query.graphql';
|
||||
|
||||
import { mockAvailableNamespaces, mockNamespacesResponse, mockUserNamespace } from '../mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
describe('ImportTargetDropdown', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultProps = {
|
||||
selected: mockUserNamespace,
|
||||
userNamespace: mockUserNamespace,
|
||||
};
|
||||
|
||||
const createComponent = ({ props = {} } = {}) => {
|
||||
const apolloProvider = createMockApollo([
|
||||
[
|
||||
searchNamespacesWhereUserCanImportProjectsQuery,
|
||||
jest.fn().mockResolvedValue(mockNamespacesResponse),
|
||||
],
|
||||
]);
|
||||
|
||||
wrapper = shallowMount(ImportTargetDropdown, {
|
||||
apolloProvider,
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findListbox = () => wrapper.findComponent(GlCollapsibleListbox);
|
||||
const findListboxUsersItems = () => findListbox().props('items')[0].options;
|
||||
const findListboxGroupsItems = () => findListbox().props('items')[1].options;
|
||||
|
||||
const waitForQuery = async () => {
|
||||
jest.advanceTimersByTime(DEBOUNCE_DELAY);
|
||||
await waitForPromises();
|
||||
};
|
||||
|
||||
it('renders listbox', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findListbox().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('truncates "toggle-text" when "selected" is too long', () => {
|
||||
const mockSelected = 'a-group-path-that-is-longer-than-24-characters';
|
||||
|
||||
createComponent({
|
||||
props: { selected: mockSelected },
|
||||
});
|
||||
|
||||
expect(findListbox().props('toggleText')).toBe('a-group-path-that-is-lo…');
|
||||
});
|
||||
|
||||
it('passes userNamespace as "Users" group item', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findListboxUsersItems()).toEqual([
|
||||
{ text: mockUserNamespace, value: mockUserNamespace },
|
||||
]);
|
||||
});
|
||||
|
||||
it('passes namespaces from GraphQL as "Groups" group item', async () => {
|
||||
createComponent();
|
||||
|
||||
await waitForQuery();
|
||||
|
||||
expect(findListboxGroupsItems()).toEqual(
|
||||
mockAvailableNamespaces.map((namespace) => ({
|
||||
text: namespace.fullPath,
|
||||
value: namespace.fullPath,
|
||||
})),
|
||||
);
|
||||
});
|
||||
|
||||
it('filters namespaces based on user input', async () => {
|
||||
createComponent();
|
||||
|
||||
findListbox().vm.$emit('search', 'match');
|
||||
|
||||
await waitForQuery();
|
||||
|
||||
expect(findListboxGroupsItems()).toEqual([
|
||||
{ text: 'match1', value: 'match1' },
|
||||
{ text: 'match2', value: 'match2' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import { GlBadge, GlButton } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import { STATUSES } from '~/import_entities//constants';
|
||||
import ImportGroupDropdown from '~/import_entities/components/group_dropdown.vue';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
|
||||
import { STATUSES } from '~/import_entities/constants';
|
||||
import ImportTargetDropdown from '~/import_entities/components/import_target_dropdown.vue';
|
||||
import ImportStatus from '~/import_entities/components/import_status.vue';
|
||||
import ProviderRepoTableRow from '~/import_entities/import_projects/components/provider_repo_table_row.vue';
|
||||
|
||||
|
|
@ -39,8 +40,9 @@ describe('ProviderRepoTableRow', () => {
|
|||
|
||||
const findImportButton = () => findButton('Import');
|
||||
const findReimportButton = () => findButton('Re-import');
|
||||
const findGroupDropdown = () => wrapper.findComponent(ImportGroupDropdown);
|
||||
const findImportTargetDropdown = () => wrapper.findComponent(ImportTargetDropdown);
|
||||
const findImportStatus = () => wrapper.findComponent(ImportStatus);
|
||||
const findProviderLink = () => wrapper.findByTestId('providerLink');
|
||||
|
||||
const findCancelButton = () => {
|
||||
const buttons = wrapper
|
||||
|
|
@ -55,7 +57,7 @@ describe('ProviderRepoTableRow', () => {
|
|||
|
||||
const store = initStore();
|
||||
|
||||
wrapper = shallowMount(ProviderRepoTableRow, {
|
||||
wrapper = shallowMountExtended(ProviderRepoTableRow, {
|
||||
store,
|
||||
propsData: { userNamespace, optionalStages: {}, ...props },
|
||||
});
|
||||
|
|
@ -75,7 +77,7 @@ describe('ProviderRepoTableRow', () => {
|
|||
});
|
||||
|
||||
it('renders project information', () => {
|
||||
const providerLink = wrapper.find('[data-testid=providerLink]');
|
||||
const providerLink = findProviderLink();
|
||||
|
||||
expect(providerLink.attributes().href).toMatch(repo.importSource.providerLink);
|
||||
expect(providerLink.text()).toMatch(repo.importSource.fullName);
|
||||
|
|
@ -86,7 +88,7 @@ describe('ProviderRepoTableRow', () => {
|
|||
});
|
||||
|
||||
it('renders a group namespace select', () => {
|
||||
expect(wrapper.findComponent(ImportGroupDropdown).exists()).toBe(true);
|
||||
expect(findImportTargetDropdown().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders import button', () => {
|
||||
|
|
@ -106,7 +108,11 @@ describe('ProviderRepoTableRow', () => {
|
|||
|
||||
it('includes optionalStages to import', async () => {
|
||||
const OPTIONAL_STAGES = { stage1: true, stage2: false };
|
||||
await wrapper.setProps({ optionalStages: OPTIONAL_STAGES });
|
||||
|
||||
mountComponent({
|
||||
repo,
|
||||
optionalStages: OPTIONAL_STAGES,
|
||||
});
|
||||
|
||||
findImportButton().vm.$emit('click');
|
||||
|
||||
|
|
@ -192,7 +198,7 @@ describe('ProviderRepoTableRow', () => {
|
|||
});
|
||||
|
||||
it('renders project information', () => {
|
||||
const providerLink = wrapper.find('[data-testid=providerLink]');
|
||||
const providerLink = findProviderLink();
|
||||
|
||||
expect(providerLink.attributes().href).toMatch(repo.importSource.providerLink);
|
||||
expect(providerLink.text()).toMatch(repo.importSource.fullName);
|
||||
|
|
@ -203,7 +209,7 @@ describe('ProviderRepoTableRow', () => {
|
|||
});
|
||||
|
||||
it('does not render a namespace select', () => {
|
||||
expect(findGroupDropdown().exists()).toBe(false);
|
||||
expect(findImportTargetDropdown().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not render import button', () => {
|
||||
|
|
@ -219,7 +225,7 @@ describe('ProviderRepoTableRow', () => {
|
|||
|
||||
await nextTick();
|
||||
|
||||
expect(findGroupDropdown().exists()).toBe(true);
|
||||
expect(findImportTargetDropdown().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('imports repo when clicking re-import button', async () => {
|
||||
|
|
@ -282,7 +288,7 @@ describe('ProviderRepoTableRow', () => {
|
|||
});
|
||||
|
||||
it('renders project information', () => {
|
||||
const providerLink = wrapper.find('[data-testid=providerLink]');
|
||||
const providerLink = findProviderLink();
|
||||
|
||||
expect(providerLink.attributes().href).toMatch(repo.importSource.providerLink);
|
||||
expect(providerLink.text()).toMatch(repo.importSource.fullName);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
const mockGroupFactory = (fullPath) => ({
|
||||
id: `gid://gitlab/Group/${fullPath}`,
|
||||
fullPath,
|
||||
name: fullPath,
|
||||
visibility: 'public',
|
||||
webUrl: `http://gdk.test:3000/groups/${fullPath}`,
|
||||
__typename: 'Group',
|
||||
});
|
||||
|
||||
export const mockAvailableNamespaces = [
|
||||
mockGroupFactory('match1'),
|
||||
mockGroupFactory('unrelated'),
|
||||
mockGroupFactory('match2'),
|
||||
];
|
||||
|
||||
export const mockNamespacesResponse = {
|
||||
data: {
|
||||
currentUser: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
groups: {
|
||||
nodes: mockAvailableNamespaces,
|
||||
__typename: 'GroupConnection',
|
||||
},
|
||||
namespace: {
|
||||
id: 'gid://gitlab/Namespaces::UserNamespace/1',
|
||||
fullPath: 'root',
|
||||
__typename: 'Namespace',
|
||||
},
|
||||
__typename: 'UserCore',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockUserNamespace = 'user1';
|
||||
|
|
@ -22,7 +22,7 @@ RSpec.describe Backup::Database, feature_category: :backup_restore do
|
|||
end
|
||||
end
|
||||
|
||||
before_all do
|
||||
before(:all) do # rubocop:disable RSpec/BeforeAll
|
||||
Rake::Task.define_task(:environment)
|
||||
Rake.application.rake_require 'active_record/railties/databases'
|
||||
Rake.application.rake_require 'tasks/gitlab/backup'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,169 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop_spec_helper'
|
||||
|
||||
require_relative '../../../rubocop/cop/experiments_test_coverage'
|
||||
|
||||
RSpec.describe RuboCop::Cop::ExperimentsTestCoverage, feature_category: :experimentation_conversion do
|
||||
let(:class_offense) { described_class::CLASS_OFFENSE }
|
||||
let(:block_offense) { described_class::BLOCK_OFFENSE }
|
||||
|
||||
before do
|
||||
allow(File).to receive(:exist?).and_return(true)
|
||||
allow(File).to receive(:new).and_return(instance_double(File, read: tests_code))
|
||||
end
|
||||
|
||||
describe '#on_class' do
|
||||
context 'when there are no tests' do
|
||||
let(:tests_code) { '' }
|
||||
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~RUBY)
|
||||
class ExperimentName < ApplicationExperiment
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{class_offense}
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is no stub_experiments' do
|
||||
let(:tests_code) { "candidate third" }
|
||||
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~RUBY)
|
||||
class ExperimentName < ApplicationExperiment
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{class_offense}
|
||||
candidate
|
||||
variant(:third) { 'third option' }
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
context 'when variant test is missing' do
|
||||
let(:tests_code) { "\nstub_experiments(experiment_name: :candidate)" }
|
||||
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~RUBY)
|
||||
class ExperimentName < ApplicationExperiment
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{class_offense}
|
||||
candidate
|
||||
variant(:third) { 'third option' }
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
context 'when stub_experiments is commented out' do
|
||||
let(:tests_code) do
|
||||
"\n# stub_experiments(experiment_name: :candidate, experiment_name: :third)"
|
||||
end
|
||||
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~RUBY)
|
||||
class ExperimentName < ApplicationExperiment
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{class_offense}
|
||||
candidate
|
||||
variant(:third) { 'third option' }
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
context 'when all tests are present' do
|
||||
let(:tests_code) do
|
||||
"#\nstub_experiments(experiment_name: :candidate, experiment_name: :third)"
|
||||
end
|
||||
|
||||
before do
|
||||
allow(cop).to receive(:filepath).and_return('app/experiments/experiment_name_experiment.rb')
|
||||
end
|
||||
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses(<<~RUBY)
|
||||
class ExperimentName < ApplicationExperiment
|
||||
candidate
|
||||
variant(:third) { 'third option' }
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#on_block' do
|
||||
context 'when there are no tests' do
|
||||
let(:tests_code) { '' }
|
||||
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~RUBY)
|
||||
experiment(:experiment_name) do |e|
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{block_offense}
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is no stub_experiments' do
|
||||
let(:tests_code) { "candidate third" }
|
||||
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~RUBY)
|
||||
experiment(:experiment_name) do |e|
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{block_offense}
|
||||
e.candidate { 'candidate' }
|
||||
e.variant(:third) { 'third option' }
|
||||
e.run
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
context 'when variant test is missing' do
|
||||
let(:tests_code) { "\nstub_experiments(experiment_name: :candidate)" }
|
||||
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~RUBY)
|
||||
experiment(:experiment_name) do |e|
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{block_offense}
|
||||
e.candidate { 'candidate' }
|
||||
e.variant(:third) { 'third option' }
|
||||
e.run
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
context 'when stub_experiments is commented out' do
|
||||
let(:tests_code) do
|
||||
"\n# stub_experiments(experiment_name: :candidate, experiment_name: :third)"
|
||||
end
|
||||
|
||||
it 'registers an offense' do
|
||||
expect_offense(<<~RUBY)
|
||||
experiment(:experiment_name) do |e|
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{block_offense}
|
||||
e.candidate { 'candidate' }
|
||||
e.variant(:third) { 'third option' }
|
||||
e.run
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
context 'when all tests are present' do
|
||||
let(:tests_code) do
|
||||
"#\nstub_experiments(experiment_name: :candidate, experiment_name: :third)"
|
||||
end
|
||||
|
||||
it 'does not register an offense' do
|
||||
expect_no_offenses(<<~RUBY)
|
||||
experiment(:experiment_name) do |e|
|
||||
e.candidate { 'candidate' }
|
||||
e.variant(:third) { 'third option' }
|
||||
e.run
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -38,7 +38,7 @@ RSpec.describe 'gitlab:backup namespace rake tasks', :delete, feature_category:
|
|||
%w[db repositories]
|
||||
end
|
||||
|
||||
before_all do
|
||||
before(:all) do # rubocop:disable RSpec/BeforeAll
|
||||
Rake.application.rake_require 'active_record/railties/databases'
|
||||
Rake.application.rake_require 'tasks/gitlab/backup'
|
||||
Rake.application.rake_require 'tasks/gitlab/shell'
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ require 'spec_helper'
|
|||
require 'rake'
|
||||
|
||||
RSpec.describe 'gitlab:db namespace rake task', :silence_stdout, feature_category: :database do
|
||||
before_all do
|
||||
before(:all) do # rubocop:disable RSpec/BeforeAll
|
||||
Rake.application.rake_require 'active_record/railties/databases'
|
||||
Rake.application.rake_require 'tasks/seed_fu'
|
||||
Rake.application.rake_require 'tasks/gitlab/db'
|
||||
|
|
|
|||
Loading…
Reference in New Issue