Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
946b1e2fe9
commit
9bf40d9fdc
|
@ -61,26 +61,6 @@ Layout/FirstArrayElementIndentation:
|
|||
- 'spec/models/ci/daily_build_group_report_result_spec.rb'
|
||||
- 'spec/models/ci/pipeline_spec.rb'
|
||||
- 'spec/models/ci/runner_version_spec.rb'
|
||||
- 'spec/models/ci/unit_test_spec.rb'
|
||||
- 'spec/models/clusters/applications/cert_manager_spec.rb'
|
||||
- 'spec/models/clusters/platforms/kubernetes_spec.rb'
|
||||
- 'spec/models/commit_collection_spec.rb'
|
||||
- 'spec/models/compare_spec.rb'
|
||||
- 'spec/models/concerns/id_in_ordered_spec.rb'
|
||||
- 'spec/models/concerns/noteable_spec.rb'
|
||||
- 'spec/models/diff_note_spec.rb'
|
||||
- 'spec/models/discussion_spec.rb'
|
||||
- 'spec/models/group_spec.rb'
|
||||
- 'spec/models/integration_spec.rb'
|
||||
- 'spec/models/integrations/chat_message/issue_message_spec.rb'
|
||||
- 'spec/models/integrations/chat_message/wiki_page_message_spec.rb'
|
||||
- 'spec/models/integrations/jira_spec.rb'
|
||||
- 'spec/models/label_note_spec.rb'
|
||||
- 'spec/models/merge_request/cleanup_schedule_spec.rb'
|
||||
- 'spec/models/merge_request_diff_spec.rb'
|
||||
- 'spec/models/merge_request_spec.rb'
|
||||
- 'spec/models/operations/feature_flags/strategy_spec.rb'
|
||||
- 'spec/models/project_group_link_spec.rb'
|
||||
- 'spec/models/repository_spec.rb'
|
||||
- 'spec/models/user_preference_spec.rb'
|
||||
- 'spec/models/user_spec.rb'
|
||||
|
|
|
@ -1 +1 @@
|
|||
2ff0039f15ef06063925ff6c0406f6e092ad2435
|
||||
5cba52f4acb04ddbe27d8b7cb2e936ea0be45ae1
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
<script>
|
||||
import { GlDropdownItem } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlDropdownItem,
|
||||
},
|
||||
|
||||
props: {
|
||||
char: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
referenceProps: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
|
||||
items: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
|
||||
command: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
selectedIndex: 0,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
isUser() {
|
||||
return this.referenceProps.referenceType === 'user';
|
||||
},
|
||||
|
||||
isIssue() {
|
||||
return this.referenceProps.referenceType === 'issue';
|
||||
},
|
||||
|
||||
isMergeRequest() {
|
||||
return this.referenceProps.referenceType === 'merge_request';
|
||||
},
|
||||
|
||||
isMilestone() {
|
||||
return this.referenceProps.referenceType === 'milestone';
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
items() {
|
||||
this.selectedIndex = 0;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
getReferenceText(item) {
|
||||
switch (this.referenceProps.referenceType) {
|
||||
case 'user':
|
||||
return `${this.char}${item.username}`;
|
||||
case 'issue':
|
||||
case 'merge_request':
|
||||
return `${this.char}${item.iid}`;
|
||||
case 'milestone':
|
||||
return `${this.char}${item.title}`;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
onKeyDown({ event }) {
|
||||
if (event.key === 'ArrowUp') {
|
||||
this.upHandler();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
this.downHandler();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
this.enterHandler();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
upHandler() {
|
||||
this.selectedIndex = (this.selectedIndex + this.items.length - 1) % this.items.length;
|
||||
},
|
||||
|
||||
downHandler() {
|
||||
this.selectedIndex = (this.selectedIndex + 1) % this.items.length;
|
||||
},
|
||||
|
||||
enterHandler() {
|
||||
this.selectItem(this.selectedIndex);
|
||||
},
|
||||
|
||||
selectItem(index) {
|
||||
const item = this.items[index];
|
||||
|
||||
if (item) {
|
||||
this.command({
|
||||
text: this.getReferenceText(item),
|
||||
href: '#',
|
||||
...this.referenceProps,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul
|
||||
:class="{ show: items.length > 0 }"
|
||||
class="gl-new-dropdown dropdown-men"
|
||||
data-testid="content-editor-reference-dropdown"
|
||||
>
|
||||
<div class="gl-new-dropdown-inner gl-overflow-y-auto">
|
||||
<gl-dropdown-item
|
||||
v-for="(item, index) in items"
|
||||
:key="index"
|
||||
:class="{ 'gl-bg-gray-50': index === selectedIndex }"
|
||||
@click="selectItem(index)"
|
||||
>
|
||||
<span v-if="isUser">
|
||||
{{ item.username }}
|
||||
<small>{{ item.name }}</small>
|
||||
</span>
|
||||
<span v-if="isIssue || isMergeRequest || isMilestone">
|
||||
<small>{{ item.iid }}</small>
|
||||
{{ item.title }}
|
||||
</span>
|
||||
</gl-dropdown-item>
|
||||
</div>
|
||||
</ul>
|
||||
</template>
|
|
@ -1 +1,15 @@
|
|||
export { Heading as default } from '@tiptap/extension-heading';
|
||||
import { Heading } from '@tiptap/extension-heading';
|
||||
import { textblockTypeInputRule } from '@tiptap/core';
|
||||
|
||||
export default Heading.extend({
|
||||
addInputRules() {
|
||||
return this.options.levels.map((level) => {
|
||||
return textblockTypeInputRule({
|
||||
// make sure heading regex doesn't conflict with issue references
|
||||
find: new RegExp(`^(#{1,${level}})[ \t]$`),
|
||||
type: this.type,
|
||||
getAttributes: { level },
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -57,7 +57,7 @@ export default Node.create({
|
|||
'a',
|
||||
{
|
||||
class: node.attrs.className,
|
||||
href: node.attrs.href,
|
||||
href: '#',
|
||||
'data-reference-type': node.attrs.referenceType,
|
||||
'data-original': node.attrs.originalText,
|
||||
},
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
import { Node } from '@tiptap/core';
|
||||
import { VueRenderer } from '@tiptap/vue-2';
|
||||
import tippy from 'tippy.js';
|
||||
import Suggestion from '@tiptap/suggestion';
|
||||
import { PluginKey } from 'prosemirror-state';
|
||||
import { isFunction } from 'lodash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import Reference from '../components/reference_dropdown.vue';
|
||||
|
||||
function createSuggestionPlugin({ editor, char, dataSource, search, referenceProps }) {
|
||||
return Suggestion({
|
||||
editor,
|
||||
char,
|
||||
pluginKey: new PluginKey(`reference_${referenceProps.referenceType}`),
|
||||
command: ({ editor: tiptapEditor, range, props }) => {
|
||||
tiptapEditor
|
||||
.chain()
|
||||
.focus()
|
||||
.insertContentAt(range, [{ type: 'reference', attrs: props }])
|
||||
.run();
|
||||
},
|
||||
|
||||
async items({ query }) {
|
||||
if (!dataSource) return [];
|
||||
|
||||
try {
|
||||
const items = await (isFunction(dataSource) ? dataSource() : axios.get(dataSource));
|
||||
return items.data.filter(search(query));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
render: () => {
|
||||
let component;
|
||||
let popup;
|
||||
|
||||
return {
|
||||
onStart: (props) => {
|
||||
component = new VueRenderer(Reference, {
|
||||
propsData: {
|
||||
...props,
|
||||
char,
|
||||
referenceProps,
|
||||
},
|
||||
editor: props.editor,
|
||||
});
|
||||
|
||||
if (!props.clientRect) {
|
||||
return;
|
||||
}
|
||||
|
||||
popup = tippy('body', {
|
||||
getReferenceClientRect: props.clientRect,
|
||||
appendTo: () => document.body,
|
||||
content: component.element,
|
||||
showOnCreate: true,
|
||||
interactive: true,
|
||||
trigger: 'manual',
|
||||
placement: 'bottom-start',
|
||||
});
|
||||
},
|
||||
|
||||
onUpdate(props) {
|
||||
component.updateProps(props);
|
||||
|
||||
if (!props.clientRect) {
|
||||
return;
|
||||
}
|
||||
|
||||
popup?.[0].setProps({
|
||||
getReferenceClientRect: props.clientRect,
|
||||
});
|
||||
},
|
||||
|
||||
onKeyDown(props) {
|
||||
if (props.event.key === 'Escape') {
|
||||
popup?.[0].hide();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return component.ref?.onKeyDown(props);
|
||||
},
|
||||
|
||||
onExit() {
|
||||
popup?.[0].destroy();
|
||||
component.destroy();
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default Node.create({
|
||||
name: 'suggestions',
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
createSuggestionPlugin({
|
||||
editor: this.editor,
|
||||
char: '@',
|
||||
dataSource: gl.GfmAutoComplete?.dataSources.members,
|
||||
referenceProps: {
|
||||
className: 'gfm gfm-project_member',
|
||||
referenceType: 'user',
|
||||
},
|
||||
search: (query) => ({ name, username }) =>
|
||||
name.toLocaleLowerCase().includes(query.toLocaleLowerCase()) ||
|
||||
username.toLocaleLowerCase().includes(query.toLocaleLowerCase()),
|
||||
}),
|
||||
createSuggestionPlugin({
|
||||
editor: this.editor,
|
||||
char: '#',
|
||||
dataSource: gl.GfmAutoComplete?.dataSources.issues,
|
||||
referenceProps: {
|
||||
className: 'gfm gfm-issue',
|
||||
referenceType: 'issue',
|
||||
},
|
||||
search: (query) => ({ iid, title }) =>
|
||||
String(iid).toLocaleLowerCase().includes(query.toLocaleLowerCase()) ||
|
||||
title.toLocaleLowerCase().includes(query.toLocaleLowerCase()),
|
||||
}),
|
||||
createSuggestionPlugin({
|
||||
editor: this.editor,
|
||||
char: '!',
|
||||
dataSource: gl.GfmAutoComplete?.dataSources.mergeRequests,
|
||||
referenceProps: {
|
||||
className: 'gfm gfm-issue',
|
||||
referenceType: 'merge_request',
|
||||
},
|
||||
search: (query) => ({ iid, title }) =>
|
||||
String(iid).toLocaleLowerCase().includes(query.toLocaleLowerCase()) ||
|
||||
title.toLocaleLowerCase().includes(query.toLocaleLowerCase()),
|
||||
}),
|
||||
createSuggestionPlugin({
|
||||
editor: this.editor,
|
||||
char: '%',
|
||||
dataSource: gl.GfmAutoComplete?.dataSources.milestones,
|
||||
referenceProps: {
|
||||
className: 'gfm gfm-milestone',
|
||||
referenceType: 'milestone',
|
||||
},
|
||||
search: (query) => ({ iid, title }) =>
|
||||
String(iid).toLocaleLowerCase().includes(query.toLocaleLowerCase()) ||
|
||||
title.toLocaleLowerCase().includes(query.toLocaleLowerCase()),
|
||||
}),
|
||||
];
|
||||
},
|
||||
});
|
|
@ -46,6 +46,7 @@ import ReferenceDefinition from '../extensions/reference_definition';
|
|||
import Sourcemap from '../extensions/sourcemap';
|
||||
import Strike from '../extensions/strike';
|
||||
import Subscript from '../extensions/subscript';
|
||||
import Suggestions from '../extensions/suggestions';
|
||||
import Superscript from '../extensions/superscript';
|
||||
import Table from '../extensions/table';
|
||||
import TableCell from '../extensions/table_cell';
|
||||
|
@ -133,6 +134,7 @@ export const createContentEditor = ({
|
|||
Sourcemap,
|
||||
Strike,
|
||||
Subscript,
|
||||
Suggestions,
|
||||
Superscript,
|
||||
TableCell,
|
||||
TableHeader,
|
||||
|
|
|
@ -38,7 +38,12 @@ export default {
|
|||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.textarea.focus();
|
||||
this.focus();
|
||||
},
|
||||
methods: {
|
||||
focus() {
|
||||
this.$refs.textarea?.focus();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<script>
|
||||
import { GlAlert } from '@gitlab/ui';
|
||||
import $ from 'jquery';
|
||||
import Autosave from '~/autosave';
|
||||
import { getDraft, updateDraft, getLockVersion, clearDraft } from '~/lib/utils/autosave';
|
||||
import { IssuableType } from '~/issues/constants';
|
||||
import eventHub from '../event_hub';
|
||||
import EditActions from './edit_actions.vue';
|
||||
|
@ -76,10 +75,17 @@ export default {
|
|||
},
|
||||
},
|
||||
data() {
|
||||
const autosaveKey = [document.location.pathname, document.location.search];
|
||||
const descriptionAutosaveKey = [...autosaveKey, 'description'];
|
||||
const titleAutosaveKey = [...autosaveKey, 'title'];
|
||||
|
||||
return {
|
||||
titleAutosaveKey,
|
||||
descriptionAutosaveKey,
|
||||
autosaveReset: false,
|
||||
formData: {
|
||||
title: this.formState.title,
|
||||
description: this.formState.description,
|
||||
title: getDraft(titleAutosaveKey) || this.formState.title,
|
||||
description: getDraft(descriptionAutosaveKey) || this.formState.description,
|
||||
},
|
||||
showOutdatedDescriptionWarning: false,
|
||||
};
|
||||
|
@ -118,58 +124,40 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
initAutosave() {
|
||||
const {
|
||||
description: {
|
||||
$refs: { textarea },
|
||||
},
|
||||
title: {
|
||||
$refs: { input },
|
||||
},
|
||||
} = this.$refs;
|
||||
|
||||
this.autosaveDescription = new Autosave(
|
||||
$(textarea),
|
||||
[document.location.pathname, document.location.search, 'description'],
|
||||
null,
|
||||
this.formState.lock_version,
|
||||
);
|
||||
|
||||
const savedLockVersion = this.autosaveDescription.getSavedLockVersion();
|
||||
const savedLockVersion = getLockVersion(this.descriptionAutosaveKey);
|
||||
|
||||
this.showOutdatedDescriptionWarning =
|
||||
savedLockVersion && String(this.formState.lock_version) !== savedLockVersion;
|
||||
|
||||
this.autosaveTitle = new Autosave($(input), [
|
||||
document.location.pathname,
|
||||
document.location.search,
|
||||
'title',
|
||||
]);
|
||||
},
|
||||
resetAutosave() {
|
||||
this.autosaveDescription.reset();
|
||||
this.autosaveTitle.reset();
|
||||
this.autosaveReset = true;
|
||||
clearDraft(this.descriptionAutosaveKey);
|
||||
clearDraft(this.titleAutosaveKey);
|
||||
},
|
||||
keepAutosave() {
|
||||
const {
|
||||
description: {
|
||||
$refs: { textarea },
|
||||
},
|
||||
} = this.$refs;
|
||||
|
||||
textarea.focus();
|
||||
this.$refs.description.focus();
|
||||
this.showOutdatedDescriptionWarning = false;
|
||||
},
|
||||
discardAutosave() {
|
||||
const {
|
||||
description: {
|
||||
$refs: { textarea },
|
||||
},
|
||||
} = this.$refs;
|
||||
|
||||
textarea.value = this.initialDescriptionText;
|
||||
textarea.focus();
|
||||
this.formData.description = this.initialDescriptionText;
|
||||
clearDraft(this.descriptionAutosaveKey);
|
||||
this.$refs.description.focus();
|
||||
this.showOutdatedDescriptionWarning = false;
|
||||
},
|
||||
updateTitleDraft(title) {
|
||||
updateDraft(this.titleAutosaveKey, title);
|
||||
},
|
||||
updateDescriptionDraft(description) {
|
||||
/*
|
||||
* This conditional statement prevents a race-condition
|
||||
* between clearing the draft and submitting a new draft
|
||||
* update while the user is typing. It happens when saving
|
||||
* using the cmd + enter keyboard shortcut.
|
||||
*/
|
||||
if (!this.autosaveReset) {
|
||||
updateDraft(this.descriptionAutosaveKey, description, this.formState.lock_version);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -194,7 +182,7 @@ export default {
|
|||
>
|
||||
<div class="row gl-mb-3">
|
||||
<div class="col-12">
|
||||
<issuable-title-field ref="title" v-model="formData.title" />
|
||||
<issuable-title-field ref="title" v-model="formData.title" @input="updateTitleDraft" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
@ -220,6 +208,7 @@ export default {
|
|||
:markdown-docs-path="markdownDocsPath"
|
||||
:can-attach-file="canAttachFile"
|
||||
:enable-autocomplete="enableAutocomplete"
|
||||
@input="updateDescriptionDraft"
|
||||
/>
|
||||
|
||||
<edit-actions :endpoint="endpoint" :form-state="formState" :issuable-type="issuableType" />
|
||||
|
|
|
@ -1,8 +1,27 @@
|
|||
import { isString } from 'lodash';
|
||||
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
|
||||
|
||||
const normalizeKey = (autosaveKey) => {
|
||||
let normalizedKey;
|
||||
|
||||
if (Array.isArray(autosaveKey) && autosaveKey.every(isString)) {
|
||||
normalizedKey = autosaveKey.join('/');
|
||||
} else if (isString(autosaveKey)) {
|
||||
normalizedKey = autosaveKey;
|
||||
} else {
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
throw new Error('Invalid autosave key');
|
||||
}
|
||||
|
||||
return `autosave/${normalizedKey}`;
|
||||
};
|
||||
|
||||
const lockVersionKey = (autosaveKey) => `${normalizeKey(autosaveKey)}/lockVersion`;
|
||||
|
||||
export const clearDraft = (autosaveKey) => {
|
||||
try {
|
||||
window.localStorage.removeItem(`autosave/${autosaveKey}`);
|
||||
window.localStorage.removeItem(normalizeKey(autosaveKey));
|
||||
window.localStorage.removeItem(lockVersionKey(autosaveKey));
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
|
@ -11,7 +30,7 @@ export const clearDraft = (autosaveKey) => {
|
|||
|
||||
export const getDraft = (autosaveKey) => {
|
||||
try {
|
||||
return window.localStorage.getItem(`autosave/${autosaveKey}`);
|
||||
return window.localStorage.getItem(normalizeKey(autosaveKey));
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
|
@ -19,9 +38,22 @@ export const getDraft = (autosaveKey) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const updateDraft = (autosaveKey, text) => {
|
||||
export const getLockVersion = (autosaveKey) => {
|
||||
try {
|
||||
window.localStorage.setItem(`autosave/${autosaveKey}`, text);
|
||||
return window.localStorage.getItem(lockVersionKey(autosaveKey));
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateDraft = (autosaveKey, text, lockVersion) => {
|
||||
try {
|
||||
window.localStorage.setItem(normalizeKey(autosaveKey), text);
|
||||
if (lockVersion) {
|
||||
window.localStorage.setItem(lockVersionKey(autosaveKey), lockVersion);
|
||||
}
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { GlEmptyState } from '@gitlab/ui';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { n__ } from '~/locale';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
|
||||
|
@ -69,7 +69,7 @@ export default {
|
|||
return this.queryVariables;
|
||||
},
|
||||
error() {
|
||||
createFlash({ message: FETCH_IMAGES_LIST_ERROR_MESSAGE });
|
||||
createAlert({ message: FETCH_IMAGES_LIST_ERROR_MESSAGE });
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { GlResizeObserverDirective, GlEmptyState } from '@gitlab/ui';
|
||||
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import Tracking from '~/tracking';
|
||||
|
@ -66,7 +66,7 @@ export default {
|
|||
this.updateBreadcrumb();
|
||||
},
|
||||
error() {
|
||||
createFlash({ message: FETCH_IMAGES_LIST_ERROR_MESSAGE });
|
||||
createAlert({ message: FETCH_IMAGES_LIST_ERROR_MESSAGE });
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from '@gitlab/ui';
|
||||
import { get } from 'lodash';
|
||||
import getContainerRepositoriesQuery from 'shared_queries/container_registry/get_container_repositories.query.graphql';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { FILTERED_SEARCH_TERM } from '~/packages_and_registries/shared/constants';
|
||||
import Tracking from '~/tracking';
|
||||
import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue';
|
||||
|
@ -100,7 +100,7 @@ export default {
|
|||
this.containerRepositoriesCount = data[this.graphqlResource]?.containerRepositoriesCount;
|
||||
},
|
||||
error() {
|
||||
createFlash({ message: FETCH_IMAGES_LIST_ERROR_MESSAGE });
|
||||
createAlert({ message: FETCH_IMAGES_LIST_ERROR_MESSAGE });
|
||||
},
|
||||
},
|
||||
additionalDetails: {
|
||||
|
@ -115,7 +115,7 @@ export default {
|
|||
return data[this.graphqlResource]?.containerRepositories.nodes;
|
||||
},
|
||||
error() {
|
||||
createFlash({ message: FETCH_IMAGES_LIST_ERROR_MESSAGE });
|
||||
createAlert({ message: FETCH_IMAGES_LIST_ERROR_MESSAGE });
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Api from '~/api';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/flash';
|
||||
import {
|
||||
DELETE_PACKAGE_ERROR_MESSAGE,
|
||||
DELETE_PACKAGE_FILE_ERROR_MESSAGE,
|
||||
|
@ -20,7 +20,7 @@ export const fetchPackageVersions = ({ commit, state }) => {
|
|||
}
|
||||
})
|
||||
.catch(() => {
|
||||
createFlash({ message: FETCH_PACKAGE_VERSIONS_ERROR, type: 'warning' });
|
||||
createAlert({ message: FETCH_PACKAGE_VERSIONS_ERROR, variant: VARIANT_WARNING });
|
||||
})
|
||||
.finally(() => {
|
||||
commit(types.SET_LOADING, false);
|
||||
|
@ -33,7 +33,7 @@ export const deletePackage = ({
|
|||
},
|
||||
}) => {
|
||||
return Api.deleteProjectPackage(project_id, id).catch(() => {
|
||||
createFlash({ message: DELETE_PACKAGE_ERROR_MESSAGE, type: 'warning' });
|
||||
createAlert({ message: DELETE_PACKAGE_ERROR_MESSAGE, variant: VARIANT_WARNING });
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -51,9 +51,9 @@ export const deletePackageFile = (
|
|||
.then(() => {
|
||||
const filtered = packageFiles.filter((f) => f.id !== fileId);
|
||||
commit(types.UPDATE_PACKAGE_FILES, filtered);
|
||||
createFlash({ message: DELETE_PACKAGE_FILE_SUCCESS_MESSAGE, type: 'success' });
|
||||
createAlert({ message: DELETE_PACKAGE_FILE_SUCCESS_MESSAGE, variant: VARIANT_SUCCESS });
|
||||
})
|
||||
.catch(() => {
|
||||
createFlash({ message: DELETE_PACKAGE_FILE_ERROR_MESSAGE, type: 'warning' });
|
||||
createAlert({ message: DELETE_PACKAGE_FILE_ERROR_MESSAGE, variant: VARIANT_WARNING });
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert, VARIANT_INFO } from '~/flash';
|
||||
import { historyReplaceState } from '~/lib/utils/common_utils';
|
||||
import { s__ } from '~/locale';
|
||||
import {
|
||||
|
@ -84,7 +84,7 @@ export default {
|
|||
const showAlert = urlParams.get(SHOW_DELETE_SUCCESS_ALERT);
|
||||
if (showAlert) {
|
||||
// to be refactored to use gl-alert
|
||||
createFlash({ message: DELETE_PACKAGE_SUCCESS_MESSAGE, type: 'notice' });
|
||||
createAlert({ message: DELETE_PACKAGE_SUCCESS_MESSAGE, variant: VARIANT_INFO });
|
||||
const cleanUrl = window.location.href.split('?')[0];
|
||||
historyReplaceState(cleanUrl);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Api from '~/api';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert, VARIANT_SUCCESS } from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { DELETE_PACKAGE_ERROR_MESSAGE } from '~/packages_and_registries/shared/constants';
|
||||
import {
|
||||
|
@ -43,7 +43,7 @@ export const requestPackagesList = ({ dispatch, state }, params = {}) => {
|
|||
dispatch('receivePackagesListSuccess', { data, headers });
|
||||
})
|
||||
.catch(() => {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: FETCH_PACKAGES_LIST_ERROR_MESSAGE,
|
||||
});
|
||||
})
|
||||
|
@ -54,7 +54,7 @@ export const requestPackagesList = ({ dispatch, state }, params = {}) => {
|
|||
|
||||
export const requestDeletePackage = ({ dispatch, state }, { _links }) => {
|
||||
if (!_links || !_links.delete_api_path) {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: DELETE_PACKAGE_ERROR_MESSAGE,
|
||||
});
|
||||
const error = new Error(MISSING_DELETE_PATH_ERROR);
|
||||
|
@ -69,14 +69,14 @@ export const requestDeletePackage = ({ dispatch, state }, { _links }) => {
|
|||
const page = getNewPaginationPage(currentPage, perPage, total - 1);
|
||||
|
||||
dispatch('requestPackagesList', { page });
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: DELETE_PACKAGE_SUCCESS_MESSAGE,
|
||||
type: 'success',
|
||||
variant: VARIANT_SUCCESS,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch('setLoading', false);
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: DELETE_PACKAGE_ERROR_MESSAGE,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import destroyPackageMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/flash';
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
import { DELETE_PACKAGE_SUCCESS_MESSAGE } from '~/packages_and_registries/package_registry/constants';
|
||||
|
@ -39,15 +39,15 @@ export default {
|
|||
throw data.destroyPackage.errors[0];
|
||||
}
|
||||
if (this.showSuccessAlert) {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: this.$options.i18n.successMessage,
|
||||
type: 'success',
|
||||
variant: VARIANT_SUCCESS,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: this.$options.i18n.errorMessage,
|
||||
type: 'warning',
|
||||
variant: VARIANT_WARNING,
|
||||
captureError: true,
|
||||
error,
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
GlTabs,
|
||||
GlSprintf,
|
||||
} from '@gitlab/ui';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/flash';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { numberToHumanSize } from '~/lib/utils/number_utils';
|
||||
import { objectToQuery } from '~/lib/utils/url_utility';
|
||||
|
@ -101,7 +101,7 @@ export default {
|
|||
return data.package || {};
|
||||
},
|
||||
error(error) {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: FETCH_PACKAGE_DETAILS_ERROR_MESSAGE,
|
||||
captureError: true,
|
||||
error,
|
||||
|
@ -205,20 +205,20 @@ export default {
|
|||
if (data?.destroyPackageFiles?.errors[0]) {
|
||||
throw data.destroyPackageFiles.errors[0];
|
||||
}
|
||||
createFlash({
|
||||
createAlert({
|
||||
message:
|
||||
ids.length === 1
|
||||
? DELETE_PACKAGE_FILE_SUCCESS_MESSAGE
|
||||
: DELETE_PACKAGE_FILES_SUCCESS_MESSAGE,
|
||||
type: 'success',
|
||||
variant: VARIANT_SUCCESS,
|
||||
});
|
||||
} catch (error) {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message:
|
||||
ids.length === 1
|
||||
? DELETE_PACKAGE_FILE_ERROR_MESSAGE
|
||||
: DELETE_PACKAGE_FILES_ERROR_MESSAGE,
|
||||
type: 'warning',
|
||||
variant: VARIANT_WARNING,
|
||||
captureError: true,
|
||||
error,
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert, VARIANT_INFO } from '~/flash';
|
||||
import { historyReplaceState } from '~/lib/utils/common_utils';
|
||||
import { s__ } from '~/locale';
|
||||
import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages_and_registries/shared/constants';
|
||||
|
@ -105,7 +105,7 @@ export default {
|
|||
const showAlert = urlParams.get(SHOW_DELETE_SUCCESS_ALERT);
|
||||
if (showAlert) {
|
||||
// to be refactored to use gl-alert
|
||||
createFlash({ message: DELETE_PACKAGE_SUCCESS_MESSAGE, type: 'notice' });
|
||||
createAlert({ message: DELETE_PACKAGE_SUCCESS_MESSAGE, variant: VARIANT_INFO });
|
||||
const cleanUrl = window.location.href.split('?')[0];
|
||||
historyReplaceState(cleanUrl);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { GlDropdown, GlDropdownItem, GlButton } from '@gitlab/ui';
|
||||
import csrf from '~/lib/utils/csrf';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import RevisionCard from './revision_card.vue';
|
||||
|
@ -9,6 +9,8 @@ export default {
|
|||
components: {
|
||||
RevisionCard,
|
||||
GlButton,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
},
|
||||
props: {
|
||||
projectCompareIndexPath: {
|
||||
|
@ -53,6 +55,10 @@ export default {
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
straight: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -67,8 +73,27 @@ export default {
|
|||
revision: this.paramsTo,
|
||||
refsProjectPath: this.sourceProjectRefsPath,
|
||||
},
|
||||
isStraight: this.straight,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
straightModeDropdownItems() {
|
||||
return [
|
||||
{
|
||||
modeType: 'off',
|
||||
isEnabled: false,
|
||||
content: '..',
|
||||
testId: 'disableStraightModeButton',
|
||||
},
|
||||
{
|
||||
modeType: 'on',
|
||||
isEnabled: true,
|
||||
content: '...',
|
||||
testId: 'enableStraightModeButton',
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
this.$refs.form.submit();
|
||||
|
@ -85,6 +110,9 @@ export default {
|
|||
onSwapRevision() {
|
||||
[this.from, this.to] = [this.to, this.from]; // swaps 'from' and 'to'
|
||||
},
|
||||
setStraightMode(isStraight) {
|
||||
this.isStraight = isStraight;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -112,10 +140,22 @@ export default {
|
|||
@selectRevision="onSelectRevision"
|
||||
/>
|
||||
<div
|
||||
class="compare-ellipsis gl-display-flex gl-justify-content-center gl-align-items-center gl-align-self-end gl-my-4 gl-md-my-0"
|
||||
class="gl-display-flex gl-justify-content-center gl-align-items-center gl-align-self-end gl-my-3 gl-md-my-0 gl-pl-3 gl-pr-3"
|
||||
data-testid="ellipsis"
|
||||
>
|
||||
...
|
||||
<input :value="isStraight ? 'true' : 'false'" type="hidden" name="straight" />
|
||||
<gl-dropdown data-testid="modeDropdown" :text="isStraight ? '...' : '..'" size="small">
|
||||
<gl-dropdown-item
|
||||
v-for="mode in straightModeDropdownItems"
|
||||
:key="mode.modeType"
|
||||
:is-check-item="true"
|
||||
:is-checked="isStraight == mode.isEnabled"
|
||||
:data-testid="mode.testId"
|
||||
@click="setStraightMode(mode.isEnabled)"
|
||||
>
|
||||
<span class="dropdown-menu-inner-content"> {{ mode.content }} </span>
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
</div>
|
||||
<revision-card
|
||||
data-testid="targetRevisionCard"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import CompareApp from './components/app.vue';
|
||||
|
||||
export default function init() {
|
||||
|
@ -9,6 +10,7 @@ export default function init() {
|
|||
targetProjectRefsPath,
|
||||
paramsFrom,
|
||||
paramsTo,
|
||||
straight,
|
||||
projectCompareIndexPath,
|
||||
projectMergeRequestPath,
|
||||
createMrPath,
|
||||
|
@ -29,6 +31,7 @@ export default function init() {
|
|||
targetProjectRefsPath,
|
||||
paramsFrom,
|
||||
paramsTo,
|
||||
straight: parseBoolean(straight),
|
||||
projectCompareIndexPath,
|
||||
projectMergeRequestPath,
|
||||
createMrPath,
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
GlButton,
|
||||
} from '@gitlab/ui';
|
||||
import { kebabCase, snakeCase } from 'lodash';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { IssuableType } from '~/issues/constants';
|
||||
import { timeFor } from '~/lib/utils/datetime_utility';
|
||||
|
@ -125,7 +125,7 @@ export default {
|
|||
return data?.workspace?.issuable.attribute;
|
||||
},
|
||||
error(error) {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: this.i18n.currentFetchError,
|
||||
captureError: true,
|
||||
error,
|
||||
|
@ -179,7 +179,7 @@ export default {
|
|||
return [];
|
||||
},
|
||||
error(error) {
|
||||
createFlash({ message: this.i18n.listFetchError, captureError: true, error });
|
||||
createAlert({ message: this.i18n.listFetchError, captureError: true, error });
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -280,7 +280,7 @@ export default {
|
|||
})
|
||||
.then(({ data }) => {
|
||||
if (data.issuableSetAttribute?.errors?.length) {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: data.issuableSetAttribute.errors[0],
|
||||
captureError: true,
|
||||
error: data.issuableSetAttribute.errors[0],
|
||||
|
@ -290,7 +290,7 @@ export default {
|
|||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
createFlash({ message: this.i18n.updateError, captureError: true, error });
|
||||
createAlert({ message: this.i18n.updateError, captureError: true, error });
|
||||
})
|
||||
.finally(() => {
|
||||
this.updating = false;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { GlDropdownForm, GlIcon, GlLoadingIcon, GlToggle, GlTooltipDirective } from '@gitlab/ui';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { IssuableType } from '~/issues/constants';
|
||||
import { isLoggedIn } from '~/lib/utils/common_utils';
|
||||
import { __, sprintf } from '~/locale';
|
||||
|
@ -74,7 +74,7 @@ export default {
|
|||
this.$emit('subscribedUpdated', data.workspace?.issuable?.subscribed);
|
||||
},
|
||||
error() {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: sprintf(
|
||||
__('Something went wrong while setting %{issuableType} notifications.'),
|
||||
{
|
||||
|
@ -138,7 +138,7 @@ export default {
|
|||
},
|
||||
}) => {
|
||||
if (errors.length) {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: errors[0],
|
||||
});
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ export default {
|
|||
},
|
||||
)
|
||||
.catch(() => {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: sprintf(
|
||||
__('Something went wrong while setting %{issuableType} notifications.'),
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { GlLoadingIcon, GlTableLite, GlButton, GlTooltipDirective } from '@gitlab/ui';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/graphql_shared/constants';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { formatDate, parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility';
|
||||
|
@ -47,7 +47,7 @@ export default {
|
|||
return this.extractTimelogs(data);
|
||||
},
|
||||
error() {
|
||||
createFlash({ message: __('Something went wrong. Please try again.') });
|
||||
createAlert({ message: __('Something went wrong. Please try again.') });
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -105,7 +105,7 @@ export default {
|
|||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: s__('TimeTracking|An error occurred while removing the timelog.'),
|
||||
captureError: true,
|
||||
error,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { produce } from 'immer';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import { todoQueries, TodoMutationTypes, todoMutations } from '~/sidebar/constants';
|
||||
import { todoLabel } from '~/vue_shared/components/sidebar/todo_toggle//utils';
|
||||
|
@ -73,7 +73,7 @@ export default {
|
|||
this.$emit('todoUpdated', currentUserTodos.length > 0);
|
||||
},
|
||||
error() {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: sprintf(__('Something went wrong while setting %{issuableType} to-do item.'), {
|
||||
issuableType: this.issuableType,
|
||||
}),
|
||||
|
@ -155,7 +155,7 @@ export default {
|
|||
},
|
||||
}) => {
|
||||
if (errors.length) {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: errors[0],
|
||||
});
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ export default {
|
|||
},
|
||||
)
|
||||
.catch(() => {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: sprintf(__('Something went wrong while setting %{issuableType} to-do item.'), {
|
||||
issuableType: this.issuableType,
|
||||
}),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import $ from 'jquery';
|
||||
import { escape } from 'lodash';
|
||||
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
function isValidProjectId(id) {
|
||||
|
@ -44,7 +44,7 @@ class SidebarMoveIssue {
|
|||
.fetchAutocompleteProjects(searchTerm)
|
||||
.then(callback)
|
||||
.catch(() =>
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: __('An error occurred while fetching projects autocomplete.'),
|
||||
}),
|
||||
);
|
||||
|
@ -79,7 +79,7 @@ class SidebarMoveIssue {
|
|||
this.$confirmButton.disable().addClass('is-loading');
|
||||
|
||||
this.mediator.moveIssue().catch(() => {
|
||||
createFlash({ message: __('An error occurred while moving the issue.') });
|
||||
createAlert({ message: __('An error occurred while moving the issue.') });
|
||||
this.$confirmButton.enable().removeClass('is-loading');
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Store from '~/sidebar/stores/sidebar_store';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
import toast from '~/vue_shared/plugins/global_toast';
|
||||
import { visitUrl } from '../lib/utils/url_utility';
|
||||
|
@ -97,7 +97,7 @@ export default class SidebarMediator {
|
|||
this.processFetchedData(restResponse.data, graphQlResponse.data);
|
||||
})
|
||||
.catch(() =>
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: __('Error occurred when fetching sidebar data'),
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
/* eslint-disable consistent-return */
|
||||
|
||||
import $ from 'jquery';
|
||||
import { createAlert } from '~/flash';
|
||||
import { loadingIconForLegacyJS } from '~/loading_icon_for_legacy_js';
|
||||
import { spriteIcon } from '~/lib/utils/common_utils';
|
||||
import FilesCommentButton from './files_comment_button';
|
||||
import createFlash from './flash';
|
||||
import initImageDiffHelper from './image_diff/helpers/init_image_diff';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import { __ } from './locale';
|
||||
|
@ -96,7 +96,7 @@ export default class SingleFileDiff {
|
|||
if (cb) cb();
|
||||
})
|
||||
.catch(() => {
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: __('An error occurred while retrieving diff'),
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { GlButton, GlLoadingIcon, GlFormInput, GlFormGroup } from '@gitlab/ui';
|
||||
|
||||
import eventHub from '~/blob/components/eventhub';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { redirectTo, joinPaths } from '~/lib/utils/url_utility';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import {
|
||||
|
@ -145,7 +145,7 @@ export default {
|
|||
const defaultErrorMsg = this.newSnippet
|
||||
? SNIPPET_CREATE_MUTATION_ERROR
|
||||
: SNIPPET_UPDATE_MUTATION_ERROR;
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: sprintf(defaultErrorMsg, { err }),
|
||||
});
|
||||
this.isUpdating = false;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import BlobHeaderEdit from '~/blob/components/blob_edit_header.vue';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { getBaseURL, joinPaths } from '~/lib/utils/url_utility';
|
||||
import { sprintf } from '~/locale';
|
||||
|
@ -63,7 +63,7 @@ export default {
|
|||
.catch((e) => this.flashAPIFailure(e));
|
||||
},
|
||||
flashAPIFailure(err) {
|
||||
createFlash({ message: sprintf(SNIPPET_BLOB_CONTENT_FETCH_ERROR, { err }) });
|
||||
createAlert({ message: sprintf(SNIPPET_BLOB_CONTENT_FETCH_ERROR, { err }) });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -19,7 +19,7 @@ import axios from '~/lib/utils/axios_utils';
|
|||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import createFlash, { FLASH_TYPES } from '~/flash';
|
||||
import { createAlert, VARIANT_DANGER, VARIANT_SUCCESS } from '~/flash';
|
||||
|
||||
import DeleteSnippetMutation from '../mutations/delete_snippet.mutation.graphql';
|
||||
|
||||
|
@ -196,12 +196,12 @@ export default {
|
|||
try {
|
||||
this.isSubmittingSpam = true;
|
||||
await axios.post(this.reportAbusePath);
|
||||
createFlash({
|
||||
createAlert({
|
||||
message: this.$options.i18n.snippetSpamSuccess,
|
||||
type: FLASH_TYPES.SUCCESS,
|
||||
variant: VARIANT_SUCCESS,
|
||||
});
|
||||
} catch (error) {
|
||||
createFlash({ message: this.$options.i18n.snippetSpamFailure });
|
||||
createAlert({ message: this.$options.i18n.snippetSpamFailure, variant: VARIANT_DANGER });
|
||||
} finally {
|
||||
this.isSubmittingSpam = false;
|
||||
}
|
||||
|
|
|
@ -26,16 +26,6 @@ export default {
|
|||
required: false,
|
||||
default: true,
|
||||
},
|
||||
divergedCommitsCount: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
targetBranchPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
closesText() {
|
||||
|
|
|
@ -47,7 +47,8 @@ class Projects::CompareController < Projects::ApplicationController
|
|||
from_to_vars = {
|
||||
from: compare_params[:from].presence,
|
||||
to: compare_params[:to].presence,
|
||||
from_project_id: compare_params[:from_project_id].presence
|
||||
from_project_id: compare_params[:from_project_id].presence,
|
||||
straight: compare_params[:straight].presence
|
||||
}
|
||||
|
||||
if from_to_vars[:from].blank? || from_to_vars[:to].blank?
|
||||
|
@ -112,7 +113,11 @@ class Projects::CompareController < Projects::ApplicationController
|
|||
def compare
|
||||
return @compare if defined?(@compare)
|
||||
|
||||
@compare = CompareService.new(source_project, head_ref).execute(target_project, start_ref)
|
||||
@compare = CompareService.new(source_project, head_ref).execute(target_project, start_ref, straight: straight)
|
||||
end
|
||||
|
||||
def straight
|
||||
compare_params[:straight] == "true"
|
||||
end
|
||||
|
||||
def start_ref
|
||||
|
@ -160,6 +165,6 @@ class Projects::CompareController < Projects::ApplicationController
|
|||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def compare_params
|
||||
@compare_params ||= params.permit(:from, :to, :from_project_id)
|
||||
@compare_params ||= params.permit(:from, :to, :from_project_id, :straight)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ module Types
|
|||
module BranchProtections
|
||||
class MergeAccessLevelType < BaseAccessLevelType # rubocop:disable Graphql/AuthorizeTypes
|
||||
graphql_name 'MergeAccessLevel'
|
||||
description 'Represents the merge access level of a branch protection.'
|
||||
description 'Defines which user roles, users, or groups can merge into a protected branch.'
|
||||
accepts ::ProtectedBranch::MergeAccessLevel
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ module Types
|
|||
module BranchProtections
|
||||
class PushAccessLevelType < BaseAccessLevelType # rubocop:disable Graphql/AuthorizeTypes
|
||||
graphql_name 'PushAccessLevel'
|
||||
description 'Represents the push access level of a branch protection.'
|
||||
description 'Defines which user roles, users, or groups can push to a protected branch.'
|
||||
accepts ::ProtectedBranch::PushAccessLevel
|
||||
end
|
||||
end
|
||||
|
|
|
@ -42,7 +42,8 @@ module CompareHelper
|
|||
source_project_refs_path: refs_project_path(project),
|
||||
target_project_refs_path: refs_project_path(@target_project),
|
||||
params_from: params[:from],
|
||||
params_to: params[:to]
|
||||
params_to: params[:to],
|
||||
straight: params[:straight]
|
||||
}.tap do |data|
|
||||
data[:projects_from] = target_projects(project).map do |target_project|
|
||||
{ id: target_project.id, name: target_project.full_path }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
- page_description brand_title unless page_description
|
||||
- site_name = "GitLab"
|
||||
- site_name = _('GitLab')
|
||||
%head{ prefix: "og: http://ogp.me/ns#" }
|
||||
%meta{ charset: "utf-8" }
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
.js-signature-container{ data: { 'signatures-path' => signatures_namespace_project_compare_index_path } }
|
||||
#js-compare-selector{ data: project_compare_selector_data(@project, @merge_request, params) }
|
||||
|
||||
- if @commits.present?
|
||||
- if @commits.present? || @diffs.present?
|
||||
-# Only show commit list in the first page
|
||||
- hide_commit_list = params[:page].present? && params[:page] != '1'
|
||||
= render "projects/commits/commit_list" unless hide_commit_list
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: usage_data_i_ci_secrets_management_vault_build_created
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46515
|
||||
rollout_issue_url:
|
||||
milestone: '13.6'
|
||||
type: development
|
||||
group: group::configure
|
||||
default_enabled: true
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: jira_raise_timeouts
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86439
|
||||
rollout_issue_url:
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/375587
|
||||
milestone: '15.0'
|
||||
type: ops
|
||||
group: group::integrations
|
||||
|
|
|
@ -440,6 +440,7 @@ onboarding
|
|||
OpenID
|
||||
OpenShift
|
||||
Opsgenie
|
||||
outdent
|
||||
Overcommit
|
||||
Packagist
|
||||
parallelization
|
||||
|
|
|
@ -9322,6 +9322,29 @@ The edge type for [`TreeEntry`](#treeentry).
|
|||
| <a id="treeentryedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="treeentryedgenode"></a>`node` | [`TreeEntry`](#treeentry) | The item at the end of the edge. |
|
||||
|
||||
#### `UnprotectAccessLevelConnection`
|
||||
|
||||
The connection type for [`UnprotectAccessLevel`](#unprotectaccesslevel).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="unprotectaccesslevelconnectionedges"></a>`edges` | [`[UnprotectAccessLevelEdge]`](#unprotectaccessleveledge) | A list of edges. |
|
||||
| <a id="unprotectaccesslevelconnectionnodes"></a>`nodes` | [`[UnprotectAccessLevel]`](#unprotectaccesslevel) | A list of nodes. |
|
||||
| <a id="unprotectaccesslevelconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `UnprotectAccessLevelEdge`
|
||||
|
||||
The edge type for [`UnprotectAccessLevel`](#unprotectaccesslevel).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="unprotectaccessleveledgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="unprotectaccessleveledgenode"></a>`node` | [`UnprotectAccessLevel`](#unprotectaccesslevel) | The item at the end of the edge. |
|
||||
|
||||
#### `UploadRegistryConnection`
|
||||
|
||||
The connection type for [`UploadRegistry`](#uploadregistry).
|
||||
|
@ -10204,6 +10227,7 @@ Branch protection details for a branch rule.
|
|||
| <a id="branchprotectioncodeownerapprovalrequired"></a>`codeOwnerApprovalRequired` | [`Boolean!`](#boolean) | Enforce code owner approvals before allowing a merge. |
|
||||
| <a id="branchprotectionmergeaccesslevels"></a>`mergeAccessLevels` | [`MergeAccessLevelConnection`](#mergeaccesslevelconnection) | Details about who can merge when this branch is the source branch. (see [Connections](#connections)) |
|
||||
| <a id="branchprotectionpushaccesslevels"></a>`pushAccessLevels` | [`PushAccessLevelConnection`](#pushaccesslevelconnection) | Details about who can push when this branch is the source branch. (see [Connections](#connections)) |
|
||||
| <a id="branchprotectionunprotectaccesslevels"></a>`unprotectAccessLevels` | [`UnprotectAccessLevelConnection`](#unprotectaccesslevelconnection) | Details about who can unprotect this branch. (see [Connections](#connections)) |
|
||||
|
||||
### `BranchRule`
|
||||
|
||||
|
@ -13937,7 +13961,7 @@ Maven metadata.
|
|||
|
||||
### `MergeAccessLevel`
|
||||
|
||||
Represents the merge access level of a branch protection.
|
||||
Defines which user roles, users, or groups can merge into a protected branch.
|
||||
|
||||
#### Fields
|
||||
|
||||
|
@ -17345,7 +17369,7 @@ Which group, user or role is allowed to execute deployments to the environment.
|
|||
|
||||
### `PushAccessLevel`
|
||||
|
||||
Represents the push access level of a branch protection.
|
||||
Defines which user roles, users, or groups can push to a protected branch.
|
||||
|
||||
#### Fields
|
||||
|
||||
|
@ -18574,6 +18598,19 @@ Represents a directory.
|
|||
| <a id="treeentrywebpath"></a>`webPath` | [`String`](#string) | Web path for the tree entry (directory). |
|
||||
| <a id="treeentryweburl"></a>`webUrl` | [`String`](#string) | Web URL for the tree entry (directory). |
|
||||
|
||||
### `UnprotectAccessLevel`
|
||||
|
||||
Defines which user roles, users, or groups can unprotect a protected branch.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="unprotectaccesslevelaccesslevel"></a>`accessLevel` | [`Int!`](#int) | GitLab::Access level. |
|
||||
| <a id="unprotectaccesslevelaccessleveldescription"></a>`accessLevelDescription` | [`String!`](#string) | Human readable representation for this access level. |
|
||||
| <a id="unprotectaccesslevelgroup"></a>`group` | [`Group`](#group) | Group associated with this access level. |
|
||||
| <a id="unprotectaccessleveluser"></a>`user` | [`UserCore`](#usercore) | User associated with this access level. |
|
||||
|
||||
### `UploadRegistry`
|
||||
|
||||
Represents the Geo replication and verification state of an upload.
|
||||
|
|
|
@ -321,26 +321,14 @@ class PreparePrimaryKeyForPartitioning < Gitlab::Database::Migration[2.0]
|
|||
NEW_INDEX_NAME = :new_index_name
|
||||
|
||||
def up
|
||||
with_lock_retries(raise_on_exhaustion: true) do
|
||||
execute("ALTER TABLE #{TABLE_NAME} DROP CONSTRAINT #{PRIMARY_KEY} CASCADE")
|
||||
|
||||
rename_index(TABLE_NAME, NEW_INDEX_NAME, PRIMARY_KEY)
|
||||
|
||||
execute("ALTER TABLE #{TABLE_NAME} ADD CONSTRAINT #{PRIMARY_KEY} PRIMARY KEY USING INDEX #{PRIMARY_KEY}")
|
||||
end
|
||||
swap_primary_key(TABLE_NAME, PRIMARY_KEY, NEW_INDEX_NAME)
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_index(TABLE_NAME, :id, unique: true, name: OLD_INDEX_NAME)
|
||||
add_concurrent_index(TABLE_NAME, [:id, :partition_id], unique: true, name: NEW_INDEX_NAME)
|
||||
|
||||
with_lock_retries(raise_on_exhaustion: true) do
|
||||
execute("ALTER TABLE #{TABLE_NAME} DROP CONSTRAINT #{PRIMARY_KEY} CASCADE")
|
||||
|
||||
rename_index(TABLE_NAME, OLD_INDEX_NAME, PRIMARY_KEY)
|
||||
|
||||
execute("ALTER TABLE #{TABLE_NAME} ADD CONSTRAINT #{PRIMARY_KEY} PRIMARY KEY USING INDEX #{PRIMARY_KEY}")
|
||||
end
|
||||
unswap_primary_key(TABLE_NAME, PRIMARY_KEY, OLD_INDEX_NAME)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
|
|
@ -982,6 +982,43 @@ NOTE:
|
|||
`add_sequence` should be avoided for columns with foreign keys.
|
||||
Adding sequence to these columns is **only allowed** in the down method (restore previous schema state).
|
||||
|
||||
## Swapping primary key
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/98645) in GitLab 15.5.
|
||||
|
||||
Swapping the primary key is required to partition a table as the **partition key must be included in the primary key**.
|
||||
|
||||
You can use the `swap_primary_key` method provided by the database team.
|
||||
|
||||
Under the hood, it works like this:
|
||||
|
||||
- Drop the primary key constraint.
|
||||
- Add the primary key using the index defined beforehand.
|
||||
|
||||
```ruby
|
||||
class SwapPrimaryKey < Gitlab::Database::Migration[2.0]
|
||||
TABLE_NAME = :table_name
|
||||
PRIMARY_KEY = :table_name_pkey
|
||||
OLD_INDEX_NAME = :old_index_name
|
||||
NEW_INDEX_NAME = :new_index_name
|
||||
|
||||
def up
|
||||
swap_primary_key(TABLE_NAME, PRIMARY_KEY, NEW_INDEX_NAME)
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_index(TABLE_NAME, :id, unique: true, name: OLD_INDEX_NAME)
|
||||
add_concurrent_index(TABLE_NAME, [:id, :partition_id], unique: true, name: NEW_INDEX_NAME)
|
||||
|
||||
unswap_primary_key(TABLE_NAME, PRIMARY_KEY, OLD_INDEX_NAME)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
NOTE:
|
||||
Make sure to introduce the new index beforehand in a separate migration in order
|
||||
to swap the primary key.
|
||||
|
||||
## Integer column type
|
||||
|
||||
By default, an integer column can hold up to a 4-byte (32-bit) number. That is
|
||||
|
|
|
@ -55,6 +55,8 @@ descriptions):
|
|||
| <kbd>Command</kbd> + <kbd>i</kbd> | <kbd>Control</kbd> + <kbd>i</kbd> | Italicize the selected text (surround it with `_`). |
|
||||
| <kbd>Command</kbd> + <kbd>Shift</kbd> + <kbd>x</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>x</kbd> | Strike through the selected text (surround it with `~~`). |
|
||||
| <kbd>Command</kbd> + <kbd>k</kbd> | <kbd>Control</kbd> + <kbd>k</kbd> | Add a link (surround the selected text with `[]()`). |
|
||||
| <kbd>Command</kbd> + <kbd>]</kbd> | <kbd>Control</kbd> + <kbd>]</kbd> | Indent list item. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/351924) in GitLab 15.3. |
|
||||
| <kbd>Command</kbd> + <kbd>[</kbd> | <kbd>Control</kbd> + <kbd>[</kbd> | Outdent list item. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/351924) in GitLab 15.3. |
|
||||
|
||||
The shortcuts for editing in text fields are always enabled, even if other
|
||||
keyboard shortcuts are disabled.
|
||||
|
|
|
@ -8,6 +8,11 @@ pre-push:
|
|||
files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD
|
||||
glob: '*.{js,vue}'
|
||||
run: yarn run lint:eslint {files}
|
||||
jsonlint:
|
||||
tags: style
|
||||
files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD
|
||||
glob: '*.{json}'
|
||||
run: scripts/lint-json.sh {files}
|
||||
haml-lint:
|
||||
tags: view haml style
|
||||
files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD
|
||||
|
|
|
@ -1503,6 +1503,26 @@ into similar problems in the future (e.g. when new tables are created).
|
|||
SQL
|
||||
end
|
||||
|
||||
def drop_constraint(table_name, constraint_name, cascade: false)
|
||||
execute <<~SQL
|
||||
ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint_name)} #{cascade_statement(cascade)}
|
||||
SQL
|
||||
end
|
||||
|
||||
def add_primary_key_using_index(table_name, pk_name, index_to_use)
|
||||
execute <<~SQL
|
||||
ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{quote_table_name(pk_name)} PRIMARY KEY USING INDEX #{quote_table_name(index_to_use)}
|
||||
SQL
|
||||
end
|
||||
|
||||
def swap_primary_key(table_name, primary_key_name, index_to_use)
|
||||
with_lock_retries(raise_on_exhaustion: true) do
|
||||
drop_constraint(table_name, primary_key_name, cascade: true)
|
||||
add_primary_key_using_index(table_name, primary_key_name, index_to_use)
|
||||
end
|
||||
end
|
||||
alias_method :unswap_primary_key, :swap_primary_key
|
||||
|
||||
def drop_sequence(table_name, column_name, sequence_name)
|
||||
execute <<~SQL
|
||||
ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} DROP DEFAULT;
|
||||
|
@ -1519,6 +1539,10 @@ into similar problems in the future (e.g. when new tables are created).
|
|||
|
||||
private
|
||||
|
||||
def cascade_statement(cascade)
|
||||
cascade ? 'CASCADE' : ''
|
||||
end
|
||||
|
||||
def create_temporary_columns_and_triggers(table, columns, primary_key: :id, data_type: :bigint)
|
||||
unless table_exists?(table)
|
||||
raise "Table #{table} does not exist"
|
||||
|
|
|
@ -261,11 +261,6 @@
|
|||
redis_slot: project_management
|
||||
aggregation: daily
|
||||
# Secrets Management
|
||||
- name: i_ci_secrets_management_vault_build_created
|
||||
category: ci_secrets_management
|
||||
redis_slot: ci_secrets_management
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_i_ci_secrets_management_vault_build_created
|
||||
- name: i_snippets_show
|
||||
category: snippets
|
||||
redis_slot: snippets
|
||||
|
|
|
@ -39,7 +39,7 @@ namespace :tw do
|
|||
CodeOwnerRule.new('Documentation Guidelines', '@sselhorn'),
|
||||
CodeOwnerRule.new('Dynamic Analysis', '@rdickenson'),
|
||||
CodeOwnerRule.new('Ecosystem', '@kpaizee'),
|
||||
CodeOwnerRule.new('Editor', '@aqualls'),
|
||||
CodeOwnerRule.new('Editor', '@ashrafkhamis'),
|
||||
CodeOwnerRule.new('Foundations', '@rdickenson'),
|
||||
CodeOwnerRule.new('Fuzz Testing', '@rdickenson'),
|
||||
CodeOwnerRule.new('Geo', '@axil'),
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
"@tiptap/extension-task-item": "^2.0.0-beta.37",
|
||||
"@tiptap/extension-task-list": "^2.0.0-beta.29",
|
||||
"@tiptap/extension-text": "^2.0.0-beta.17",
|
||||
"@tiptap/suggestion": "^2.0.0-beta.96",
|
||||
"@tiptap/vue-2": "^2.0.0-beta.84",
|
||||
"apollo-upload-client": "15.0.0",
|
||||
"autosize": "^5.0.1",
|
||||
|
@ -171,6 +172,7 @@
|
|||
"swagger-ui-dist": "4.12.0",
|
||||
"three": "^0.143.0",
|
||||
"timeago.js": "^4.0.2",
|
||||
"tippy.js": "^6.3.7",
|
||||
"unified": "^10.1.2",
|
||||
"unist-builder": "^3.0.0",
|
||||
"unist-util-visit-parents": "^5.1.0",
|
||||
|
@ -233,6 +235,7 @@
|
|||
"jest-raw-loader": "^1.0.1",
|
||||
"jest-transform-graphql": "^2.1.0",
|
||||
"jest-util": "^27.5.1",
|
||||
"jsonlint": "^1.6.3",
|
||||
"markdownlint-cli": "0.32.2",
|
||||
"miragejs": "^0.1.40",
|
||||
"mock-apollo-client": "1.2.0",
|
||||
|
@ -262,4 +265,4 @@
|
|||
"node": ">=12.22.1",
|
||||
"yarn": "^1.10.0"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,12 @@ module QA
|
|||
|
||||
QA_PATTERN = %r{^qa/}.freeze
|
||||
SPEC_PATTERN = %r{^qa/qa/specs/features/}.freeze
|
||||
DEPENDENCY_PATTERN = Regexp.union(
|
||||
/_VERSION/,
|
||||
/Gemfile\.lock/,
|
||||
/yarn\.lock/,
|
||||
/Dockerfile\.assets/
|
||||
)
|
||||
|
||||
def initialize(mr_diff, mr_labels)
|
||||
@mr_diff = mr_diff
|
||||
|
@ -21,7 +27,8 @@ module QA
|
|||
#
|
||||
# @return [String]
|
||||
def qa_tests
|
||||
return if mr_diff.empty?
|
||||
return if mr_diff.empty? || dependency_changes
|
||||
|
||||
# make paths relative to qa directory
|
||||
return changed_files&.map { |path| path.delete_prefix("qa/") }&.join(" ") if only_spec_changes?
|
||||
return qa_spec_directories_for_devops_stage&.join(" ") if non_qa_changes? && mr_labels.any?
|
||||
|
@ -104,6 +111,13 @@ module QA
|
|||
Dir.glob("qa/specs/**/*/").select { |dir| dir =~ %r{\d+_#{devops_stage}/$} }
|
||||
end
|
||||
|
||||
# Changes to gitlab dependencies
|
||||
#
|
||||
# @return [Boolean]
|
||||
def dependency_changes
|
||||
changed_files.any? { |file| file.match?(DEPENDENCY_PATTERN) }
|
||||
end
|
||||
|
||||
# Change files in merge request
|
||||
#
|
||||
# @return [Array<String>]
|
||||
|
|
|
@ -35,7 +35,7 @@ RSpec.describe QA::Tools::Ci::QaChanges do
|
|||
context "with framework changes" do
|
||||
let(:mr_diff) { [{ path: "qa/qa.rb" }] }
|
||||
|
||||
it ".qa_tests do not return specifix specs" do
|
||||
it ".qa_tests do not return specific specs" do
|
||||
expect(qa_changes.qa_tests).to be_nil
|
||||
end
|
||||
|
||||
|
@ -84,4 +84,14 @@ RSpec.describe QA::Tools::Ci::QaChanges do
|
|||
expect(qa_changes.quarantine_changes?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
%w[GITALY_SERVER_VERSION Gemfile.lock yarn.lock Dockerfile.assets].each do |dependency_file|
|
||||
context "when #{dependency_file} change" do
|
||||
let(:mr_diff) { [{ path: dependency_file }] }
|
||||
|
||||
it ".qa_tests do not return specific specs" do
|
||||
expect(qa_changes.qa_tests).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
for file in "$@"
|
||||
do
|
||||
yarn run -s jsonlint -p "$file" | perl -pe 'chomp if eof' | diff "$file" -
|
||||
done
|
|
@ -67,11 +67,13 @@ RSpec.describe Projects::CompareController do
|
|||
from: from_ref,
|
||||
to: to_ref,
|
||||
w: whitespace,
|
||||
page: page
|
||||
page: page,
|
||||
straight: straight
|
||||
}
|
||||
end
|
||||
|
||||
let(:whitespace) { nil }
|
||||
let(:straight) { nil }
|
||||
let(:page) { nil }
|
||||
|
||||
context 'when the refs exist in the same project' do
|
||||
|
@ -142,6 +144,58 @@ RSpec.describe Projects::CompareController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when comparing missing commits between source and target' do
|
||||
let(:from_project_id) { nil }
|
||||
let(:from_ref) { '5937ac0a7beb003549fc5fd26fc247adbce4a52e' }
|
||||
let(:to_ref) { '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9' }
|
||||
let(:page) { 1 }
|
||||
|
||||
context 'when comparing them in the other direction' do
|
||||
let(:straight) { "false" }
|
||||
let(:from_ref) { '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9' }
|
||||
let(:to_ref) { '5937ac0a7beb003549fc5fd26fc247adbce4a52e' }
|
||||
|
||||
it 'the commits are there' do
|
||||
show_request
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(assigns(:commits).length).to be >= 2
|
||||
expect(assigns(:diffs).raw_diff_files.size).to be >= 2
|
||||
expect(assigns(:diffs).diff_files.first).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'with straight mode true' do
|
||||
let(:from_ref) { '5937ac0a7beb003549fc5fd26fc247adbce4a52e' }
|
||||
let(:to_ref) { '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9' }
|
||||
|
||||
let(:straight) { "true" }
|
||||
|
||||
it 'the commits are empty, but the removed lines are visible as diffs' do
|
||||
show_request
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(assigns(:commits).length).to be == 0
|
||||
expect(assigns(:diffs).diff_files.size).to be >= 4
|
||||
end
|
||||
end
|
||||
|
||||
context 'with straight mode false' do
|
||||
let(:from_ref) { '5937ac0a7beb003549fc5fd26fc247adbce4a52e' }
|
||||
let(:to_ref) { '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9' }
|
||||
|
||||
let(:straight) { "false" }
|
||||
|
||||
it 'the additional commits are not visible in diffs and commits' do
|
||||
show_request
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(assigns(:commits).length).to be == 0
|
||||
expect(assigns(:diffs).diff_files.size).to be == 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the refs exist in different projects but the user cannot see' do
|
||||
let(:from_project_id) { private_fork.id }
|
||||
let(:from_ref) { 'improve%2Fmore-awesome' }
|
||||
|
@ -450,10 +504,13 @@ RSpec.describe Projects::CompareController do
|
|||
project_id: project,
|
||||
from: from_ref,
|
||||
to: to_ref,
|
||||
straight: straight,
|
||||
format: :json
|
||||
}
|
||||
end
|
||||
|
||||
let(:straight) { nil }
|
||||
|
||||
context 'when the source and target refs exist' do
|
||||
let(:from_ref) { 'improve%2Fawesome' }
|
||||
let(:to_ref) { 'feature' }
|
||||
|
@ -464,6 +521,39 @@ RSpec.describe Projects::CompareController do
|
|||
let(:signature_commit) { project.commit_by(oid: '0b4bc9a49b562e85de7cc9e834518ea6828729b9') }
|
||||
let(:non_signature_commit) { build(:commit, project: project, safe_message: "message", sha: 'non_signature_commit') }
|
||||
|
||||
before do
|
||||
escaped_from_ref = Addressable::URI.unescape(from_ref)
|
||||
escaped_to_ref = Addressable::URI.unescape(to_ref)
|
||||
|
||||
compare_service = CompareService.new(project, escaped_to_ref)
|
||||
compare = compare_service.execute(project, escaped_from_ref, straight: false)
|
||||
|
||||
expect(CompareService).to receive(:new).with(project, escaped_to_ref).and_return(compare_service)
|
||||
expect(compare_service).to receive(:execute).with(project, escaped_from_ref, straight: false).and_return(compare)
|
||||
|
||||
expect(compare).to receive(:commits).and_return(CommitCollection.new(project, [signature_commit, non_signature_commit]))
|
||||
expect(non_signature_commit).to receive(:has_signature?).and_return(false)
|
||||
end
|
||||
|
||||
it 'returns only the commit with a signature' do
|
||||
signatures_request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
signatures = json_response['signatures']
|
||||
|
||||
expect(signatures.size).to eq(1)
|
||||
expect(signatures.first['commit_sha']).to eq(signature_commit.sha)
|
||||
expect(signatures.first['html']).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user has access to the project with straight compare' do
|
||||
render_views
|
||||
|
||||
let(:signature_commit) { project.commit_by(oid: '0b4bc9a49b562e85de7cc9e834518ea6828729b9') }
|
||||
let(:non_signature_commit) { build(:commit, project: project, safe_message: "message", sha: 'non_signature_commit') }
|
||||
let(:straight) { "true" }
|
||||
|
||||
before do
|
||||
escaped_from_ref = Addressable::URI.unescape(from_ref)
|
||||
escaped_to_ref = Addressable::URI.unescape(to_ref)
|
||||
|
@ -472,7 +562,7 @@ RSpec.describe Projects::CompareController do
|
|||
compare = compare_service.execute(project, escaped_from_ref)
|
||||
|
||||
expect(CompareService).to receive(:new).with(project, escaped_to_ref).and_return(compare_service)
|
||||
expect(compare_service).to receive(:execute).with(project, escaped_from_ref).and_return(compare)
|
||||
expect(compare_service).to receive(:execute).with(project, escaped_from_ref, straight: true).and_return(compare)
|
||||
|
||||
expect(compare).to receive(:commits).and_return(CommitCollection.new(project, [signature_commit, non_signature_commit]))
|
||||
expect(non_signature_commit).to receive(:has_signature?).and_return(false)
|
||||
|
|
|
@ -39,33 +39,13 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
|
||||
trait :maintainers_can_push do
|
||||
trait :no_one_can_merge do
|
||||
transient do
|
||||
default_push_level { false }
|
||||
default_merge_level { false }
|
||||
end
|
||||
|
||||
after(:build) do |protected_branch|
|
||||
protected_branch.push_access_levels.new(access_level: Gitlab::Access::MAINTAINER)
|
||||
end
|
||||
end
|
||||
|
||||
trait :maintainers_can_merge do
|
||||
transient do
|
||||
default_push_level { false }
|
||||
end
|
||||
|
||||
after(:build) do |protected_branch|
|
||||
protected_branch.push_access_levels.new(access_level: Gitlab::Access::MAINTAINER)
|
||||
end
|
||||
end
|
||||
|
||||
trait :developers_can_push do
|
||||
transient do
|
||||
default_push_level { false }
|
||||
end
|
||||
|
||||
after(:build) do |protected_branch|
|
||||
protected_branch.push_access_levels.new(access_level: Gitlab::Access::DEVELOPER)
|
||||
protected_branch.merge_access_levels.new(access_level: Gitlab::Access::NO_ACCESS)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -79,6 +59,16 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
|
||||
trait :maintainers_can_merge do
|
||||
transient do
|
||||
default_merge_level { false }
|
||||
end
|
||||
|
||||
after(:build) do |protected_branch|
|
||||
protected_branch.merge_access_levels.new(access_level: Gitlab::Access::MAINTAINER)
|
||||
end
|
||||
end
|
||||
|
||||
trait :no_one_can_push do
|
||||
transient do
|
||||
default_push_level { false }
|
||||
|
@ -89,13 +79,23 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
|
||||
trait :no_one_can_merge do
|
||||
trait :developers_can_push do
|
||||
transient do
|
||||
default_merge_level { false }
|
||||
default_push_level { false }
|
||||
end
|
||||
|
||||
after(:build) do |protected_branch|
|
||||
protected_branch.merge_access_levels.new(access_level: Gitlab::Access::NO_ACCESS)
|
||||
protected_branch.push_access_levels.new(access_level: Gitlab::Access::DEVELOPER)
|
||||
end
|
||||
end
|
||||
|
||||
trait :maintainers_can_push do
|
||||
transient do
|
||||
default_push_level { false }
|
||||
end
|
||||
|
||||
after(:build) do |protected_branch|
|
||||
protected_branch.push_access_levels.new(access_level: Gitlab::Access::MAINTAINER)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import Heading from '~/content_editor/extensions/heading';
|
||||
import { createTestEditor, createDocBuilder, triggerNodeInputRule } from '../test_utils';
|
||||
|
||||
describe('content_editor/extensions/heading', () => {
|
||||
let tiptapEditor;
|
||||
let doc;
|
||||
let p;
|
||||
let heading;
|
||||
|
||||
beforeEach(() => {
|
||||
tiptapEditor = createTestEditor({ extensions: [Heading] });
|
||||
({
|
||||
builders: { doc, p, heading },
|
||||
} = createDocBuilder({
|
||||
tiptapEditor,
|
||||
names: {
|
||||
heading: { nodeType: Heading.name },
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
describe('when typing a valid heading input rule', () => {
|
||||
it.each`
|
||||
level | inputRuleText
|
||||
${1} | ${'# '}
|
||||
${2} | ${'## '}
|
||||
${3} | ${'### '}
|
||||
${4} | ${'#### '}
|
||||
${5} | ${'##### '}
|
||||
${6} | ${'###### '}
|
||||
`('inserts a heading node for $inputRuleText', ({ level, inputRuleText }) => {
|
||||
const expectedDoc = doc(heading({ level }));
|
||||
|
||||
triggerNodeInputRule({ tiptapEditor, inputRuleText });
|
||||
|
||||
expect(tiptapEditor.getJSON()).toEqual(expectedDoc.toJSON());
|
||||
});
|
||||
});
|
||||
|
||||
describe('when typing a invalid heading input rule', () => {
|
||||
it.each`
|
||||
inputRuleText
|
||||
${'#hi'}
|
||||
${'#\n'}
|
||||
`('does not insert a heading node for $inputRuleText', ({ inputRuleText }) => {
|
||||
const expectedDoc = doc(p());
|
||||
|
||||
triggerNodeInputRule({ tiptapEditor, inputRuleText });
|
||||
|
||||
// no change to the document
|
||||
expect(tiptapEditor.getJSON()).toEqual(expectedDoc.toJSON());
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,7 +1,10 @@
|
|||
import fs from 'fs';
|
||||
import jsYaml from 'js-yaml';
|
||||
import { memoize } from 'lodash';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import axios from 'axios';
|
||||
import { createContentEditor } from '~/content_editor';
|
||||
import httpStatus from '~/lib/utils/http_status';
|
||||
|
||||
const getFocusedMarkdownExamples = memoize(
|
||||
() => process.env.FOCUSED_MARKDOWN_EXAMPLES?.split(',') || [],
|
||||
|
@ -42,6 +45,11 @@ const loadMarkdownApiExamples = (markdownYamlPath) => {
|
|||
};
|
||||
|
||||
const testSerializesHtmlToMarkdownForElement = async ({ markdown, html }) => {
|
||||
const mock = new MockAdapter(axios);
|
||||
|
||||
// Ignore any API requests from the suggestions plugin
|
||||
mock.onGet().reply(httpStatus.OK, []);
|
||||
|
||||
const contentEditor = createContentEditor({
|
||||
// Overwrite renderMarkdown to always return this specific html
|
||||
renderMarkdown: () => html,
|
||||
|
@ -55,6 +63,8 @@ const testSerializesHtmlToMarkdownForElement = async ({ markdown, html }) => {
|
|||
// Assert that the markdown we ended up with after sending it through all the ContentEditor
|
||||
// plumbing matches the original markdown from the YAML.
|
||||
expect(serializedContent.trim()).toBe(markdown.trim());
|
||||
|
||||
mock.restore();
|
||||
};
|
||||
|
||||
// describeMarkdownProcesssing
|
||||
|
|
|
@ -44,7 +44,7 @@ describe('content_editor/services/track_input_rules_and_shortcuts', () => {
|
|||
|
||||
describe('when creating a heading using an keyboard shortcut', () => {
|
||||
it('sends a tracking event indicating that a heading was created using an input rule', async () => {
|
||||
const shortcuts = Heading.config.addKeyboardShortcuts.call(Heading);
|
||||
const shortcuts = Heading.parent.config.addKeyboardShortcuts.call(Heading);
|
||||
const [firstShortcut] = Object.keys(shortcuts);
|
||||
const nodeName = Heading.name;
|
||||
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import { GlAlert } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
import Autosave from '~/autosave';
|
||||
import { getDraft, updateDraft, clearDraft, getLockVersion } from '~/lib/utils/autosave';
|
||||
import DescriptionTemplate from '~/issues/show/components/fields/description_template.vue';
|
||||
import IssuableTitleField from '~/issues/show/components/fields/title.vue';
|
||||
import DescriptionField from '~/issues/show/components/fields/description.vue';
|
||||
import IssueTypeField from '~/issues/show/components/fields/type.vue';
|
||||
import formComponent from '~/issues/show/components/form.vue';
|
||||
import LockedWarning from '~/issues/show/components/locked_warning.vue';
|
||||
import eventHub from '~/issues/show/event_hub';
|
||||
|
||||
jest.mock('~/autosave');
|
||||
jest.mock('~/lib/utils/autosave');
|
||||
|
||||
describe('Inline edit form component', () => {
|
||||
let wrapper;
|
||||
|
@ -38,9 +40,14 @@ describe('Inline edit form component', () => {
|
|||
...defaultProps,
|
||||
...props,
|
||||
},
|
||||
stubs: {
|
||||
DescriptionField,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findTitleField = () => wrapper.findComponent(IssuableTitleField);
|
||||
const findDescriptionField = () => wrapper.findComponent(DescriptionField);
|
||||
const findDescriptionTemplate = () => wrapper.findComponent(DescriptionTemplate);
|
||||
const findIssuableTypeField = () => wrapper.findComponent(IssueTypeField);
|
||||
const findLockedWarning = () => wrapper.findComponent(LockedWarning);
|
||||
|
@ -108,16 +115,34 @@ describe('Inline edit form component', () => {
|
|||
});
|
||||
|
||||
describe('autosave', () => {
|
||||
let spy;
|
||||
|
||||
beforeEach(() => {
|
||||
spy = jest.spyOn(Autosave.prototype, 'reset');
|
||||
getDraft.mockImplementation((autosaveKey) => {
|
||||
return autosaveKey[autosaveKey.length - 1];
|
||||
});
|
||||
});
|
||||
|
||||
it('initialized Autosave on mount', () => {
|
||||
it('initializes title and description fields with saved drafts', () => {
|
||||
createComponent();
|
||||
|
||||
expect(Autosave).toHaveBeenCalledTimes(2);
|
||||
expect(findTitleField().props().value).toBe('title');
|
||||
expect(findDescriptionField().props().value).toBe('description');
|
||||
});
|
||||
|
||||
it('updates local storage drafts when title and description change', () => {
|
||||
const updatedTitle = 'updated title';
|
||||
const updatedDescription = 'updated description';
|
||||
|
||||
createComponent();
|
||||
|
||||
findTitleField().vm.$emit('input', updatedTitle);
|
||||
findDescriptionField().vm.$emit('input', updatedDescription);
|
||||
|
||||
expect(updateDraft).toHaveBeenCalledWith(expect.any(Array), updatedTitle);
|
||||
expect(updateDraft).toHaveBeenCalledWith(
|
||||
expect.any(Array),
|
||||
updatedDescription,
|
||||
defaultProps.formState.lock_version,
|
||||
);
|
||||
});
|
||||
|
||||
it('calls reset on autosave when eventHub emits appropriate events', () => {
|
||||
|
@ -125,33 +150,60 @@ describe('Inline edit form component', () => {
|
|||
|
||||
eventHub.$emit('close.form');
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
expect(clearDraft).toHaveBeenCalledTimes(2);
|
||||
|
||||
eventHub.$emit('delete.issuable');
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(4);
|
||||
expect(clearDraft).toHaveBeenCalledTimes(4);
|
||||
|
||||
eventHub.$emit('update.issuable');
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(6);
|
||||
expect(clearDraft).toHaveBeenCalledTimes(6);
|
||||
});
|
||||
|
||||
describe('outdated description', () => {
|
||||
const clientSideMockVersion = 'lock version from local storage';
|
||||
const serverSideMockVersion = 'lock version from server';
|
||||
|
||||
const mockGetLockVersion = () => getLockVersion.mockResolvedValue(clientSideMockVersion);
|
||||
|
||||
it('does not show warning if lock version from server is the same as the local lock version', () => {
|
||||
createComponent();
|
||||
expect(findAlert().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('shows warning if lock version from server differs than the local lock version', async () => {
|
||||
Autosave.prototype.getSavedLockVersion.mockResolvedValue('lock version from local storage');
|
||||
mockGetLockVersion();
|
||||
|
||||
createComponent({
|
||||
formState: { ...defaultProps.formState, lock_version: 'lock version from server' },
|
||||
formState: { ...defaultProps.formState, lock_version: serverSideMockVersion },
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
expect(findAlert().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('when saved draft is discarded', () => {
|
||||
beforeEach(async () => {
|
||||
mockGetLockVersion();
|
||||
|
||||
createComponent({
|
||||
formState: { ...defaultProps.formState, lock_version: serverSideMockVersion },
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
|
||||
findAlert().vm.$emit('secondaryAction');
|
||||
});
|
||||
|
||||
it('hides the warning alert', () => {
|
||||
expect(findAlert().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('clears the description draft', () => {
|
||||
expect(clearDraft).toHaveBeenCalledWith(expect.any(Array));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,32 +1,42 @@
|
|||
import { clearDraft, getDraft, updateDraft } from '~/lib/utils/autosave';
|
||||
import { clearDraft, getDraft, updateDraft, getLockVersion } from '~/lib/utils/autosave';
|
||||
|
||||
describe('autosave utils', () => {
|
||||
const autosaveKey = 'dummy-autosave-key';
|
||||
const text = 'some dummy text';
|
||||
const lockVersion = '2';
|
||||
const normalizedAutosaveKey = `autosave/${autosaveKey}`;
|
||||
const lockVersionKey = `autosave/${autosaveKey}/lockVersion`;
|
||||
|
||||
describe('clearDraft', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.setItem(`autosave/${autosaveKey}`, text);
|
||||
localStorage.setItem(normalizedAutosaveKey, text);
|
||||
localStorage.setItem(lockVersionKey, lockVersion);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
localStorage.removeItem(`autosave/${autosaveKey}`);
|
||||
localStorage.removeItem(normalizedAutosaveKey);
|
||||
});
|
||||
|
||||
it('removes the draft from localStorage', () => {
|
||||
clearDraft(autosaveKey);
|
||||
|
||||
expect(localStorage.getItem(`autosave/${autosaveKey}`)).toBe(null);
|
||||
expect(localStorage.getItem(normalizedAutosaveKey)).toBe(null);
|
||||
});
|
||||
|
||||
it('removes the lockVersion from localStorage', () => {
|
||||
clearDraft(autosaveKey);
|
||||
|
||||
expect(localStorage.getItem(lockVersionKey)).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDraft', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.setItem(`autosave/${autosaveKey}`, text);
|
||||
localStorage.setItem(normalizedAutosaveKey, text);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
localStorage.removeItem(`autosave/${autosaveKey}`);
|
||||
localStorage.removeItem(normalizedAutosaveKey);
|
||||
});
|
||||
|
||||
it('returns the draft from localStorage', () => {
|
||||
|
@ -36,7 +46,7 @@ describe('autosave utils', () => {
|
|||
});
|
||||
|
||||
it('returns null if no entry exists in localStorage', () => {
|
||||
localStorage.removeItem(`autosave/${autosaveKey}`);
|
||||
localStorage.removeItem(normalizedAutosaveKey);
|
||||
|
||||
const result = getDraft(autosaveKey);
|
||||
|
||||
|
@ -46,19 +56,44 @@ describe('autosave utils', () => {
|
|||
|
||||
describe('updateDraft', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.setItem(`autosave/${autosaveKey}`, text);
|
||||
localStorage.setItem(normalizedAutosaveKey, text);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
localStorage.removeItem(`autosave/${autosaveKey}`);
|
||||
localStorage.removeItem(normalizedAutosaveKey);
|
||||
});
|
||||
|
||||
it('removes the draft from localStorage', () => {
|
||||
it('updates the stored draft', () => {
|
||||
const newText = 'new text';
|
||||
|
||||
updateDraft(autosaveKey, newText);
|
||||
|
||||
expect(localStorage.getItem(`autosave/${autosaveKey}`)).toBe(newText);
|
||||
expect(localStorage.getItem(normalizedAutosaveKey)).toBe(newText);
|
||||
});
|
||||
|
||||
describe('when lockVersion is provided', () => {
|
||||
it('updates the stored lockVersion', () => {
|
||||
const newText = 'new text';
|
||||
const newLockVersion = '2';
|
||||
|
||||
updateDraft(autosaveKey, newText, lockVersion);
|
||||
|
||||
expect(localStorage.getItem(lockVersionKey)).toBe(newLockVersion);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLockVersion', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.setItem(lockVersionKey, lockVersion);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
localStorage.removeItem(lockVersionKey);
|
||||
});
|
||||
|
||||
it('returns the lockVersion from localStorage', () => {
|
||||
expect(getLockVersion(autosaveKey)).toBe(lockVersion);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import testAction from 'helpers/vuex_action_helper';
|
||||
import Api from '~/api';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/flash';
|
||||
import { FETCH_PACKAGE_VERSIONS_ERROR } from '~/packages_and_registries/infrastructure_registry/details/constants';
|
||||
import {
|
||||
fetchPackageVersions,
|
||||
|
@ -67,9 +67,9 @@ describe('Actions Package details store', () => {
|
|||
[],
|
||||
);
|
||||
expect(Api.projectPackage).toHaveBeenCalledWith(packageEntity.project_id, packageEntity.id);
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: FETCH_PACKAGE_VERSIONS_ERROR,
|
||||
type: 'warning',
|
||||
variant: VARIANT_WARNING,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -87,9 +87,9 @@ describe('Actions Package details store', () => {
|
|||
Api.deleteProjectPackage = jest.fn().mockRejectedValue();
|
||||
|
||||
await testAction(deletePackage, undefined, { packageEntity }, [], []);
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: DELETE_PACKAGE_ERROR_MESSAGE,
|
||||
type: 'warning',
|
||||
variant: VARIANT_WARNING,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -112,18 +112,18 @@ describe('Actions Package details store', () => {
|
|||
packageEntity.id,
|
||||
fileId,
|
||||
);
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
|
||||
type: 'success',
|
||||
variant: VARIANT_SUCCESS,
|
||||
});
|
||||
});
|
||||
|
||||
it('should create flash on API error', async () => {
|
||||
Api.deleteProjectPackageFile = jest.fn().mockRejectedValue();
|
||||
await testAction(deletePackageFile, fileId, { packageEntity }, [], []);
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: DELETE_PACKAGE_FILE_ERROR_MESSAGE,
|
||||
type: 'warning',
|
||||
variant: VARIANT_WARNING,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils';
|
|||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert, VARIANT_INFO } from '~/flash';
|
||||
import * as commonUtils from '~/lib/utils/common_utils';
|
||||
import PackageListApp from '~/packages_and_registries/infrastructure_registry/list/components/packages_list_app.vue';
|
||||
import { DELETE_PACKAGE_SUCCESS_MESSAGE } from '~/packages_and_registries/infrastructure_registry/list/constants';
|
||||
|
@ -222,9 +222,9 @@ describe('packages_list_app', () => {
|
|||
it(`creates a flash if the query string contains ${SHOW_DELETE_SUCCESS_ALERT}`, () => {
|
||||
mountComponent();
|
||||
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: DELETE_PACKAGE_SUCCESS_MESSAGE,
|
||||
type: 'notice',
|
||||
variant: VARIANT_INFO,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -238,7 +238,7 @@ describe('packages_list_app', () => {
|
|||
setWindowLocation('?');
|
||||
mountComponent();
|
||||
|
||||
expect(createFlash).not.toHaveBeenCalled();
|
||||
expect(createAlert).not.toHaveBeenCalled();
|
||||
expect(commonUtils.historyReplaceState).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@ import axios from 'axios';
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import testAction from 'helpers/vuex_action_helper';
|
||||
import Api from '~/api';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { MISSING_DELETE_PATH_ERROR } from '~/packages_and_registries/infrastructure_registry/list/constants';
|
||||
import * as actions from '~/packages_and_registries/infrastructure_registry/list/stores/actions';
|
||||
import * as types from '~/packages_and_registries/infrastructure_registry/list/stores/mutation_types';
|
||||
|
@ -107,7 +107,7 @@ describe('Actions Package list store', () => {
|
|||
{ type: 'setLoading', payload: false },
|
||||
],
|
||||
);
|
||||
expect(createFlash).toHaveBeenCalled();
|
||||
expect(createAlert).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should force the terraform_module type when forceTerraform is true', async () => {
|
||||
|
@ -209,17 +209,17 @@ describe('Actions Package list store', () => {
|
|||
{ type: 'setLoading', payload: false },
|
||||
],
|
||||
);
|
||||
expect(createFlash).toHaveBeenCalled();
|
||||
expect(createAlert).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each`
|
||||
property | actionPayload
|
||||
${'_links'} | ${{}}
|
||||
${'delete_api_path'} | ${{ _links: {} }}
|
||||
`('should reject and createFlash when $property is missing', ({ actionPayload }) => {
|
||||
`('should reject and createAlert when $property is missing', ({ actionPayload }) => {
|
||||
return testAction(actions.requestDeletePackage, actionPayload, null, [], []).catch((e) => {
|
||||
expect(e).toEqual(new Error(MISSING_DELETE_PATH_ERROR));
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: DELETE_PACKAGE_ERROR_MESSAGE,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,7 +3,7 @@ import VueApollo from 'vue-apollo';
|
|||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/flash';
|
||||
import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue';
|
||||
|
||||
import destroyPackageMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql';
|
||||
|
@ -104,22 +104,22 @@ describe('DeletePackage', () => {
|
|||
expect(wrapper.emitted('end')).toEqual([[]]);
|
||||
});
|
||||
|
||||
it('does not call createFlash', async () => {
|
||||
it('does not call createAlert', async () => {
|
||||
createComponent();
|
||||
|
||||
await clickOnButtonAndWait(eventPayload);
|
||||
|
||||
expect(createFlash).not.toHaveBeenCalled();
|
||||
expect(createAlert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls createFlash with the success message when showSuccessAlert is true', async () => {
|
||||
it('calls createAlert with the success message when showSuccessAlert is true', async () => {
|
||||
createComponent({ showSuccessAlert: true });
|
||||
|
||||
await clickOnButtonAndWait(eventPayload);
|
||||
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: DeletePackage.i18n.successMessage,
|
||||
type: 'success',
|
||||
variant: VARIANT_SUCCESS,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -141,14 +141,14 @@ describe('DeletePackage', () => {
|
|||
expect(wrapper.emitted('end')).toEqual([[]]);
|
||||
});
|
||||
|
||||
it('calls createFlash with the error message', async () => {
|
||||
it('calls createAlert with the error message', async () => {
|
||||
createComponent({ showSuccessAlert: true });
|
||||
|
||||
await clickOnButtonAndWait(eventPayload);
|
||||
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: DeletePackage.i18n.errorMessage,
|
||||
type: 'warning',
|
||||
variant: VARIANT_WARNING,
|
||||
captureError: true,
|
||||
error: expect.any(Error),
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
|
|||
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
|
||||
import AdditionalMetadata from '~/packages_and_registries/package_registry/components/details/additional_metadata.vue';
|
||||
import PackagesApp from '~/packages_and_registries/package_registry/pages/details.vue';
|
||||
|
@ -149,7 +149,7 @@ describe('PackagesApp', () => {
|
|||
|
||||
await waitForPromises();
|
||||
|
||||
expect(createFlash).toHaveBeenCalledWith(
|
||||
expect(createAlert).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: FETCH_PACKAGE_DETAILS_ERROR_MESSAGE,
|
||||
}),
|
||||
|
@ -383,7 +383,7 @@ describe('PackagesApp', () => {
|
|||
|
||||
await doDeleteFile();
|
||||
|
||||
expect(createFlash).toHaveBeenCalledWith(
|
||||
expect(createAlert).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
|
||||
}),
|
||||
|
@ -399,7 +399,7 @@ describe('PackagesApp', () => {
|
|||
|
||||
await doDeleteFile();
|
||||
|
||||
expect(createFlash).toHaveBeenCalledWith(
|
||||
expect(createAlert).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: DELETE_PACKAGE_FILE_ERROR_MESSAGE,
|
||||
}),
|
||||
|
@ -416,7 +416,7 @@ describe('PackagesApp', () => {
|
|||
|
||||
await doDeleteFile();
|
||||
|
||||
expect(createFlash).toHaveBeenCalledWith(
|
||||
expect(createAlert).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: DELETE_PACKAGE_FILE_ERROR_MESSAGE,
|
||||
}),
|
||||
|
@ -468,7 +468,7 @@ describe('PackagesApp', () => {
|
|||
|
||||
await doDeleteFiles();
|
||||
|
||||
expect(createFlash).toHaveBeenCalledWith(
|
||||
expect(createAlert).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: DELETE_PACKAGE_FILES_SUCCESS_MESSAGE,
|
||||
}),
|
||||
|
@ -484,7 +484,7 @@ describe('PackagesApp', () => {
|
|||
|
||||
await doDeleteFiles();
|
||||
|
||||
expect(createFlash).toHaveBeenCalledWith(
|
||||
expect(createAlert).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: DELETE_PACKAGE_FILES_ERROR_MESSAGE,
|
||||
}),
|
||||
|
@ -501,7 +501,7 @@ describe('PackagesApp', () => {
|
|||
|
||||
await doDeleteFiles();
|
||||
|
||||
expect(createFlash).toHaveBeenCalledWith(
|
||||
expect(createAlert).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: DELETE_PACKAGE_FILES_ERROR_MESSAGE,
|
||||
}),
|
||||
|
|
|
@ -134,6 +134,40 @@ describe('CompareApp component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('mode dropdown', () => {
|
||||
const findModeDropdownButton = () => wrapper.find('[data-testid="modeDropdown"]');
|
||||
const findEnableStraightModeButton = () =>
|
||||
wrapper.find('[data-testid="enableStraightModeButton"]');
|
||||
const findDisableStraightModeButton = () =>
|
||||
wrapper.find('[data-testid="disableStraightModeButton"]');
|
||||
|
||||
it('renders the mode dropdown button', () => {
|
||||
expect(findModeDropdownButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('has the correct text', () => {
|
||||
expect(findEnableStraightModeButton().text()).toBe('...');
|
||||
expect(findDisableStraightModeButton().text()).toBe('..');
|
||||
});
|
||||
|
||||
it('straight mode button when clicked', async () => {
|
||||
expect(wrapper.props('straight')).toBe(false);
|
||||
expect(wrapper.find('input[name="straight"]').attributes('value')).toBe('false');
|
||||
|
||||
findEnableStraightModeButton().vm.$emit('click');
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.find('input[name="straight"]').attributes('value')).toBe('true');
|
||||
|
||||
findDisableStraightModeButton().vm.$emit('click');
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.find('input[name="straight"]').attributes('value')).toBe('false');
|
||||
});
|
||||
});
|
||||
|
||||
describe('merge request buttons', () => {
|
||||
const findProjectMrButton = () => wrapper.find('[data-testid="projectMrButton"]');
|
||||
const findCreateMrButton = () => wrapper.find('[data-testid="createMrButton"]');
|
||||
|
|
|
@ -17,6 +17,7 @@ export const appDefaultProps = {
|
|||
projects: [sourceProject],
|
||||
paramsFrom: 'main',
|
||||
paramsTo: 'target/branch',
|
||||
straight: false,
|
||||
createMrPath: '',
|
||||
sourceProjectRefsPath,
|
||||
targetProjectRefsPath,
|
||||
|
|
|
@ -15,7 +15,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
|
|||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { IssuableType } from '~/issues/constants';
|
||||
import { timeFor } from '~/lib/utils/datetime_utility';
|
||||
|
@ -369,9 +369,9 @@ describe('SidebarDropdownWidget', () => {
|
|||
findDropdownItemWithText('title').vm.$emit('click');
|
||||
});
|
||||
|
||||
it(`calls createFlash with "${expectedMsg}"`, async () => {
|
||||
it(`calls createAlert with "${expectedMsg}"`, async () => {
|
||||
await nextTick();
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: expectedMsg,
|
||||
captureError: true,
|
||||
error: expectedMsg,
|
||||
|
@ -455,14 +455,14 @@ describe('SidebarDropdownWidget', () => {
|
|||
describe('milestones', () => {
|
||||
let projectMilestonesSpy;
|
||||
|
||||
it('should call createFlash if milestones query fails', async () => {
|
||||
it('should call createAlert if milestones query fails', async () => {
|
||||
await createComponentWithApollo({
|
||||
projectMilestonesSpy: jest.fn().mockRejectedValue(error),
|
||||
});
|
||||
|
||||
await clickEdit();
|
||||
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: wrapper.vm.i18n.listFetchError,
|
||||
captureError: true,
|
||||
error: expect.any(Error),
|
||||
|
@ -514,12 +514,12 @@ describe('SidebarDropdownWidget', () => {
|
|||
});
|
||||
|
||||
describe('currentAttributes', () => {
|
||||
it('should call createFlash if currentAttributes query fails', async () => {
|
||||
it('should call createAlert if currentAttributes query fails', async () => {
|
||||
await createComponentWithApollo({
|
||||
currentMilestoneSpy: jest.fn().mockRejectedValue(error),
|
||||
});
|
||||
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: wrapper.vm.i18n.currentFetchError,
|
||||
captureError: true,
|
||||
error: expect.any(Error),
|
||||
|
|
|
@ -4,7 +4,7 @@ import Vue from 'vue';
|
|||
import VueApollo from 'vue-apollo';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
|
||||
import SidebarSubscriptionWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue';
|
||||
import issueSubscribedQuery from '~/sidebar/queries/issue_subscribed.query.graphql';
|
||||
|
@ -144,7 +144,7 @@ describe('Sidebar Subscriptions Widget', () => {
|
|||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(createFlash).toHaveBeenCalled();
|
||||
expect(createAlert).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('merge request', () => {
|
||||
|
|
|
@ -6,7 +6,7 @@ import VueApollo from 'vue-apollo';
|
|||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import Report from '~/sidebar/components/time_tracking/report.vue';
|
||||
import getIssueTimelogsQuery from '~/vue_shared/components/sidebar/queries/get_issue_timelogs.query.graphql';
|
||||
import getMrTimelogsQuery from '~/vue_shared/components/sidebar/queries/get_mr_timelogs.query.graphql';
|
||||
|
@ -65,7 +65,7 @@ describe('Issuable Time Tracking Report', () => {
|
|||
mountComponent({ queryHandler: jest.fn().mockRejectedValue('ERROR') });
|
||||
await waitForPromises();
|
||||
|
||||
expect(createFlash).toHaveBeenCalled();
|
||||
expect(createAlert).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('for issue', () => {
|
||||
|
@ -153,7 +153,7 @@ describe('Issuable Time Tracking Report', () => {
|
|||
await findDeleteButton().trigger('click');
|
||||
await waitForPromises();
|
||||
|
||||
expect(createFlash).not.toHaveBeenCalled();
|
||||
expect(createAlert).not.toHaveBeenCalled();
|
||||
expect(mutateSpy).toHaveBeenCalledWith({
|
||||
mutation: deleteTimelogMutation,
|
||||
variables: {
|
||||
|
@ -164,7 +164,7 @@ describe('Issuable Time Tracking Report', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('calls `createFlash` with errorMessage and does not remove the row on promise reject', async () => {
|
||||
it('calls `createAlert` with errorMessage and does not remove the row on promise reject', async () => {
|
||||
const mutateSpy = jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue({});
|
||||
|
||||
await waitForPromises();
|
||||
|
@ -180,7 +180,7 @@ describe('Issuable Time Tracking Report', () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: 'An error occurred while removing the timelog.',
|
||||
captureError: true,
|
||||
error: expect.any(Object),
|
||||
|
|
|
@ -4,7 +4,7 @@ import Vue, { nextTick } from 'vue';
|
|||
import VueApollo from 'vue-apollo';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import SidebarTodoWidget from '~/sidebar/components/todo_toggle/sidebar_todo_widget.vue';
|
||||
import epicTodoQuery from '~/sidebar/queries/epic_todo.query.graphql';
|
||||
import TodoButton from '~/vue_shared/components/sidebar/todo_toggle/todo_button.vue';
|
||||
|
@ -83,7 +83,7 @@ describe('Sidebar Todo Widget', () => {
|
|||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(createFlash).toHaveBeenCalled();
|
||||
expect(createAlert).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('collapsed', () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import $ from 'jquery';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import SidebarMoveIssue from '~/sidebar/lib/sidebar_move_issue';
|
||||
import SidebarService from '~/sidebar/services/sidebar_service';
|
||||
|
@ -115,7 +115,7 @@ describe('SidebarMoveIssue', () => {
|
|||
// Wait for the move issue request to fail
|
||||
await waitForPromises();
|
||||
|
||||
expect(createFlash).toHaveBeenCalled();
|
||||
expect(createAlert).toHaveBeenCalled();
|
||||
expect(test.$confirmButton.prop('disabled')).toBe(false);
|
||||
expect(test.$confirmButton.hasClass('is-loading')).toBe(false);
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ import { stubPerformanceWebAPI } from 'helpers/performance';
|
|||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import GetSnippetQuery from 'shared_queries/snippet/snippet.query.graphql';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import * as urlUtils from '~/lib/utils/url_utility';
|
||||
import SnippetEditApp from '~/snippets/components/edit.vue';
|
||||
import SnippetBlobActionsEdit from '~/snippets/components/snippet_blob_actions_edit.vue';
|
||||
|
@ -361,7 +361,7 @@ describe('Snippet Edit app', () => {
|
|||
await waitForPromises();
|
||||
|
||||
expect(urlUtils.redirectTo).not.toHaveBeenCalled();
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: `Can't create snippet: ${TEST_MUTATION_ERROR}`,
|
||||
});
|
||||
});
|
||||
|
@ -385,7 +385,7 @@ describe('Snippet Edit app', () => {
|
|||
});
|
||||
|
||||
expect(urlUtils.redirectTo).not.toHaveBeenCalled();
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: `Can't update snippet: ${TEST_MUTATION_ERROR}`,
|
||||
});
|
||||
},
|
||||
|
@ -407,7 +407,7 @@ describe('Snippet Edit app', () => {
|
|||
|
||||
it('should flash', () => {
|
||||
// Apollo automatically wraps the resolver's error in a NetworkError
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: `Can't update snippet: ${TEST_API_ERROR.message}`,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@ import AxiosMockAdapter from 'axios-mock-adapter';
|
|||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import BlobHeaderEdit from '~/blob/components/blob_edit_header.vue';
|
||||
import createFlash from '~/flash';
|
||||
import { createAlert } from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import SnippetBlobEdit from '~/snippets/components/snippet_blob_edit.vue';
|
||||
|
@ -125,7 +125,7 @@ describe('Snippet Blob Edit component', () => {
|
|||
it('should call flash', async () => {
|
||||
await waitForPromises();
|
||||
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: "Can't fetch content for the blob: Error: Request failed with status code 500",
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@ import { differenceInMilliseconds } from '~/lib/utils/datetime_utility';
|
|||
import SnippetHeader, { i18n } from '~/snippets/components/snippet_header.vue';
|
||||
import DeleteSnippetMutation from '~/snippets/mutations/delete_snippet.mutation.graphql';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import createFlash, { FLASH_TYPES } from '~/flash';
|
||||
import { createAlert, VARIANT_DANGER, VARIANT_SUCCESS } from '~/flash';
|
||||
|
||||
jest.mock('~/flash');
|
||||
|
||||
|
@ -267,9 +267,9 @@ describe('Snippet header component', () => {
|
|||
});
|
||||
|
||||
it.each`
|
||||
request | variant | text
|
||||
${200} | ${'SUCCESS'} | ${i18n.snippetSpamSuccess}
|
||||
${500} | ${'DANGER'} | ${i18n.snippetSpamFailure}
|
||||
request | variant | text
|
||||
${200} | ${VARIANT_SUCCESS} | ${i18n.snippetSpamSuccess}
|
||||
${500} | ${VARIANT_DANGER} | ${i18n.snippetSpamFailure}
|
||||
`(
|
||||
'renders a "$variant" flash message with "$text" message for a request with a "$request" response',
|
||||
async ({ request, variant, text }) => {
|
||||
|
@ -278,9 +278,9 @@ describe('Snippet header component', () => {
|
|||
submitAsSpamBtn.trigger('click');
|
||||
await waitForPromises();
|
||||
|
||||
expect(createFlash).toHaveBeenLastCalledWith({
|
||||
expect(createAlert).toHaveBeenLastCalledWith({
|
||||
message: expect.stringContaining(text),
|
||||
type: FLASH_TYPES[variant],
|
||||
variant,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import Vue, { nextTick } from 'vue';
|
||||
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL } from 'spec/test_constants';
|
||||
import diffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
|
||||
import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
|
||||
|
||||
describe('DiffViewer', () => {
|
||||
const requiredProps = {
|
||||
|
@ -14,37 +12,28 @@ describe('DiffViewer', () => {
|
|||
oldPath: RED_BOX_IMAGE_URL,
|
||||
oldSha: 'DEF',
|
||||
};
|
||||
let vm;
|
||||
let wrapper;
|
||||
|
||||
function createComponent(props) {
|
||||
const DiffViewer = Vue.extend(diffViewer);
|
||||
|
||||
vm = mountComponent(DiffViewer, props);
|
||||
function createComponent(propsData) {
|
||||
wrapper = mount(DiffViewer, { propsData });
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('renders image diff', async () => {
|
||||
it('renders image diff', () => {
|
||||
window.gon = {
|
||||
relative_url_root: '',
|
||||
};
|
||||
|
||||
createComponent({ ...requiredProps, projectPath: '' });
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe(
|
||||
`//-/raw/DEF/${RED_BOX_IMAGE_URL}`,
|
||||
);
|
||||
|
||||
expect(vm.$el.querySelector('.added img').getAttribute('src')).toBe(
|
||||
`//-/raw/ABC/${GREEN_BOX_IMAGE_URL}`,
|
||||
);
|
||||
expect(wrapper.find('.deleted img').attributes('src')).toBe(`//-/raw/DEF/${RED_BOX_IMAGE_URL}`);
|
||||
expect(wrapper.find('.added img').attributes('src')).toBe(`//-/raw/ABC/${GREEN_BOX_IMAGE_URL}`);
|
||||
});
|
||||
|
||||
it('renders fallback download diff display', async () => {
|
||||
it('renders fallback download diff display', () => {
|
||||
createComponent({
|
||||
...requiredProps,
|
||||
diffViewerMode: 'added',
|
||||
|
@ -52,18 +41,10 @@ describe('DiffViewer', () => {
|
|||
oldPath: 'testold.abc',
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(vm.$el.querySelector('.deleted .file-info').textContent.trim()).toContain('testold.abc');
|
||||
|
||||
expect(vm.$el.querySelector('.deleted .btn.btn-default').textContent.trim()).toContain(
|
||||
'Download',
|
||||
);
|
||||
|
||||
expect(vm.$el.querySelector('.added .file-info').textContent.trim()).toContain('test.abc');
|
||||
expect(vm.$el.querySelector('.added .btn.btn-default').textContent.trim()).toContain(
|
||||
'Download',
|
||||
);
|
||||
expect(wrapper.find('.deleted .file-info').text()).toContain('testold.abc');
|
||||
expect(wrapper.find('.deleted .btn.btn-default').text()).toContain('Download');
|
||||
expect(wrapper.find('.added .file-info').text()).toContain('test.abc');
|
||||
expect(wrapper.find('.added .btn.btn-default').text()).toContain('Download');
|
||||
});
|
||||
|
||||
describe('renamed file', () => {
|
||||
|
@ -85,7 +66,7 @@ describe('DiffViewer', () => {
|
|||
oldPath: 'testold.abc',
|
||||
});
|
||||
|
||||
expect(vm.$el.textContent).toContain('File renamed with no changes.');
|
||||
expect(wrapper.text()).toContain('File renamed with no changes.');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -99,6 +80,6 @@ describe('DiffViewer', () => {
|
|||
bMode: '321',
|
||||
});
|
||||
|
||||
expect(vm.$el.textContent).toContain('File mode changed from 123 to 321');
|
||||
expect(wrapper.text()).toContain('File mode changed from 123 to 321');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,127 +1,119 @@
|
|||
import Vue, { nextTick } from 'vue';
|
||||
import createComponent from 'helpers/vue_mount_component_helper';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { file } from 'jest/ide/helpers';
|
||||
import ItemComponent from '~/vue_shared/components/file_finder/item.vue';
|
||||
|
||||
describe('File finder item spec', () => {
|
||||
const Component = Vue.extend(ItemComponent);
|
||||
let vm;
|
||||
let localFile;
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
localFile = {
|
||||
...file(),
|
||||
name: 'test file',
|
||||
path: 'test/file',
|
||||
};
|
||||
|
||||
vm = createComponent(Component, {
|
||||
file: localFile,
|
||||
focused: true,
|
||||
searchText: '',
|
||||
index: 0,
|
||||
const createComponent = ({ file: customFileFields = {}, ...otherProps } = {}) => {
|
||||
wrapper = mount(ItemComponent, {
|
||||
propsData: {
|
||||
file: {
|
||||
...file(),
|
||||
name: 'test file',
|
||||
path: 'test/file',
|
||||
...customFileFields,
|
||||
},
|
||||
focused: true,
|
||||
searchText: '',
|
||||
index: 0,
|
||||
...otherProps,
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('renders file name & path', () => {
|
||||
expect(vm.$el.textContent).toContain('test file');
|
||||
expect(vm.$el.textContent).toContain('test/file');
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.text()).toContain('test file');
|
||||
expect(wrapper.text()).toContain('test/file');
|
||||
});
|
||||
|
||||
describe('focused', () => {
|
||||
it('adds is-focused class', () => {
|
||||
expect(vm.$el.classList).toContain('is-focused');
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.classes()).toContain('is-focused');
|
||||
});
|
||||
|
||||
it('does not have is-focused class when not focused', async () => {
|
||||
vm.focused = false;
|
||||
createComponent({ focused: false });
|
||||
|
||||
await nextTick();
|
||||
expect(vm.$el.classList).not.toContain('is-focused');
|
||||
expect(wrapper.classes()).not.toContain('is-focused');
|
||||
});
|
||||
});
|
||||
|
||||
describe('changed file icon', () => {
|
||||
it('does not render when not a changed or temp file', () => {
|
||||
expect(vm.$el.querySelector('.diff-changed-stats')).toBe(null);
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.find('.diff-changed-stats').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders when a changed file', async () => {
|
||||
vm.file.changed = true;
|
||||
createComponent({ file: { changed: true } });
|
||||
|
||||
await nextTick();
|
||||
expect(vm.$el.querySelector('.diff-changed-stats')).not.toBe(null);
|
||||
expect(wrapper.find('.diff-changed-stats').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders when a temp file', async () => {
|
||||
vm.file.tempFile = true;
|
||||
createComponent({ file: { tempFile: true } });
|
||||
|
||||
await nextTick();
|
||||
expect(vm.$el.querySelector('.diff-changed-stats')).not.toBe(null);
|
||||
expect(wrapper.find('.diff-changed-stats').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('emits event when clicked', () => {
|
||||
jest.spyOn(vm, '$emit').mockImplementation(() => {});
|
||||
it('emits event when clicked', async () => {
|
||||
createComponent();
|
||||
|
||||
vm.$el.click();
|
||||
await wrapper.find('*').trigger('click');
|
||||
|
||||
expect(vm.$emit).toHaveBeenCalledWith('click', vm.file);
|
||||
expect(wrapper.emitted('click')[0]).toStrictEqual([wrapper.props('file')]);
|
||||
});
|
||||
|
||||
describe('path', () => {
|
||||
let el;
|
||||
|
||||
beforeEach(async () => {
|
||||
vm.searchText = 'file';
|
||||
|
||||
el = vm.$el.querySelector('.diff-changed-file-path');
|
||||
|
||||
nextTick();
|
||||
});
|
||||
const findChangedFilePath = () => wrapper.find('.diff-changed-file-path');
|
||||
|
||||
it('highlights text', () => {
|
||||
expect(el.querySelectorAll('.highlighted').length).toBe(4);
|
||||
createComponent({ searchText: 'file' });
|
||||
|
||||
expect(findChangedFilePath().findAll('.highlighted')).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('adds ellipsis to long text', async () => {
|
||||
vm.file.path = new Array(70)
|
||||
const path = new Array(70)
|
||||
.fill()
|
||||
.map((_, i) => `${i}-`)
|
||||
.join('');
|
||||
|
||||
await nextTick();
|
||||
expect(el.textContent).toBe(`...${vm.file.path.substr(vm.file.path.length - 60)}`);
|
||||
createComponent({ searchText: 'file', file: { path } });
|
||||
|
||||
expect(findChangedFilePath().text()).toBe(`...${path.substring(path.length - 60)}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('name', () => {
|
||||
let el;
|
||||
|
||||
beforeEach(async () => {
|
||||
vm.searchText = 'file';
|
||||
|
||||
el = vm.$el.querySelector('.diff-changed-file-name');
|
||||
|
||||
await nextTick();
|
||||
});
|
||||
const findChangedFileName = () => wrapper.find('.diff-changed-file-name');
|
||||
|
||||
it('highlights text', () => {
|
||||
expect(el.querySelectorAll('.highlighted').length).toBe(4);
|
||||
createComponent({ searchText: 'file' });
|
||||
|
||||
expect(findChangedFileName().findAll('.highlighted')).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('does not add ellipsis to long text', async () => {
|
||||
vm.file.name = new Array(70)
|
||||
const name = new Array(70)
|
||||
.fill()
|
||||
.map((_, i) => `${i}-`)
|
||||
.join('');
|
||||
|
||||
await nextTick();
|
||||
expect(el.textContent).not.toBe(`...${vm.file.name.substr(vm.file.name.length - 60)}`);
|
||||
createComponent({ searchText: 'file', file: { name } });
|
||||
|
||||
expect(findChangedFileName().text()).not.toBe(`...${name.substring(name.length - 60)}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import Vue, { nextTick } from 'vue';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
|
||||
|
||||
describe('GlCountdown', () => {
|
||||
const Component = Vue.extend(GlCountdown);
|
||||
let vm;
|
||||
let wrapper;
|
||||
let now = '2000-01-01T00:00:00Z';
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -12,21 +11,20 @@ describe('GlCountdown', () => {
|
|||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
jest.clearAllTimers();
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('when there is time remaining', () => {
|
||||
beforeEach(async () => {
|
||||
vm = mountComponent(Component, {
|
||||
endDateString: '2000-01-01T01:02:03Z',
|
||||
wrapper = mount(GlCountdown, {
|
||||
propsData: {
|
||||
endDateString: '2000-01-01T01:02:03Z',
|
||||
},
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
it('displays remaining time', () => {
|
||||
expect(vm.$el.textContent).toContain('01:02:03');
|
||||
expect(wrapper.text()).toContain('01:02:03');
|
||||
});
|
||||
|
||||
it('updates remaining time', async () => {
|
||||
|
@ -34,21 +32,21 @@ describe('GlCountdown', () => {
|
|||
jest.advanceTimersByTime(1000);
|
||||
|
||||
await nextTick();
|
||||
expect(vm.$el.textContent).toContain('01:02:02');
|
||||
expect(wrapper.text()).toContain('01:02:02');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there is no time remaining', () => {
|
||||
beforeEach(async () => {
|
||||
vm = mountComponent(Component, {
|
||||
endDateString: '1900-01-01T00:00:00Z',
|
||||
wrapper = mount(GlCountdown, {
|
||||
propsData: {
|
||||
endDateString: '1900-01-01T00:00:00Z',
|
||||
},
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
it('displays 00:00:00', () => {
|
||||
expect(vm.$el.textContent).toContain('00:00:00');
|
||||
expect(wrapper.text()).toContain('00:00:00');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -62,8 +60,10 @@ describe('GlCountdown', () => {
|
|||
});
|
||||
|
||||
it('throws a validation error', () => {
|
||||
vm = mountComponent(Component, {
|
||||
endDateString: 'this is invalid',
|
||||
wrapper = mount(GlCountdown, {
|
||||
propsData: {
|
||||
endDateString: 'this is invalid',
|
||||
},
|
||||
});
|
||||
|
||||
expect(Vue.config.warnHandler).toHaveBeenCalledTimes(1);
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import panelResizer from '~/vue_shared/components/panel_resizer.vue';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
|
||||
|
||||
describe('Panel Resizer component', () => {
|
||||
let vm;
|
||||
let PanelResizer;
|
||||
let wrapper;
|
||||
|
||||
const triggerEvent = (eventName, el = vm.$el, clientX = 0) => {
|
||||
const triggerEvent = (eventName, el = wrapper.element, clientX = 0) => {
|
||||
const event = document.createEvent('MouseEvents');
|
||||
event.initMouseEvent(
|
||||
eventName,
|
||||
|
@ -29,57 +27,64 @@ describe('Panel Resizer component', () => {
|
|||
el.dispatchEvent(event);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
PanelResizer = Vue.extend(panelResizer);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('should render a div element with the correct classes and styles', () => {
|
||||
vm = mountComponent(PanelResizer, {
|
||||
startSize: 100,
|
||||
side: 'left',
|
||||
wrapper = mount(PanelResizer, {
|
||||
propsData: {
|
||||
startSize: 100,
|
||||
side: 'left',
|
||||
},
|
||||
});
|
||||
|
||||
expect(vm.$el.tagName).toEqual('DIV');
|
||||
expect(vm.$el.getAttribute('class')).toBe(
|
||||
'position-absolute position-top-0 position-bottom-0 drag-handle position-left-0',
|
||||
);
|
||||
expect(wrapper.element.tagName).toEqual('DIV');
|
||||
expect(wrapper.classes().sort()).toStrictEqual([
|
||||
'drag-handle',
|
||||
'position-absolute',
|
||||
'position-bottom-0',
|
||||
'position-left-0',
|
||||
'position-top-0',
|
||||
]);
|
||||
|
||||
expect(vm.$el.getAttribute('style')).toBe('cursor: ew-resize;');
|
||||
expect(wrapper.element.getAttribute('style')).toBe('cursor: ew-resize;');
|
||||
});
|
||||
|
||||
it('should render a div element with the correct classes for a right side panel', () => {
|
||||
vm = mountComponent(PanelResizer, {
|
||||
startSize: 100,
|
||||
side: 'right',
|
||||
wrapper = mount(PanelResizer, {
|
||||
propsData: {
|
||||
startSize: 100,
|
||||
side: 'right',
|
||||
},
|
||||
});
|
||||
|
||||
expect(vm.$el.tagName).toEqual('DIV');
|
||||
expect(vm.$el.getAttribute('class')).toBe(
|
||||
'position-absolute position-top-0 position-bottom-0 drag-handle position-right-0',
|
||||
);
|
||||
expect(wrapper.element.tagName).toEqual('DIV');
|
||||
expect(wrapper.classes().sort()).toStrictEqual([
|
||||
'drag-handle',
|
||||
'position-absolute',
|
||||
'position-bottom-0',
|
||||
'position-right-0',
|
||||
'position-top-0',
|
||||
]);
|
||||
});
|
||||
|
||||
it('drag the resizer', () => {
|
||||
vm = mountComponent(PanelResizer, {
|
||||
startSize: 100,
|
||||
side: 'left',
|
||||
wrapper = mount(PanelResizer, {
|
||||
propsData: {
|
||||
startSize: 100,
|
||||
side: 'left',
|
||||
},
|
||||
});
|
||||
|
||||
jest.spyOn(vm, '$emit').mockImplementation(() => {});
|
||||
triggerEvent('mousedown', vm.$el);
|
||||
triggerEvent('mousedown');
|
||||
triggerEvent('mousemove', document);
|
||||
triggerEvent('mouseup', document);
|
||||
|
||||
expect(vm.$emit.mock.calls).toEqual([
|
||||
['resize-start', 100],
|
||||
['update:size', 100],
|
||||
['resize-end', 100],
|
||||
]);
|
||||
|
||||
expect(vm.size).toBe(100);
|
||||
expect(wrapper.emitted()).toEqual({
|
||||
'resize-start': [[100]],
|
||||
'update:size': [[100]],
|
||||
'resize-end': [[100]],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,121 +1,109 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import stackedProgressBarComponent from '~/vue_shared/components/stacked_progress_bar.vue';
|
||||
|
||||
const createComponent = (config) => {
|
||||
const Component = Vue.extend(stackedProgressBarComponent);
|
||||
const defaultConfig = {
|
||||
successLabel: 'Synced',
|
||||
failureLabel: 'Failed',
|
||||
neutralLabel: 'Out of sync',
|
||||
successCount: 25,
|
||||
failureCount: 10,
|
||||
totalCount: 5000,
|
||||
...config,
|
||||
};
|
||||
|
||||
return mountComponent(Component, defaultConfig);
|
||||
};
|
||||
import { mount } from '@vue/test-utils';
|
||||
import StackedProgressBarComponent from '~/vue_shared/components/stacked_progress_bar.vue';
|
||||
|
||||
describe('StackedProgressBarComponent', () => {
|
||||
let vm;
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
vm = createComponent();
|
||||
});
|
||||
const createComponent = (config) => {
|
||||
const defaultConfig = {
|
||||
successLabel: 'Synced',
|
||||
failureLabel: 'Failed',
|
||||
neutralLabel: 'Out of sync',
|
||||
successCount: 25,
|
||||
failureCount: 10,
|
||||
totalCount: 5000,
|
||||
...config,
|
||||
};
|
||||
|
||||
wrapper = mount(StackedProgressBarComponent, { propsData: defaultConfig });
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
const findSuccessBarText = (wrapper) =>
|
||||
wrapper.$el.querySelector('.status-green').innerText.trim();
|
||||
const findNeutralBarText = (wrapper) =>
|
||||
wrapper.$el.querySelector('.status-neutral').innerText.trim();
|
||||
const findFailureBarText = (wrapper) => wrapper.$el.querySelector('.status-red').innerText.trim();
|
||||
const findUnavailableBarText = (wrapper) =>
|
||||
wrapper.$el.querySelector('.status-unavailable').innerText.trim();
|
||||
|
||||
describe('computed', () => {
|
||||
describe('neutralCount', () => {
|
||||
it('returns neutralCount based on totalCount, successCount and failureCount', () => {
|
||||
expect(vm.neutralCount).toBe(4965); // 5000 - 25 - 10
|
||||
});
|
||||
});
|
||||
});
|
||||
const findSuccessBar = () => wrapper.find('.status-green');
|
||||
const findNeutralBar = () => wrapper.find('.status-neutral');
|
||||
const findFailureBar = () => wrapper.find('.status-red');
|
||||
const findUnavailableBar = () => wrapper.find('.status-unavailable');
|
||||
|
||||
describe('template', () => {
|
||||
it('renders container element', () => {
|
||||
expect(vm.$el.classList.contains('stacked-progress-bar')).toBe(true);
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.classes()).toContain('stacked-progress-bar');
|
||||
});
|
||||
|
||||
it('renders empty state when count is unavailable', () => {
|
||||
const vmX = createComponent({ totalCount: 0, successCount: 0, failureCount: 0 });
|
||||
createComponent({ totalCount: 0, successCount: 0, failureCount: 0 });
|
||||
|
||||
expect(findUnavailableBarText(vmX)).not.toBeUndefined();
|
||||
expect(findUnavailableBar()).not.toBeUndefined();
|
||||
});
|
||||
|
||||
it('renders bar elements when count is available', () => {
|
||||
expect(findSuccessBarText(vm)).not.toBeUndefined();
|
||||
expect(findNeutralBarText(vm)).not.toBeUndefined();
|
||||
expect(findFailureBarText(vm)).not.toBeUndefined();
|
||||
createComponent();
|
||||
|
||||
expect(findSuccessBar().exists()).toBe(true);
|
||||
expect(findNeutralBar().exists()).toBe(true);
|
||||
expect(findFailureBar().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('getPercent', () => {
|
||||
it('returns correct percentages from provided count based on `totalCount`', () => {
|
||||
vm = createComponent({ totalCount: 100, successCount: 25, failureCount: 10 });
|
||||
createComponent({ totalCount: 100, successCount: 25, failureCount: 10 });
|
||||
|
||||
expect(findSuccessBarText(vm)).toBe('25%');
|
||||
expect(findNeutralBarText(vm)).toBe('65%');
|
||||
expect(findFailureBarText(vm)).toBe('10%');
|
||||
expect(findSuccessBar().text()).toBe('25%');
|
||||
expect(findNeutralBar().text()).toBe('65%');
|
||||
expect(findFailureBar().text()).toBe('10%');
|
||||
});
|
||||
|
||||
it('returns percentage with decimal place when decimal is greater than 1', () => {
|
||||
vm = createComponent({ successCount: 67 });
|
||||
createComponent({ successCount: 67 });
|
||||
|
||||
expect(findSuccessBarText(vm)).toBe('1.3%');
|
||||
expect(findSuccessBar().text()).toBe('1.3%');
|
||||
});
|
||||
|
||||
it('returns percentage as `< 1%` from provided count based on `totalCount` when evaluated value is less than 1', () => {
|
||||
vm = createComponent({ successCount: 10 });
|
||||
createComponent({ successCount: 10 });
|
||||
|
||||
expect(findSuccessBarText(vm)).toBe('< 1%');
|
||||
expect(findSuccessBar().text()).toBe('< 1%');
|
||||
});
|
||||
|
||||
it('returns not available if totalCount is falsy', () => {
|
||||
vm = createComponent({ totalCount: 0 });
|
||||
createComponent({ totalCount: 0 });
|
||||
|
||||
expect(findUnavailableBarText(vm)).toBe('Not available');
|
||||
expect(findUnavailableBar().text()).toBe('Not available');
|
||||
});
|
||||
|
||||
it('returns 99.9% when numbers are extreme decimals', () => {
|
||||
vm = createComponent({ totalCount: 1000000 });
|
||||
createComponent({ totalCount: 1000000 });
|
||||
|
||||
expect(findNeutralBarText(vm)).toBe('99.9%');
|
||||
expect(findNeutralBar().text()).toBe('99.9%');
|
||||
});
|
||||
});
|
||||
|
||||
describe('barStyle', () => {
|
||||
it('returns style string based on percentage provided', () => {
|
||||
expect(vm.barStyle(50)).toBe('width: 50%;');
|
||||
describe('bar style', () => {
|
||||
it('renders width based on percentage provided', () => {
|
||||
createComponent({ totalCount: 100, successCount: 25 });
|
||||
|
||||
expect(findSuccessBar().element.style.width).toBe('25%');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTooltip', () => {
|
||||
describe('tooltip', () => {
|
||||
describe('when hideTooltips is false', () => {
|
||||
it('returns label string based on label and count provided', () => {
|
||||
expect(vm.getTooltip('Synced', 10)).toBe('Synced: 10');
|
||||
createComponent({ successCount: 10, successLabel: 'Synced', hideTooltips: false });
|
||||
|
||||
expect(findSuccessBar().attributes('title')).toBe('Synced: 10');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when hideTooltips is true', () => {
|
||||
beforeEach(() => {
|
||||
vm = createComponent({ hideTooltips: true });
|
||||
});
|
||||
|
||||
it('returns an empty string', () => {
|
||||
expect(vm.getTooltip('Synced', 10)).toBe('');
|
||||
createComponent({ successCount: 10, successLabel: 'Synced', hideTooltips: true });
|
||||
|
||||
expect(findSuccessBar().attributes('title')).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3359,6 +3359,73 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#drop_constraint' do
|
||||
it "executes the statement to drop the constraint" do
|
||||
expect(model).to receive(:execute).with("ALTER TABLE \"test_table\" DROP CONSTRAINT \"constraint_name\" CASCADE\n")
|
||||
|
||||
model.drop_constraint(:test_table, :constraint_name, cascade: true)
|
||||
end
|
||||
|
||||
context 'when cascade option is false' do
|
||||
it "executes the statement to drop the constraint without cascade" do
|
||||
expect(model).to receive(:execute).with("ALTER TABLE \"test_table\" DROP CONSTRAINT \"constraint_name\" \n")
|
||||
|
||||
model.drop_constraint(:test_table, :constraint_name, cascade: false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#add_primary_key_using_index' do
|
||||
it "executes the statement to add the primary key" do
|
||||
expect(model).to receive(:execute).with /ALTER TABLE "test_table" ADD CONSTRAINT "old_name" PRIMARY KEY USING INDEX "new_name"/
|
||||
|
||||
model.add_primary_key_using_index(:test_table, :old_name, :new_name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when changing the primary key of a given table' do
|
||||
before do
|
||||
model.create_table(:test_table, primary_key: :id) do |t|
|
||||
t.integer :partition_number, default: 1
|
||||
end
|
||||
|
||||
model.add_index(:test_table, :id, unique: true, name: :old_index_name)
|
||||
model.add_index(:test_table, [:id, :partition_number], unique: true, name: :new_index_name)
|
||||
end
|
||||
|
||||
describe '#swap_primary_key' do
|
||||
it 'executes statements to swap primary key', :aggregate_failures do
|
||||
expect(model).to receive(:with_lock_retries).with(raise_on_exhaustion: true).ordered.and_yield
|
||||
expect(model).to receive(:execute).with(/ALTER TABLE "test_table" DROP CONSTRAINT "test_table_pkey" CASCADE/).and_call_original
|
||||
expect(model).to receive(:execute).with(/ALTER TABLE "test_table" ADD CONSTRAINT "test_table_pkey" PRIMARY KEY USING INDEX "new_index_name"/).and_call_original
|
||||
|
||||
model.swap_primary_key(:test_table, :test_table_pkey, :new_index_name)
|
||||
end
|
||||
|
||||
context 'when new index does not exist' do
|
||||
before do
|
||||
model.remove_index(:test_table, column: [:id, :partition_number])
|
||||
end
|
||||
|
||||
it 'raises ActiveRecord::StatementInvalid' do
|
||||
expect do
|
||||
model.swap_primary_key(:test_table, :test_table_pkey, :new_index_name)
|
||||
end.to raise_error(ActiveRecord::StatementInvalid)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#unswap_primary_key' do
|
||||
it 'executes statements to unswap primary key' do
|
||||
expect(model).to receive(:with_lock_retries).with(raise_on_exhaustion: true).ordered.and_yield
|
||||
expect(model).to receive(:execute).with(/ALTER TABLE "test_table" DROP CONSTRAINT "test_table_pkey" CASCADE/).ordered.and_call_original
|
||||
expect(model).to receive(:execute).with(/ALTER TABLE "test_table" ADD CONSTRAINT "test_table_pkey" PRIMARY KEY USING INDEX "old_index_name"/).ordered.and_call_original
|
||||
|
||||
model.unswap_primary_key(:test_table, :test_table_pkey, :old_index_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#drop_sequence' do
|
||||
it "executes the statement to drop the sequence" do
|
||||
expect(model).to receive(:execute).with /ALTER TABLE "test_table" ALTER COLUMN "test_column" DROP DEFAULT;\nDROP SEQUENCE IF EXISTS "test_table_id_seq"/
|
||||
|
|
|
@ -99,7 +99,6 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
|
|||
'incident_management_oncall',
|
||||
'testing',
|
||||
'issues_edit',
|
||||
'ci_secrets_management',
|
||||
'snippets',
|
||||
'code_review',
|
||||
'terraform',
|
||||
|
|
|
@ -43,18 +43,19 @@ RSpec.describe Ci::UnitTest do
|
|||
|
||||
result = described_class.find_or_create_by_batch(project, attrs)
|
||||
|
||||
expect(result).to match_array([
|
||||
have_attributes(
|
||||
key_hash: existing_test.key_hash,
|
||||
suite_name: 'rspec',
|
||||
name: 'Math#sum adds numbers'
|
||||
),
|
||||
have_attributes(
|
||||
key_hash: new_key,
|
||||
suite_name: 'jest',
|
||||
name: 'Component works'
|
||||
)
|
||||
])
|
||||
expect(result).to match_array(
|
||||
[
|
||||
have_attributes(
|
||||
key_hash: existing_test.key_hash,
|
||||
suite_name: 'rspec',
|
||||
name: 'Math#sum adds numbers'
|
||||
),
|
||||
have_attributes(
|
||||
key_hash: new_key,
|
||||
suite_name: 'jest',
|
||||
name: 'Component works'
|
||||
)
|
||||
])
|
||||
|
||||
expect(result).to all(be_persisted)
|
||||
end
|
||||
|
@ -77,13 +78,14 @@ RSpec.describe Ci::UnitTest do
|
|||
|
||||
result = described_class.find_or_create_by_batch(project, attrs)
|
||||
|
||||
expect(result).to match_array([
|
||||
have_attributes(
|
||||
key_hash: new_key,
|
||||
suite_name: 'abc...',
|
||||
name: 'abc...'
|
||||
)
|
||||
])
|
||||
expect(result).to match_array(
|
||||
[
|
||||
have_attributes(
|
||||
key_hash: new_key,
|
||||
suite_name: 'abc...',
|
||||
name: 'abc...'
|
||||
)
|
||||
])
|
||||
|
||||
expect(result).to all(be_persisted)
|
||||
end
|
||||
|
|
|
@ -49,13 +49,15 @@ RSpec.describe Clusters::Applications::CertManager do
|
|||
expect(subject.version).to eq('v0.10.1')
|
||||
expect(subject).to be_rbac
|
||||
expect(subject.files).to eq(cert_manager.files.merge(cluster_issuer_file))
|
||||
expect(subject.preinstall).to eq([
|
||||
'kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.10/deploy/manifests/00-crds.yaml',
|
||||
'kubectl label --overwrite namespace gitlab-managed-apps certmanager.k8s.io/disable-validation=true'
|
||||
])
|
||||
expect(subject.postinstall).to eq([
|
||||
"for i in $(seq 1 90); do kubectl apply -f /data/helm/certmanager/config/cluster_issuer.yaml && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s)"
|
||||
])
|
||||
expect(subject.preinstall).to eq(
|
||||
[
|
||||
'kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.10/deploy/manifests/00-crds.yaml',
|
||||
'kubectl label --overwrite namespace gitlab-managed-apps certmanager.k8s.io/disable-validation=true'
|
||||
])
|
||||
expect(subject.postinstall).to eq(
|
||||
[
|
||||
"for i in $(seq 1 90); do kubectl apply -f /data/helm/certmanager/config/cluster_issuer.yaml && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s)"
|
||||
])
|
||||
end
|
||||
|
||||
context 'for a specific user' do
|
||||
|
@ -99,15 +101,16 @@ RSpec.describe Clusters::Applications::CertManager do
|
|||
end
|
||||
|
||||
it 'specifies a post delete command to remove custom resource definitions' do
|
||||
expect(subject.postdelete).to eq([
|
||||
'kubectl delete secret -n gitlab-managed-apps letsencrypt-prod --ignore-not-found',
|
||||
'kubectl delete crd certificates.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd certificaterequests.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd challenges.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd clusterissuers.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd issuers.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd orders.certmanager.k8s.io --ignore-not-found'
|
||||
])
|
||||
expect(subject.postdelete).to eq(
|
||||
[
|
||||
'kubectl delete secret -n gitlab-managed-apps letsencrypt-prod --ignore-not-found',
|
||||
'kubectl delete crd certificates.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd certificaterequests.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd challenges.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd clusterissuers.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd issuers.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd orders.certmanager.k8s.io --ignore-not-found'
|
||||
])
|
||||
end
|
||||
|
||||
context 'secret key name is not found' do
|
||||
|
@ -119,14 +122,15 @@ RSpec.describe Clusters::Applications::CertManager do
|
|||
end
|
||||
|
||||
it 'does not try and delete the secret' do
|
||||
expect(subject.postdelete).to eq([
|
||||
'kubectl delete crd certificates.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd certificaterequests.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd challenges.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd clusterissuers.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd issuers.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd orders.certmanager.k8s.io --ignore-not-found'
|
||||
])
|
||||
expect(subject.postdelete).to eq(
|
||||
[
|
||||
'kubectl delete crd certificates.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd certificaterequests.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd challenges.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd clusterissuers.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd issuers.certmanager.k8s.io --ignore-not-found',
|
||||
'kubectl delete crd orders.certmanager.k8s.io --ignore-not-found'
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -601,19 +601,27 @@ RSpec.describe Clusters::Platforms::Kubernetes do
|
|||
|
||||
it 'creates a matching RolloutStatus' do
|
||||
expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus)
|
||||
expect(rollout_status.deployments.map(&:annotations)).to eq([
|
||||
{ 'app.gitlab.com/app' => project.full_path_slug, 'app.gitlab.com/env' => 'env-000000' }
|
||||
])
|
||||
expect(rollout_status.instances).to eq([{ pod_name: "kube-pod",
|
||||
stable: true,
|
||||
status: "pending",
|
||||
tooltip: "kube-pod (Pending)",
|
||||
track: "stable" },
|
||||
{ pod_name: "Not provided",
|
||||
stable: true,
|
||||
status: "pending",
|
||||
tooltip: "Not provided (Pending)",
|
||||
track: "stable" }])
|
||||
expect(rollout_status.deployments.map(&:annotations)).to eq(
|
||||
[
|
||||
{ 'app.gitlab.com/app' => project.full_path_slug, 'app.gitlab.com/env' => 'env-000000' }
|
||||
])
|
||||
expect(rollout_status.instances).to eq(
|
||||
[
|
||||
{
|
||||
pod_name: "kube-pod",
|
||||
stable: true,
|
||||
status: "pending",
|
||||
tooltip: "kube-pod (Pending)",
|
||||
track: "stable"
|
||||
},
|
||||
{
|
||||
pod_name: "Not provided",
|
||||
stable: true,
|
||||
status: "pending",
|
||||
tooltip: "Not provided (Pending)",
|
||||
track: "stable"
|
||||
}
|
||||
])
|
||||
end
|
||||
|
||||
context 'with canary ingress' do
|
||||
|
@ -720,11 +728,12 @@ RSpec.describe Clusters::Platforms::Kubernetes do
|
|||
end
|
||||
|
||||
it 'returns a pending pod for each missing replica' do
|
||||
expect(rollout_status.instances.map { |p| p.slice(:pod_name, :status) }).to eq([
|
||||
{ pod_name: 'pod-a-1', status: 'running' },
|
||||
{ pod_name: 'Not provided', status: 'pending' },
|
||||
{ pod_name: 'Not provided', status: 'pending' }
|
||||
])
|
||||
expect(rollout_status.instances.map { |p| p.slice(:pod_name, :status) }).to eq(
|
||||
[
|
||||
{ pod_name: 'pod-a-1', status: 'running' },
|
||||
{ pod_name: 'Not provided', status: 'pending' },
|
||||
{ pod_name: 'Not provided', status: 'pending' }
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -743,12 +752,13 @@ RSpec.describe Clusters::Platforms::Kubernetes do
|
|||
end
|
||||
|
||||
it 'returns the correct track for the pending pods' do
|
||||
expect(rollout_status.instances.map { |p| p.slice(:pod_name, :status, :track) }).to eq([
|
||||
{ pod_name: 'pod-a-1', status: 'running', track: 'canary' },
|
||||
{ pod_name: 'Not provided', status: 'pending', track: 'canary' },
|
||||
{ pod_name: 'Not provided', status: 'pending', track: 'stable' },
|
||||
{ pod_name: 'Not provided', status: 'pending', track: 'stable' }
|
||||
])
|
||||
expect(rollout_status.instances.map { |p| p.slice(:pod_name, :status, :track) }).to eq(
|
||||
[
|
||||
{ pod_name: 'pod-a-1', status: 'running', track: 'canary' },
|
||||
{ pod_name: 'Not provided', status: 'pending', track: 'canary' },
|
||||
{ pod_name: 'Not provided', status: 'pending', track: 'stable' },
|
||||
{ pod_name: 'Not provided', status: 'pending', track: 'stable' }
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -765,10 +775,11 @@ RSpec.describe Clusters::Platforms::Kubernetes do
|
|||
end
|
||||
|
||||
it 'returns the correct number of pending pods' do
|
||||
expect(rollout_status.instances.map { |p| p.slice(:pod_name, :status, :track) }).to eq([
|
||||
{ pod_name: 'Not provided', status: 'pending', track: 'mytrack' },
|
||||
{ pod_name: 'Not provided', status: 'pending', track: 'mytrack' }
|
||||
])
|
||||
expect(rollout_status.instances.map { |p| p.slice(:pod_name, :status, :track) }).to eq(
|
||||
[
|
||||
{ pod_name: 'Not provided', status: 'pending', track: 'mytrack' },
|
||||
{ pod_name: 'Not provided', status: 'pending', track: 'mytrack' }
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -42,10 +42,7 @@ RSpec.describe CommitCollection do
|
|||
merge_commit = project.commit("60ecb67744cb56576c30214ff52294f8ce2def98")
|
||||
expect(merge_commit).to receive(:merge_commit?).and_return(true)
|
||||
|
||||
collection = described_class.new(project, [
|
||||
commit,
|
||||
merge_commit
|
||||
])
|
||||
collection = described_class.new(project, [commit, merge_commit])
|
||||
|
||||
expect(collection.without_merge_commits).to contain_exactly(commit)
|
||||
end
|
||||
|
|
|
@ -127,13 +127,14 @@ RSpec.describe Compare do
|
|||
end
|
||||
|
||||
it 'returns affected file paths, without duplication' do
|
||||
expect(subject.modified_paths).to contain_exactly(*%w{
|
||||
foo/for_move.txt
|
||||
foo/bar/for_move.txt
|
||||
foo/for_create.txt
|
||||
foo/for_delete.txt
|
||||
foo/for_edit.txt
|
||||
})
|
||||
expect(subject.modified_paths).to contain_exactly(
|
||||
*%w{
|
||||
foo/for_move.txt
|
||||
foo/bar/for_move.txt
|
||||
foo/for_create.txt
|
||||
foo/for_delete.txt
|
||||
foo/for_edit.txt
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -12,9 +12,10 @@ RSpec.describe IdInOrdered do
|
|||
issue4 = create(:issue)
|
||||
issue5 = create(:issue)
|
||||
|
||||
expect(Issue.id_in_ordered([issue3.id, issue1.id, issue4.id, issue5.id, issue2.id])).to eq([
|
||||
issue3, issue1, issue4, issue5, issue2
|
||||
])
|
||||
expect(Issue.id_in_ordered([issue3.id, issue1.id, issue4.id, issue5.id, issue2.id])).to eq(
|
||||
[
|
||||
issue3, issue1, issue4, issue5, issue2
|
||||
])
|
||||
end
|
||||
|
||||
context 'when the ids are not an array of integers' do
|
||||
|
|
|
@ -47,18 +47,19 @@ RSpec.describe Noteable do
|
|||
let(:discussions) { subject.discussions }
|
||||
|
||||
it 'includes discussions for diff notes, commit diff notes, commit notes, and regular notes' do
|
||||
expect(discussions).to eq([
|
||||
DiffDiscussion.new([active_diff_note1, active_diff_note2], subject),
|
||||
DiffDiscussion.new([active_diff_note3], subject),
|
||||
DiffDiscussion.new([outdated_diff_note1, outdated_diff_note2], subject),
|
||||
Discussion.new([discussion_note1, discussion_note2], subject),
|
||||
DiffDiscussion.new([commit_diff_note1, commit_diff_note2], subject),
|
||||
OutOfContextDiscussion.new([commit_note1, commit_note2], subject),
|
||||
Discussion.new([commit_discussion_note1, commit_discussion_note2], subject),
|
||||
Discussion.new([commit_discussion_note3], subject),
|
||||
IndividualNoteDiscussion.new([note1], subject),
|
||||
IndividualNoteDiscussion.new([note2], subject)
|
||||
])
|
||||
expect(discussions).to eq(
|
||||
[
|
||||
DiffDiscussion.new([active_diff_note1, active_diff_note2], subject),
|
||||
DiffDiscussion.new([active_diff_note3], subject),
|
||||
DiffDiscussion.new([outdated_diff_note1, outdated_diff_note2], subject),
|
||||
Discussion.new([discussion_note1, discussion_note2], subject),
|
||||
DiffDiscussion.new([commit_diff_note1, commit_diff_note2], subject),
|
||||
OutOfContextDiscussion.new([commit_note1, commit_note2], subject),
|
||||
Discussion.new([commit_discussion_note1, commit_discussion_note2], subject),
|
||||
Discussion.new([commit_discussion_note3], subject),
|
||||
IndividualNoteDiscussion.new([note1], subject),
|
||||
IndividualNoteDiscussion.new([note2], subject)
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -88,23 +89,24 @@ RSpec.describe Noteable do
|
|||
{ table_name: n.table_name, discussion_id: n.discussion_id, id: n.id }
|
||||
end
|
||||
|
||||
expect(discussions).to match([
|
||||
a_hash_including(table_name: 'notes', discussion_id: active_diff_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: active_diff_note3.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: outdated_diff_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: discussion_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_diff_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_note2.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_discussion_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_discussion_note3.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: note2.discussion_id),
|
||||
a_hash_including(table_name: 'resource_label_events', id: label_event.id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: system_note.discussion_id),
|
||||
a_hash_including(table_name: 'resource_milestone_events', id: milestone_event.id),
|
||||
a_hash_including(table_name: 'resource_state_events', id: state_event.id)
|
||||
])
|
||||
expect(discussions).to match(
|
||||
[
|
||||
a_hash_including(table_name: 'notes', discussion_id: active_diff_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: active_diff_note3.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: outdated_diff_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: discussion_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_diff_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_note2.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_discussion_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_discussion_note3.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: note2.discussion_id),
|
||||
a_hash_including(table_name: 'resource_label_events', id: label_event.id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: system_note.discussion_id),
|
||||
a_hash_including(table_name: 'resource_milestone_events', id: milestone_event.id),
|
||||
a_hash_including(table_name: 'resource_state_events', id: state_event.id)
|
||||
])
|
||||
end
|
||||
|
||||
it 'filters by comments only' do
|
||||
|
@ -112,19 +114,20 @@ RSpec.describe Noteable do
|
|||
{ table_name: n.table_name, discussion_id: n.discussion_id, id: n.id }
|
||||
end
|
||||
|
||||
expect(discussions).to match([
|
||||
a_hash_including(table_name: 'notes', discussion_id: active_diff_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: active_diff_note3.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: outdated_diff_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: discussion_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_diff_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_note2.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_discussion_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_discussion_note3.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: note2.discussion_id)
|
||||
])
|
||||
expect(discussions).to match(
|
||||
[
|
||||
a_hash_including(table_name: 'notes', discussion_id: active_diff_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: active_diff_note3.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: outdated_diff_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: discussion_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_diff_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_note2.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_discussion_note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: commit_discussion_note3.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: note1.discussion_id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: note2.discussion_id)
|
||||
])
|
||||
end
|
||||
|
||||
it 'filters by system notes only' do
|
||||
|
@ -132,12 +135,13 @@ RSpec.describe Noteable do
|
|||
{ table_name: n.table_name, discussion_id: n.discussion_id, id: n.id }
|
||||
end
|
||||
|
||||
expect(discussions).to match([
|
||||
a_hash_including(table_name: 'resource_label_events', id: label_event.id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: system_note.discussion_id),
|
||||
a_hash_including(table_name: 'resource_milestone_events', id: milestone_event.id),
|
||||
a_hash_including(table_name: 'resource_state_events', id: state_event.id)
|
||||
])
|
||||
expect(discussions).to match(
|
||||
[
|
||||
a_hash_including(table_name: 'resource_label_events', id: label_event.id),
|
||||
a_hash_including(table_name: 'notes', discussion_id: system_note.discussion_id),
|
||||
a_hash_including(table_name: 'resource_milestone_events', id: milestone_event.id),
|
||||
a_hash_including(table_name: 'resource_state_events', id: state_event.id)
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -541,11 +541,12 @@ RSpec.describe DiffNote do
|
|||
|
||||
describe '#shas' do
|
||||
it 'returns list of SHAs based on original_position' do
|
||||
expect(subject.shas).to match_array([
|
||||
position.base_sha,
|
||||
position.start_sha,
|
||||
position.head_sha
|
||||
])
|
||||
expect(subject.shas).to match_array(
|
||||
[
|
||||
position.base_sha,
|
||||
position.start_sha,
|
||||
position.head_sha
|
||||
])
|
||||
end
|
||||
|
||||
context 'when position changes' do
|
||||
|
@ -554,14 +555,15 @@ RSpec.describe DiffNote do
|
|||
end
|
||||
|
||||
it 'includes the new position SHAs' do
|
||||
expect(subject.shas).to match_array([
|
||||
position.base_sha,
|
||||
position.start_sha,
|
||||
position.head_sha,
|
||||
new_position.base_sha,
|
||||
new_position.start_sha,
|
||||
new_position.head_sha
|
||||
])
|
||||
expect(subject.shas).to match_array(
|
||||
[
|
||||
position.base_sha,
|
||||
position.start_sha,
|
||||
position.head_sha,
|
||||
new_position.base_sha,
|
||||
new_position.start_sha,
|
||||
new_position.head_sha
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -37,10 +37,11 @@ RSpec.describe Discussion do
|
|||
describe '.build_collection' do
|
||||
it 'returns an array of discussions of the right type' do
|
||||
discussions = described_class.build_collection([first_note, second_note, third_note], merge_request)
|
||||
expect(discussions).to eq([
|
||||
DiffDiscussion.new([first_note, second_note], merge_request),
|
||||
DiffDiscussion.new([third_note], merge_request)
|
||||
])
|
||||
expect(discussions).to eq(
|
||||
[
|
||||
DiffDiscussion.new([first_note, second_note], merge_request),
|
||||
DiffDiscussion.new([third_note], merge_request)
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2293,10 +2293,11 @@ RSpec.describe Group do
|
|||
it 'clears both self and descendant cache when the parent value is updated' do
|
||||
expect(Rails.cache).to receive(:delete_multi)
|
||||
.with(
|
||||
match_array([
|
||||
start_with("namespaces:{#{parent.traversal_ids.first}}:first_auto_devops_config:#{parent.id}"),
|
||||
start_with("namespaces:{#{parent.traversal_ids.first}}:first_auto_devops_config:#{group.id}")
|
||||
])
|
||||
match_array(
|
||||
[
|
||||
start_with("namespaces:{#{parent.traversal_ids.first}}:first_auto_devops_config:#{parent.id}"),
|
||||
start_with("namespaces:{#{parent.traversal_ids.first}}:first_auto_devops_config:#{group.id}")
|
||||
])
|
||||
)
|
||||
|
||||
parent.update!(auto_devops_enabled: true)
|
||||
|
|
|
@ -971,11 +971,12 @@ RSpec.describe Integration do
|
|||
|
||||
describe '#secret_fields' do
|
||||
it 'returns all fields with type `password`' do
|
||||
allow(subject).to receive(:fields).and_return([
|
||||
{ name: 'password', type: 'password' },
|
||||
{ name: 'secret', type: 'password' },
|
||||
{ name: 'public', type: 'text' }
|
||||
])
|
||||
allow(subject).to receive(:fields).and_return(
|
||||
[
|
||||
{ name: 'password', type: 'password' },
|
||||
{ name: 'secret', type: 'password' },
|
||||
{ name: 'public', type: 'text' }
|
||||
])
|
||||
|
||||
expect(subject.secret_fields).to match_array(%w[password secret])
|
||||
end
|
||||
|
|
|
@ -47,14 +47,15 @@ RSpec.describe Integrations::ChatMessage::IssueMessage do
|
|||
it 'returns a message regarding opening of issues' do
|
||||
expect(subject.pretext).to eq(
|
||||
'[<http://somewhere.com|project_name>] Issue <http://url.com|#100 Issue title> opened by Test User (test.user)')
|
||||
expect(subject.attachments).to eq([
|
||||
{
|
||||
title: "#100 Issue title",
|
||||
title_link: "http://url.com",
|
||||
text: "issue description",
|
||||
color: color
|
||||
}
|
||||
])
|
||||
expect(subject.attachments).to eq(
|
||||
[
|
||||
{
|
||||
title: "#100 Issue title",
|
||||
title_link: "http://url.com",
|
||||
text: "issue description",
|
||||
color: color
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -71,12 +71,13 @@ RSpec.describe Integrations::ChatMessage::WikiPageMessage do
|
|||
end
|
||||
|
||||
it 'returns the commit message for a new wiki page' do
|
||||
expect(subject.attachments).to eq([
|
||||
{
|
||||
text: commit_message,
|
||||
color: color
|
||||
}
|
||||
])
|
||||
expect(subject.attachments).to eq(
|
||||
[
|
||||
{
|
||||
text: commit_message,
|
||||
color: color
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -86,12 +87,13 @@ RSpec.describe Integrations::ChatMessage::WikiPageMessage do
|
|||
end
|
||||
|
||||
it 'returns the commit message for an updated wiki page' do
|
||||
expect(subject.attachments).to eq([
|
||||
{
|
||||
text: commit_message,
|
||||
color: color
|
||||
}
|
||||
])
|
||||
expect(subject.attachments).to eq(
|
||||
[
|
||||
{
|
||||
text: commit_message,
|
||||
color: color
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -81,9 +81,10 @@ RSpec.describe Integrations::Jira do
|
|||
jira_integration.jira_issue_transition_id = 'foo bar'
|
||||
|
||||
expect(jira_integration).not_to be_valid
|
||||
expect(jira_integration.errors.full_messages).to eq([
|
||||
'Jira issue transition IDs must be a list of numbers that can be split with , or ;'
|
||||
])
|
||||
expect(jira_integration.errors.full_messages).to eq(
|
||||
[
|
||||
'Jira issue transition IDs must be a list of numbers that can be split with , or ;'
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,9 +18,10 @@ RSpec.describe LabelNote do
|
|||
it_behaves_like 'label note created from events'
|
||||
|
||||
it 'includes a link to the list of issues filtered by the label' do
|
||||
note = described_class.from_events([
|
||||
create(:resource_label_event, label: label, issue: resource)
|
||||
])
|
||||
note = described_class.from_events(
|
||||
[
|
||||
create(:resource_label_event, label: label, issue: resource)
|
||||
])
|
||||
|
||||
expect(note.note_html).to include(project_issues_path(project, label_name: label.title))
|
||||
end
|
||||
|
@ -32,9 +33,10 @@ RSpec.describe LabelNote do
|
|||
it_behaves_like 'label note created from events'
|
||||
|
||||
it 'includes a link to the list of merge requests filtered by the label' do
|
||||
note = described_class.from_events([
|
||||
create(:resource_label_event, label: label, merge_request: resource)
|
||||
])
|
||||
note = described_class.from_events(
|
||||
[
|
||||
create(:resource_label_event, label: label, merge_request: resource)
|
||||
])
|
||||
|
||||
expect(note.note_html).to include(project_merge_requests_path(project, label_name: label.title))
|
||||
end
|
||||
|
|
|
@ -111,12 +111,13 @@ RSpec.describe MergeRequest::CleanupSchedule do
|
|||
let!(:cleanup_schedule_7) { create(:merge_request_cleanup_schedule, :failed, scheduled_at: 5.days.ago) }
|
||||
|
||||
it 'returns records that are scheduled before or on current time and unstarted (ordered by scheduled first)' do
|
||||
expect(described_class.scheduled_and_unstarted).to eq([
|
||||
cleanup_schedule_2,
|
||||
cleanup_schedule_1,
|
||||
cleanup_schedule_5,
|
||||
cleanup_schedule_4
|
||||
])
|
||||
expect(described_class.scheduled_and_unstarted).to eq(
|
||||
[
|
||||
cleanup_schedule_2,
|
||||
cleanup_schedule_1,
|
||||
cleanup_schedule_5,
|
||||
cleanup_schedule_4
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -466,19 +466,20 @@ RSpec.describe MergeRequestDiff do
|
|||
diff_with_commits.update!(sorted: false) # Mark as unsorted so it'll re-order
|
||||
|
||||
# There will be 11 returned, as we have to take into account for new and old paths
|
||||
expect(diff_with_commits.diffs_in_batch(0, 10, diff_options: diff_options).diff_paths).to eq([
|
||||
'bar/branch-test.txt',
|
||||
'custom-highlighting/test.gitlab-custom',
|
||||
'encoding/iso8859.txt',
|
||||
'files/images/wm.svg',
|
||||
'files/js/commit.js.coffee',
|
||||
'files/js/commit.coffee',
|
||||
'files/lfs/lfs_object.iso',
|
||||
'files/ruby/popen.rb',
|
||||
'files/ruby/regex.rb',
|
||||
'files/.DS_Store',
|
||||
'files/whitespace'
|
||||
])
|
||||
expect(diff_with_commits.diffs_in_batch(0, 10, diff_options: diff_options).diff_paths).to eq(
|
||||
[
|
||||
'bar/branch-test.txt',
|
||||
'custom-highlighting/test.gitlab-custom',
|
||||
'encoding/iso8859.txt',
|
||||
'files/images/wm.svg',
|
||||
'files/js/commit.js.coffee',
|
||||
'files/js/commit.coffee',
|
||||
'files/lfs/lfs_object.iso',
|
||||
'files/ruby/popen.rb',
|
||||
'files/ruby/regex.rb',
|
||||
'files/.DS_Store',
|
||||
'files/whitespace'
|
||||
])
|
||||
end
|
||||
|
||||
context 'when diff_options include ignore_whitespace_change' do
|
||||
|
@ -555,29 +556,30 @@ RSpec.describe MergeRequestDiff do
|
|||
it 'sorts diff files directory first' do
|
||||
diff_with_commits.update!(sorted: false) # Mark as unsorted so it'll re-order
|
||||
|
||||
expect(diff_with_commits.diffs(diff_options).diff_paths).to eq([
|
||||
'bar/branch-test.txt',
|
||||
'custom-highlighting/test.gitlab-custom',
|
||||
'encoding/iso8859.txt',
|
||||
'files/images/wm.svg',
|
||||
'files/js/commit.js.coffee',
|
||||
'files/js/commit.coffee',
|
||||
'files/lfs/lfs_object.iso',
|
||||
'files/ruby/popen.rb',
|
||||
'files/ruby/regex.rb',
|
||||
'files/.DS_Store',
|
||||
'files/whitespace',
|
||||
'foo/bar/.gitkeep',
|
||||
'with space/README.md',
|
||||
'.DS_Store',
|
||||
'.gitattributes',
|
||||
'.gitignore',
|
||||
'.gitmodules',
|
||||
'CHANGELOG',
|
||||
'README',
|
||||
'gitlab-grack',
|
||||
'gitlab-shell'
|
||||
])
|
||||
expect(diff_with_commits.diffs(diff_options).diff_paths).to eq(
|
||||
[
|
||||
'bar/branch-test.txt',
|
||||
'custom-highlighting/test.gitlab-custom',
|
||||
'encoding/iso8859.txt',
|
||||
'files/images/wm.svg',
|
||||
'files/js/commit.js.coffee',
|
||||
'files/js/commit.coffee',
|
||||
'files/lfs/lfs_object.iso',
|
||||
'files/ruby/popen.rb',
|
||||
'files/ruby/regex.rb',
|
||||
'files/.DS_Store',
|
||||
'files/whitespace',
|
||||
'foo/bar/.gitkeep',
|
||||
'with space/README.md',
|
||||
'.DS_Store',
|
||||
'.gitattributes',
|
||||
'.gitignore',
|
||||
'.gitmodules',
|
||||
'CHANGELOG',
|
||||
'README',
|
||||
'gitlab-grack',
|
||||
'gitlab-shell'
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -661,28 +663,29 @@ RSpec.describe MergeRequestDiff do
|
|||
mr_diff = create(:merge_request).merge_request_diff
|
||||
diff_files_paths = mr_diff.merge_request_diff_files.map { |file| file.new_path.presence || file.old_path }
|
||||
|
||||
expect(diff_files_paths).to eq([
|
||||
'bar/branch-test.txt',
|
||||
'custom-highlighting/test.gitlab-custom',
|
||||
'encoding/iso8859.txt',
|
||||
'files/images/wm.svg',
|
||||
'files/js/commit.coffee',
|
||||
'files/lfs/lfs_object.iso',
|
||||
'files/ruby/popen.rb',
|
||||
'files/ruby/regex.rb',
|
||||
'files/.DS_Store',
|
||||
'files/whitespace',
|
||||
'foo/bar/.gitkeep',
|
||||
'with space/README.md',
|
||||
'.DS_Store',
|
||||
'.gitattributes',
|
||||
'.gitignore',
|
||||
'.gitmodules',
|
||||
'CHANGELOG',
|
||||
'README',
|
||||
'gitlab-grack',
|
||||
'gitlab-shell'
|
||||
])
|
||||
expect(diff_files_paths).to eq(
|
||||
[
|
||||
'bar/branch-test.txt',
|
||||
'custom-highlighting/test.gitlab-custom',
|
||||
'encoding/iso8859.txt',
|
||||
'files/images/wm.svg',
|
||||
'files/js/commit.coffee',
|
||||
'files/lfs/lfs_object.iso',
|
||||
'files/ruby/popen.rb',
|
||||
'files/ruby/regex.rb',
|
||||
'files/.DS_Store',
|
||||
'files/whitespace',
|
||||
'foo/bar/.gitkeep',
|
||||
'with space/README.md',
|
||||
'.DS_Store',
|
||||
'.gitattributes',
|
||||
'.gitignore',
|
||||
'.gitmodules',
|
||||
'CHANGELOG',
|
||||
'README',
|
||||
'gitlab-grack',
|
||||
'gitlab-shell'
|
||||
])
|
||||
end
|
||||
|
||||
it 'expands collapsed diffs before saving' do
|
||||
|
|
|
@ -1837,9 +1837,8 @@ RSpec.describe MergeRequest, factory_default: :keep do
|
|||
context 'persisted merge request' do
|
||||
context 'with a limit' do
|
||||
it 'returns a limited number of commit shas' do
|
||||
expect(subject.commit_shas(limit: 2)).to eq(%w[
|
||||
b83d6e391c22777fca1ed3012fce84f633d7fed0 498214de67004b1da3d820901307bed2a68a8ef6
|
||||
])
|
||||
expect(subject.commit_shas(limit: 2)).to eq(
|
||||
%w[b83d6e391c22777fca1ed3012fce84f633d7fed0 498214de67004b1da3d820901307bed2a68a8ef6])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -4739,15 +4738,17 @@ RSpec.describe MergeRequest, factory_default: :keep do
|
|||
context 'persisted merge request' do
|
||||
context 'with a limit' do
|
||||
it 'returns a limited number of commits' do
|
||||
expect(subject.commits(limit: 2).map(&:sha)).to eq(%w[
|
||||
b83d6e391c22777fca1ed3012fce84f633d7fed0
|
||||
498214de67004b1da3d820901307bed2a68a8ef6
|
||||
])
|
||||
expect(subject.commits(limit: 3).map(&:sha)).to eq(%w[
|
||||
b83d6e391c22777fca1ed3012fce84f633d7fed0
|
||||
498214de67004b1da3d820901307bed2a68a8ef6
|
||||
1b12f15a11fc6e62177bef08f47bc7b5ce50b141
|
||||
])
|
||||
expect(subject.commits(limit: 2).map(&:sha)).to eq(
|
||||
%w[
|
||||
b83d6e391c22777fca1ed3012fce84f633d7fed0
|
||||
498214de67004b1da3d820901307bed2a68a8ef6
|
||||
])
|
||||
expect(subject.commits(limit: 3).map(&:sha)).to eq(
|
||||
%w[
|
||||
b83d6e391c22777fca1ed3012fce84f633d7fed0
|
||||
498214de67004b1da3d820901307bed2a68a8ef6
|
||||
1b12f15a11fc6e62177bef08f47bc7b5ce50b141
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -4792,9 +4793,10 @@ RSpec.describe MergeRequest, factory_default: :keep do
|
|||
end
|
||||
|
||||
it 'returns the safe number of commits' do
|
||||
expect(subject.recent_commits.map(&:sha)).to eq(%w[
|
||||
b83d6e391c22777fca1ed3012fce84f633d7fed0 498214de67004b1da3d820901307bed2a68a8ef6
|
||||
])
|
||||
expect(subject.recent_commits.map(&:sha)).to eq(
|
||||
%w[
|
||||
b83d6e391c22777fca1ed3012fce84f633d7fed0 498214de67004b1da3d820901307bed2a68a8ef6
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue