Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-08-20 18:12:04 +00:00
parent c70a70ea42
commit bca3fb69e1
26 changed files with 467 additions and 48 deletions

View File

@ -0,0 +1,15 @@
<!-- This template is used for proposing changes to the left sidebar contextual navigation. This could include additions, removals, or general changes to overall hierarchy.-->
### Proposal
<!-- Use this section to explain the proposed changes, including details around usage and business drivers. -->
### Checklist
- [ ] If your proposal includes changes to the top-level menu items within the left sidebar, engage the [Foundations Product Design Manager](https://about.gitlab.com/handbook/product/categories/#foundations-group) for approval. The Foundations DRI will work with UX partners in product design, research, and technical writing, as applicable.
- [ ] Follow the [product development workflow](https://about.gitlab.com/handbook/product-development-flow/#validation-phase-2-problem-validation) validation process to ensure you are solving a well understood problem and that the proposed change is understandable and non-disruptive to users. Navigation-specific research is strongly encouraged.
- [ ] Engage the [Editor](https://about.gitlab.com/handbook/engineering/development/dev/create-editor/) team to ensure your proposal is in alignment with holistic changes happening to the left side bar.
- [ ] Consider whether you need to communicate the change somehow, or if you will have an interim period in the UI where your nav item will live in more than one place.
- [ ] Once implemented, update this [navigation map in Mural](https://app.mural.co/t/gitlab2474/m/gitlab2474/1589571490215/261462d0beb3043979374623710d3f2d6cfec1cb) with your navigation change.
/label ~UX ~"UI text" ~"documentation" ~"documentation" ~"Category:Navigation & Settings" ~"Category:Foundations" ~navigation

View File

@ -29,6 +29,7 @@ After your merge request has been approved according to our [approval guidelines
## Backports
- [ ] Once the MR is ready to be merged, create MRs targeting the latest 3 stable branches
* The 3 stable branches correspond to the versions in the title of the Security Release Tracking Issue.
* At this point, it might be easy to squash the commits from the MR into one
* You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [secpick documentation]
- [ ] Create each MR targeting the stable branch `X-Y-stable`, using the [Security Release merge request template].

View File

@ -18,6 +18,7 @@
## Author's checklist
- Consider taking [the GitLab Technical Writing Fundamentals course](https://gitlab.edcast.com/pathways/ECL-02528ee2-c334-4e16-abf3-e9d8b8260de4)
- [ ] Follow the:
- [Documentation Guidelines](https://docs.gitlab.com/ee/development/documentation/).
- [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide/).

View File

@ -14,7 +14,6 @@ See [the general developer security release guidelines](https://gitlab.com/gitla
- [ ] **On "Related issues" section, write down the [GitLab Security] issue it belongs to (i.e. `Related to <issue_id>`).**
- [ ] Merge request targets `master`, or a versioned stable branch (`X-Y-stable-ee`).
- [ ] Milestone is set for the version this merge request applies to. A closed milestone can be assigned via [quick actions].
- [ ] Title of this merge request is the same as for all backports.
- [ ] A [CHANGELOG entry] has been included, with `Changelog` trailer set to `security`.
- [ ] For the MR targeting `master`:
@ -24,6 +23,7 @@ See [the general developer security release guidelines](https://gitlab.com/gitla
- Please see the security release [Code reviews and Approvals](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#code-reviews-and-approvals) documentation for details on which AppSec team member to ping for approval.
- Trigger the [`package-and-qa` build]. The docker image generated will be used by the AppSec engineer to validate the security vulnerability has been remediated.
- [ ] For a backport MR targeting a versioned stable branch (`X-Y-stable-ee`)
- [ ] Milestone is set to the version this backport applies to. A closed milestone can be assigned via [quick actions].
- [ ] Ensure it's approved by a maintainer.
**Note:** Reviewer/maintainer should not be a Release Manager

View File

@ -0,0 +1,127 @@
<script>
import { GlDropdown, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui';
import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2';
import { selectedRect as getSelectedRect } from 'prosemirror-tables';
import { __ } from '~/locale';
export default {
name: 'TableCellWrapper',
components: {
NodeViewWrapper,
NodeViewContent,
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
},
props: {
editor: {
type: Object,
required: true,
},
getPos: {
type: Function,
required: true,
},
},
data() {
return {
displayActionsDropdown: false,
preventHide: true,
selectedRect: null,
};
},
computed: {
totalRows() {
return this.selectedRect?.map.height;
},
totalCols() {
return this.selectedRect?.map.width;
},
},
mounted() {
this.editor.on('selectionUpdate', this.handleSelectionUpdate);
this.handleSelectionUpdate();
},
beforeDestroy() {
this.editor.off('selectionUpdate', this.handleSelectionUpdate);
},
methods: {
handleSelectionUpdate() {
const { state } = this.editor;
const { $cursor } = state.selection;
this.displayActionsDropdown = $cursor?.pos - $cursor?.parentOffset - 1 === this.getPos();
if (this.displayActionsDropdown) {
this.selectedRect = getSelectedRect(state);
}
},
runCommand(command) {
this.editor.chain()[command]().run();
this.hideDropdown();
},
handleHide($event) {
if (this.preventHide) {
$event.preventDefault();
}
this.preventHide = true;
},
hideDropdown() {
this.preventHide = false;
this.$refs.dropdown?.hide();
},
},
i18n: {
insertColumnBefore: __('Insert column before'),
insertColumnAfter: __('Insert column after'),
insertRowBefore: __('Insert row before'),
insertRowAfter: __('Insert row after'),
deleteRow: __('Delete row'),
deleteColumn: __('Delete column'),
deleteTable: __('Delete table'),
editTableActions: __('Edit table'),
},
};
</script>
<template>
<node-view-wrapper class="gl-relative gl-padding-5 gl-min-w-10" as="td" @click="hideDropdown">
<span v-if="displayActionsDropdown" class="gl-absolute gl-right-0 gl-top-0">
<gl-dropdown
ref="dropdown"
dropup
icon="chevron-down"
size="small"
category="tertiary"
boundary="viewport"
no-caret
text-sr-only
:text="$options.i18n.editTableActions"
:popper-opts="{ positionFixed: true }"
@hide="handleHide($event)"
>
<gl-dropdown-item @click="runCommand('addColumnBefore')">
{{ $options.i18n.insertColumnBefore }}
</gl-dropdown-item>
<gl-dropdown-item @click="runCommand('addColumnAfter')">
{{ $options.i18n.insertColumnAfter }}
</gl-dropdown-item>
<gl-dropdown-item @click="runCommand('addRowBefore')">
{{ $options.i18n.insertRowBefore }}
</gl-dropdown-item>
<gl-dropdown-item @click="runCommand('addRowAfter')">
{{ $options.i18n.insertRowAfter }}
</gl-dropdown-item>
<gl-dropdown-divider />
<gl-dropdown-item v-if="totalRows > 2" @click="runCommand('deleteRow')">
{{ $options.i18n.deleteRow }}
</gl-dropdown-item>
<gl-dropdown-item v-if="totalCols > 1" @click="runCommand('deleteColumn')">
{{ $options.i18n.deleteColumn }}
</gl-dropdown-item>
<gl-dropdown-item @click="runCommand('deleteTable')">
{{ $options.i18n.deleteTable }}
</gl-dropdown-item>
</gl-dropdown>
</span>
<node-view-content />
</node-view-wrapper>
</template>

View File

@ -1,6 +1,12 @@
import { TableCell } from '@tiptap/extension-table-cell';
import { VueNodeViewRenderer } from '@tiptap/vue-2';
import TableCellWrapper from '../components/wrappers/table_cell.vue';
import { isBlockTablesFeatureEnabled } from '../services/feature_flags';
export default TableCell.extend({
content: isBlockTablesFeatureEnabled() ? 'block+' : 'inline*',
addNodeView() {
return VueNodeViewRenderer(TableCellWrapper);
},
});

View File

@ -9,7 +9,7 @@ Vue.use(Translate);
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
defaultClient: createDefaultClient({}, { assumeImmutableResults: true }),
});
export default () => {

View File

@ -3,6 +3,7 @@ import { debounce } from 'lodash';
import { mapState, mapGetters, mapActions } from 'vuex';
import {
EDITOR_TYPE_DIFF,
EDITOR_TYPE_CODE,
EDITOR_CODE_INSTANCE_FN,
EDITOR_DIFF_INSTANCE_FN,
} from '~/editor/constants';
@ -311,7 +312,10 @@ export default {
}),
);
if (this.fileType === MARKDOWN_FILE_TYPE) {
if (
this.fileType === MARKDOWN_FILE_TYPE &&
this.editor?.getEditorType() === EDITOR_TYPE_CODE
) {
import('~/editor/extensions/source_editor_markdown_ext')
.then(({ EditorMarkdownExtension: MarkdownExtension } = {}) => {
this.editor.use(

View File

@ -37,7 +37,7 @@ const issueTransitionOptions = [
help: s__(
'JiraService|Automatically transitions Jira issues to the "Done" category. %{linkStart}Learn more%{linkEnd}',
),
link: helpPagePath('integration/jira/index.html', {
link: helpPagePath('integration/jira/issues.html', {
anchor: 'automatic-issue-transitions',
}),
},
@ -47,7 +47,7 @@ const issueTransitionOptions = [
help: s__(
'JiraService|Set a custom final state by using transition IDs. %{linkStart}Learn about transition IDs%{linkEnd}',
),
link: helpPagePath('integration/jira/index.html', {
link: helpPagePath('integration/jira/issues.html', {
anchor: 'custom-issue-transitions',
}),
},

View File

@ -110,6 +110,7 @@ export default {
:href="artifact.path"
rel="nofollow"
download
class="gl-word-break-word"
data-testid="artifact-item"
>
<gl-sprintf :message="$options.i18n.downloadArtifact">

View File

@ -56,6 +56,11 @@ export default {
return this.$apollo.queries.participants.loading;
},
},
methods: {
toggleSidebar() {
this.$emit('toggleSidebar');
},
},
};
</script>
@ -66,5 +71,6 @@ export default {
:number-of-less-participants="7"
:lazy="false"
class="block participants"
@toggleSidebar="toggleSidebar"
/>
</template>

View File

@ -549,17 +549,12 @@
margin: 0;
font-size: $gl-font-size-small;
}
}
ul.dropdown-menu {
margin-top: 4px;
margin-bottom: 24px;
padding: 8px 0;
li {
margin: 0;
padding: 0 1px;
}
}
.gl-new-dropdown-item {
margin: 0;
padding: 0;
line-height: 1rem;
}
/* AsciiDoc(tor) built-in alignment roles */

View File

@ -245,11 +245,16 @@ $gl-line-height-42: px-to-rem(42px);
width: $grid-size * 28;
}
// Will be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1491
// Will be removed after https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2347 is merged
.gl-min-w-8 {
min-width: $gl-spacing-scale-8;
}
// Will be removed after https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2347 is merged
.gl-min-w-10 {
min-width: $gl-spacing-scale-10;
}
// Will both be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1526
.gl-opacity-6 {
opacity: 0.6;

View File

@ -63,8 +63,12 @@ class CommitStatus < Ci::ApplicationRecord
where('(ci_builds.created_at BETWEEN ? AND ?) AND (ci_builds.updated_at BETWEEN ? AND ?)', lookback, timeout, lookback, timeout)
}
# The scope applies `pluck` to split the queries. Use with care.
scope :for_project_paths, -> (paths) do
where(project: Project.where_full_path_in(Array(paths)))
# Pluck is used to split this query. Splitting the query is required for database decomposition for `ci_*` tables.
# https://docs.gitlab.com/ee/development/database/transaction_guidelines.html#database-decomposition-and-sharding
project_ids = Project.where_full_path_in(Array(paths)).pluck(:id)
where(project: project_ids)
end
scope :with_preloads, -> do

View File

@ -274,11 +274,6 @@ class Namespace < ApplicationRecord
projects.with_shared_runners.any?
end
# Internal Gitlab owned namespaces only (example: gitlab-org)
def unlimited_minutes?
shared_runners_minutes_limit == 0
end
def user_ids_for_project_authorizations
[owner_id]
end

View File

@ -20,8 +20,16 @@ module Projects
raise TransferError, s_('TransferProject|Please select a new namespace for your project.')
end
unless allowed_transfer?(current_user, project)
raise TransferError, s_('TransferProject|Transfer failed, please contact an admin.')
if @new_namespace.id == project.namespace_id
raise TransferError, s_('TransferProject|Project is already in this namespace.')
end
unless allowed_transfer_project?(current_user, project)
raise TransferError, s_("TransferProject|You don't have permission to transfer this project.")
end
unless allowed_to_transfer_to_namespace?(current_user, @new_namespace)
raise TransferError, s_("TransferProject|You don't have permission to transfer projects into that namespace.")
end
transfer(project)
@ -121,11 +129,12 @@ module Projects
Milestones::TransferService.new(current_user, group, project).execute
end
def allowed_transfer?(current_user, project)
@new_namespace &&
can?(current_user, :change_namespace, project) &&
@new_namespace.id != project.namespace_id &&
current_user.can?(:transfer_projects, @new_namespace)
def allowed_transfer_project?(current_user, project)
current_user.can?(:change_namespace, project)
end
def allowed_to_transfer_to_namespace?(current_user, namespace)
current_user.can?(:transfer_projects, namespace)
end
def update_namespace_and_visibility(to_namespace)

View File

@ -2,6 +2,8 @@
# rubocop:disable Style/SignalException
DEFAULT_BRANCH = 'master'
THROUGHPUT_LABELS = [
'Community contribution',
'security',
@ -29,12 +31,12 @@ end
has_milestone = !gitlab.mr_json["milestone"].nil?
unless has_milestone
unless has_milestone || (helper.security_mr? && gitlab.branch_for_base == DEFAULT_BRANCH)
warn "This merge request does not refer to an existing milestone.", sticky: false
end
has_pick_into_stable_label = gitlab.mr_labels.find { |label| label.start_with?('Pick into') }
if gitlab.branch_for_base != "master" && !has_pick_into_stable_label && !helper.security_mr?
warn "Most of the time, merge requests should target `master`. Otherwise, please set the relevant `Pick into X.Y` label."
if gitlab.branch_for_base != DEFAULT_BRANCH && !has_pick_into_stable_label && !helper.security_mr?
warn "Most of the time, merge requests should target `#{DEFAULT_BRANCH}`. Otherwise, please set the relevant `Pick into X.Y` label."
end

View File

@ -198,6 +198,7 @@ successfully, you must replicate their data using some other means.
|[Package Registry for PyPI](../../../user/packages/pypi_repository/index.md) | **Yes** (13.2) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|[Package Registry for Composer](../../../user/packages/composer_repository/index.md) | **Yes** (13.2) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|[Package Registry for generic packages](../../../user/packages/generic_packages/index.md) | **Yes** (13.5) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|[Package Registry for Helm charts](../../../user/packages/helm_repository/index.md) | **Yes** (14.1) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (14.1) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind [feature flag](../../feature_flags.md) `geo_package_file_replication`, enabled by default. |
|[Versioned Terraform State](../../terraform_state.md) | **Yes** (13.5) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.12) | Via Object Storage provider if supported. Native Geo support (Beta). | Replication is behind the feature flag `geo_terraform_state_version_replication`, enabled by default. Verification was behind the feature flag `geo_terraform_state_version_verification`, which was removed in 14.0|
|[External merge request diffs](../../merge_request_diffs.md) | **Yes** (13.5) | No | Via Object Storage provider if supported. Native Geo support (Beta). | Replication is behind the feature flag `geo_merge_request_diff_replication`, enabled by default. Verification is under development, behind the feature flag `geo_merge_request_diff_verification`, introduced in 14.0.|
|[Versioned snippets](../../../user/snippets.md#versioned-snippets) | [**Yes** (13.7)](https://gitlab.com/groups/gitlab-org/-/epics/2809) | [**Yes** (14.2)](https://gitlab.com/groups/gitlab-org/-/epics/2810) | No | Verification was implemented behind the feature flag `geo_snippet_repository_verification` in 13.11, and the feature flag was removed in 14.2. |

View File

@ -16,7 +16,7 @@ and shouldn't be relied upon for mission-critical production jobs.
## Quickstart
To start using Build Cloud for macOS Beta, you must submit an access request issue. After your
To start using Build Cloud for macOS Beta, you must submit an access request [issue](https://gitlab.com/gitlab-com/macos-buildcloud-runners-beta/-/issues/new?issuable_template=beta_access_request). After your
access has been granted and your build environment configured, you must configure your
`.gitlab-ci.yml` pipeline file:

View File

@ -45,6 +45,8 @@ Note the following:
a maintainer or administrator role in the group where the exported project lives.
- Project members with the [Owner role](../../permissions.md) are imported as Maintainers.
- Imported users can be mapped by their primary email on self-managed instances, if an administrative user (not an owner) does the import.
Additionally, the user must be an existing member of the namespace, or the user can be added as a
member of the project for contributions to be mapped.
Otherwise, a supplementary comment is left to mention that the original author and
the MRs, notes, or issues are owned by the importer.
- For project migration imports performed over GitLab.com Groups, preserving author information is

View File

@ -10780,6 +10780,9 @@ msgstr ""
msgid "Delete badge"
msgstr ""
msgid "Delete column"
msgstr ""
msgid "Delete comment"
msgstr ""
@ -10810,6 +10813,9 @@ msgstr ""
msgid "Delete project. Are you ABSOLUTELY SURE?"
msgstr ""
msgid "Delete row"
msgstr ""
msgid "Delete self monitoring project"
msgstr ""
@ -10828,6 +10834,9 @@ msgstr ""
msgid "Delete subscription"
msgstr ""
msgid "Delete table"
msgstr ""
msgid "Delete this attachment"
msgstr ""
@ -12128,6 +12137,9 @@ msgstr ""
msgid "Edit sidebar"
msgstr ""
msgid "Edit table"
msgstr ""
msgid "Edit this file only."
msgstr ""
@ -17811,6 +17823,12 @@ msgstr ""
msgid "Insert code"
msgstr ""
msgid "Insert column after"
msgstr ""
msgid "Insert column before"
msgstr ""
msgid "Insert image"
msgstr ""
@ -17820,6 +17838,12 @@ msgstr ""
msgid "Insert link"
msgstr ""
msgid "Insert row after"
msgstr ""
msgid "Insert row before"
msgstr ""
msgid "Insert suggestion"
msgstr ""
@ -35180,13 +35204,19 @@ msgstr ""
msgid "TransferProject|Project cannot be transferred, because tags are present in its container registry"
msgstr ""
msgid "TransferProject|Project is already in this namespace."
msgstr ""
msgid "TransferProject|Project with same name or path in target namespace already exists"
msgstr ""
msgid "TransferProject|Root namespace can't be updated if project has NPM packages"
msgstr ""
msgid "TransferProject|Transfer failed, please contact an admin."
msgid "TransferProject|You don't have permission to transfer projects into that namespace."
msgstr ""
msgid "TransferProject|You don't have permission to transfer this project."
msgstr ""
msgid "Tree view"

View File

@ -166,6 +166,7 @@
"prosemirror-markdown": "^1.5.1",
"prosemirror-model": "^1.13.3",
"prosemirror-state": "^1.3.4",
"prosemirror-tables": "^1.1.1",
"raphael": "^2.2.7",
"raw-loader": "^4.0.2",
"scrollparent": "^2.0.1",

View File

@ -0,0 +1,164 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { NodeViewWrapper } from '@tiptap/vue-2';
import { selectedRect as getSelectedRect } from 'prosemirror-tables';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import TableCellWrapper from '~/content_editor/components/wrappers/table_cell.vue';
import { createTestEditor, mockChainedCommands, emitEditorEvent } from '../../test_utils';
jest.mock('prosemirror-tables');
describe('content/components/wrappers/table_cell', () => {
let wrapper;
let editor;
let getPos;
const createWrapper = async () => {
wrapper = shallowMountExtended(TableCellWrapper, {
propsData: {
editor,
getPos,
},
});
};
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findDropdownItemWithLabel = (name) =>
wrapper
.findAllComponents(GlDropdownItem)
.filter((dropdownItem) => dropdownItem.text().includes(name))
.at(0);
const findDropdownItemWithLabelExists = (name) =>
wrapper
.findAllComponents(GlDropdownItem)
.filter((dropdownItem) => dropdownItem.text().includes(name)).length > 0;
const setCurrentPositionInCell = () => {
const { $cursor } = editor.state.selection;
getPos.mockReturnValue($cursor.pos - $cursor.parentOffset - 1);
};
const mockDropdownHide = () => {
/*
* TODO: Replace this method with using the scoped hide function
* provided by BootstrapVue https://bootstrap-vue.org/docs/components/dropdown.
* GitLab UI is not exposing it in the default scope
*/
findDropdown().vm.hide = jest.fn();
};
beforeEach(() => {
getPos = jest.fn();
editor = createTestEditor({});
});
afterEach(() => {
wrapper.destroy();
});
it('renders a td node-view-wrapper with relative position', () => {
createWrapper();
expect(wrapper.findComponent(NodeViewWrapper).classes()).toContain('gl-relative');
expect(wrapper.findComponent(NodeViewWrapper).props().as).toBe('td');
});
it('displays dropdown when selection cursor is on the cell', async () => {
setCurrentPositionInCell();
createWrapper();
await wrapper.vm.$nextTick();
expect(findDropdown().props()).toMatchObject({
category: 'tertiary',
icon: 'chevron-down',
size: 'small',
split: false,
});
expect(findDropdown().attributes()).toMatchObject({
boundary: 'viewport',
'no-caret': '',
});
});
it('does not display dropdown when selection cursor is not on the cell', async () => {
createWrapper();
await wrapper.vm.$nextTick();
expect(findDropdown().exists()).toBe(false);
});
describe('when dropdown is visible', () => {
beforeEach(async () => {
setCurrentPositionInCell();
getSelectedRect.mockReturnValue({
map: {
height: 1,
width: 1,
},
});
createWrapper();
await wrapper.vm.$nextTick();
mockDropdownHide();
});
it.each`
dropdownItemLabel | commandName
${'Insert column before'} | ${'addColumnBefore'}
${'Insert column after'} | ${'addColumnAfter'}
${'Insert row before'} | ${'addRowBefore'}
${'Insert row after'} | ${'addRowAfter'}
${'Delete table'} | ${'deleteTable'}
`(
'executes $commandName when $dropdownItemLabel button is clicked',
({ commandName, dropdownItemLabel }) => {
const mocks = mockChainedCommands(editor, [commandName, 'run']);
findDropdownItemWithLabel(dropdownItemLabel).vm.$emit('click');
expect(mocks[commandName]).toHaveBeenCalled();
},
);
it('does not allow deleting rows and columns', async () => {
expect(findDropdownItemWithLabelExists('Delete row')).toBe(false);
expect(findDropdownItemWithLabelExists('Delete column')).toBe(false);
});
it('allows deleting rows when there are more than 2 rows in the table', async () => {
const mocks = mockChainedCommands(editor, ['deleteRow', 'run']);
getSelectedRect.mockReturnValue({
map: {
height: 3,
},
});
emitEditorEvent({ tiptapEditor: editor, event: 'selectionUpdate' });
await wrapper.vm.$nextTick();
findDropdownItemWithLabel('Delete row').vm.$emit('click');
expect(mocks.deleteRow).toHaveBeenCalled();
});
it('allows deleting columns when there are more than 1 column in the table', async () => {
const mocks = mockChainedCommands(editor, ['deleteColumn', 'run']);
getSelectedRect.mockReturnValue({
map: {
width: 2,
},
});
emitEditorEvent({ tiptapEditor: editor, event: 'selectionUpdate' });
await wrapper.vm.$nextTick();
findDropdownItemWithLabel('Delete column').vm.$emit('click');
expect(mocks.deleteColumn).toHaveBeenCalled();
});
});
});

View File

@ -166,11 +166,6 @@ describe('RepoEditor', () => {
expect(tabs).toHaveLength(1);
expect(tabs.at(0).text()).toBe('Edit');
});
it('does not get markdown extension by default', async () => {
await createComponent();
expect(vm.editor.projectPath).toBeUndefined();
});
});
describe('when file is markdown', () => {
@ -218,11 +213,6 @@ describe('RepoEditor', () => {
});
expect(findTabs()).toHaveLength(0);
});
it('uses the markdown extension and sets it up correctly', async () => {
await createComponent({ activeFile });
expect(vm.editor.projectPath).toBe(vm.currentProjectId);
});
});
describe('when file is binary and not raw', () => {
@ -271,6 +261,31 @@ describe('RepoEditor', () => {
expect(vm.editor[fn]).toBe(EditorWebIdeExtension.prototype[fn]);
});
});
it.each`
prefix | activeFile | viewer | shouldHaveMarkdownExtension
${'Should not'} | ${createActiveFile()} | ${viewerTypes.edit} | ${false}
${'Should'} | ${dummyFile.markdown} | ${viewerTypes.edit} | ${true}
${'Should not'} | ${dummyFile.empty} | ${viewerTypes.edit} | ${false}
${'Should not'} | ${createActiveFile()} | ${viewerTypes.diff} | ${false}
${'Should not'} | ${dummyFile.markdown} | ${viewerTypes.diff} | ${false}
${'Should not'} | ${dummyFile.empty} | ${viewerTypes.diff} | ${false}
${'Should not'} | ${createActiveFile()} | ${viewerTypes.mr} | ${false}
${'Should not'} | ${dummyFile.markdown} | ${viewerTypes.mr} | ${false}
${'Should not'} | ${dummyFile.empty} | ${viewerTypes.mr} | ${false}
`(
'$prefix install markdown extension for $activeFile.name in $viewer viewer',
async ({ activeFile, viewer, shouldHaveMarkdownExtension } = {}) => {
await createComponent({ state: { viewer }, activeFile });
if (shouldHaveMarkdownExtension) {
expect(vm.editor.projectPath).toBe(vm.currentProjectId);
expect(vm.editor.togglePreview).toBeDefined();
} else {
expect(vm.editor.projectPath).toBeUndefined();
expect(vm.editor.togglePreview).toBeUndefined();
}
},
);
});
describe('setupEditor', () => {

View File

@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@ -45,6 +45,14 @@ describe('Sidebar Participants Widget', () => {
expect(findParticipants().props('loading')).toBe(true);
});
it('emits toggleSidebar event when participants child component emits toggleSidebar', async () => {
createComponent();
findParticipants().vm.$emit('toggleSidebar');
await nextTick();
expect(wrapper.emitted('toggleSidebar')).toEqual([[]]);
});
describe('when participants are loaded', () => {
beforeEach(() => {
createComponent({

View File

@ -292,10 +292,37 @@ RSpec.describe Projects::TransferService do
end
end
context 'target namespace allows developers to create projects' do
context 'target namespace matches current namespace' do
let(:group) { user.namespace }
it 'does not allow project transfer' do
transfer_result = execute_transfer
expect(transfer_result).to eq false
expect(project.namespace).to eq(user.namespace)
expect(project.errors[:new_namespace]).to include('Project is already in this namespace.')
end
end
context 'when user does not own the project' do
let(:project) { create(:project, :repository, :legacy_storage) }
before do
project.add_developer(user)
end
it 'does not allow project transfer to the target namespace' do
transfer_result = execute_transfer
expect(transfer_result).to eq false
expect(project.errors[:new_namespace]).to include("You don't have permission to transfer this project.")
end
end
context 'when user can create projects in the target namespace' do
let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) }
context 'the user is a member of the target namespace with developer permissions' do
context 'but has only developer permissions in the target namespace' do
before do
group.add_developer(user)
end
@ -305,7 +332,7 @@ RSpec.describe Projects::TransferService do
expect(transfer_result).to eq false
expect(project.namespace).to eq(user.namespace)
expect(project.errors[:new_namespace]).to include('Transfer failed, please contact an admin.')
expect(project.errors[:new_namespace]).to include("You don't have permission to transfer projects into that namespace.")
end
end
end