Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0fd2296553
commit
b487021bd3
28 changed files with 346 additions and 426 deletions
|
@ -1 +1 @@
|
|||
f6ebdc9e7754cf97776fe388f3b0914ae123f5ad
|
||||
633a58b478d6fcffc59fe600fe8dcbd66bec42f1
|
||||
|
|
|
@ -11,7 +11,12 @@ import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
|||
Vue.use(VueApollo);
|
||||
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient(),
|
||||
defaultClient: createDefaultClient(
|
||||
{},
|
||||
{
|
||||
assumeImmutableResults: true,
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
export default (params = {}) => {
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import { Mark, markInputRule, mergeAttributes } from '@tiptap/core';
|
||||
|
||||
export const inputRegexAddition = /(\{\+(.+?)\+\})$/gm;
|
||||
export const inputRegexDeletion = /(\{-(.+?)-\})$/gm;
|
||||
|
||||
export default Mark.create({
|
||||
name: 'inlineDiff',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
},
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
type: {
|
||||
default: 'addition',
|
||||
parseHTML: (element) => {
|
||||
return {
|
||||
type: element.classList.contains('deletion') ? 'deletion' : 'addition',
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: 'span.idiff',
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes: { type, ...HTMLAttributes } }) {
|
||||
return [
|
||||
'span',
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
||||
class: `idiff left right ${type}`,
|
||||
}),
|
||||
0,
|
||||
];
|
||||
},
|
||||
|
||||
addInputRules() {
|
||||
return [
|
||||
markInputRule(inputRegexAddition, this.type, () => ({ type: 'addition' })),
|
||||
markInputRule(inputRegexDeletion, this.type, () => ({ type: 'deletion' })),
|
||||
];
|
||||
},
|
||||
});
|
|
@ -16,6 +16,7 @@ import Heading from '../extensions/heading';
|
|||
import History from '../extensions/history';
|
||||
import HorizontalRule from '../extensions/horizontal_rule';
|
||||
import Image from '../extensions/image';
|
||||
import InlineDiff from '../extensions/inline_diff';
|
||||
import Italic from '../extensions/italic';
|
||||
import Link from '../extensions/link';
|
||||
import ListItem from '../extensions/list_item';
|
||||
|
@ -74,6 +75,7 @@ export const createContentEditor = ({
|
|||
History,
|
||||
HorizontalRule,
|
||||
Image,
|
||||
InlineDiff,
|
||||
Italic,
|
||||
Link,
|
||||
ListItem,
|
||||
|
|
|
@ -13,6 +13,7 @@ import HardBreak from '../extensions/hard_break';
|
|||
import Heading from '../extensions/heading';
|
||||
import HorizontalRule from '../extensions/horizontal_rule';
|
||||
import Image from '../extensions/image';
|
||||
import InlineDiff from '../extensions/inline_diff';
|
||||
import Italic from '../extensions/italic';
|
||||
import Link from '../extensions/link';
|
||||
import ListItem from '../extensions/list_item';
|
||||
|
@ -36,6 +37,15 @@ const defaultSerializerConfig = {
|
|||
[Italic.name]: { open: '_', close: '_', mixable: true, expelEnclosingWhitespace: true },
|
||||
[Subscript.name]: { open: '<sub>', close: '</sub>', mixable: true },
|
||||
[Superscript.name]: { open: '<sup>', close: '</sup>', mixable: true },
|
||||
[InlineDiff.name]: {
|
||||
mixable: true,
|
||||
open(state, mark) {
|
||||
return mark.attrs.type === 'addition' ? '{+' : '{-';
|
||||
},
|
||||
close(state, mark) {
|
||||
return mark.attrs.type === 'addition' ? '+}' : '-}';
|
||||
},
|
||||
},
|
||||
[Link.name]: {
|
||||
open() {
|
||||
return '[';
|
||||
|
|
|
@ -1,27 +1,19 @@
|
|||
<script>
|
||||
import {
|
||||
GlToken,
|
||||
GlFilteredSearchToken,
|
||||
GlFilteredSearchSuggestion,
|
||||
GlDropdownDivider,
|
||||
GlLoadingIcon,
|
||||
} from '@gitlab/ui';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { GlFilteredSearchSuggestion } from '@gitlab/ui';
|
||||
import createFlash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
import { DEBOUNCE_DELAY } from '../constants';
|
||||
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlToken,
|
||||
GlFilteredSearchToken,
|
||||
BaseToken,
|
||||
GlFilteredSearchSuggestion,
|
||||
GlDropdownDivider,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
props: {
|
||||
active: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
required: true,
|
||||
|
@ -34,82 +26,62 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
branches: this.config.initialBranches || [],
|
||||
defaultBranches: this.config.defaultBranches || [],
|
||||
loading: true,
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentValue() {
|
||||
return this.value.data.toLowerCase();
|
||||
},
|
||||
activeBranch() {
|
||||
return this.branches.find((branch) => branch.name.toLowerCase() === this.currentValue);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
active: {
|
||||
immediate: true,
|
||||
handler(newValue) {
|
||||
if (!newValue && !this.branches.length) {
|
||||
this.fetchBranchBySearchTerm(this.value.data);
|
||||
}
|
||||
},
|
||||
defaultBranches() {
|
||||
return this.config.defaultBranches || [];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fetchBranchBySearchTerm(searchTerm) {
|
||||
getActiveBranch(branches, data) {
|
||||
return branches.find((branch) => branch.name.toLowerCase() === data.toLowerCase());
|
||||
},
|
||||
fetchBranches(searchTerm) {
|
||||
this.loading = true;
|
||||
this.config
|
||||
.fetchBranches(searchTerm)
|
||||
.then(({ data }) => {
|
||||
this.branches = data;
|
||||
})
|
||||
.catch(() => createFlash({ message: __('There was a problem fetching branches.') }))
|
||||
.catch(() => {
|
||||
createFlash({ message: __('There was a problem fetching branches.') });
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
searchBranches: debounce(function debouncedSearch({ data }) {
|
||||
this.fetchBranchBySearchTerm(data);
|
||||
}, DEBOUNCE_DELAY),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-filtered-search-token
|
||||
<base-token
|
||||
:active="active"
|
||||
:config="config"
|
||||
v-bind="{ ...$props, ...$attrs }"
|
||||
:value="value"
|
||||
:default-suggestions="defaultBranches"
|
||||
:suggestions="branches"
|
||||
:suggestions-loading="loading"
|
||||
:get-active-token-value="getActiveBranch"
|
||||
@fetch-suggestions="fetchBranches"
|
||||
v-on="$listeners"
|
||||
@input="searchBranches"
|
||||
>
|
||||
<template #view-token="{ inputValue }">
|
||||
<gl-token variant="search-value">{{
|
||||
activeBranch ? activeBranch.name : inputValue
|
||||
}}</gl-token>
|
||||
<template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
|
||||
{{ activeTokenValue ? activeTokenValue.name : inputValue }}
|
||||
</template>
|
||||
<template #suggestions>
|
||||
<template #suggestions-list="{ suggestions }">
|
||||
<gl-filtered-search-suggestion
|
||||
v-for="branch in defaultBranches"
|
||||
:key="branch.value"
|
||||
:value="branch.value"
|
||||
v-for="branch in suggestions"
|
||||
:key="branch.id"
|
||||
:value="branch.name"
|
||||
>
|
||||
{{ branch.text }}
|
||||
<div class="gl-display-flex">
|
||||
<span class="gl-display-inline-block gl-mr-3 gl-p-3"></span>
|
||||
{{ branch.name }}
|
||||
</div>
|
||||
</gl-filtered-search-suggestion>
|
||||
<gl-dropdown-divider v-if="defaultBranches.length" />
|
||||
<gl-loading-icon v-if="loading" size="sm" />
|
||||
<template v-else>
|
||||
<gl-filtered-search-suggestion
|
||||
v-for="branch in branches"
|
||||
:key="branch.id"
|
||||
:value="branch.name"
|
||||
>
|
||||
<div class="gl-display-flex">
|
||||
<span class="gl-display-inline-block gl-mr-3 gl-p-3"></span>
|
||||
<div>{{ branch.name }}</div>
|
||||
</div>
|
||||
</gl-filtered-search-suggestion>
|
||||
</template>
|
||||
</template>
|
||||
</gl-filtered-search-token>
|
||||
</base-token>
|
||||
</template>
|
||||
|
|
|
@ -1,26 +1,21 @@
|
|||
<script>
|
||||
import {
|
||||
GlFilteredSearchToken,
|
||||
GlFilteredSearchSuggestion,
|
||||
GlDropdownDivider,
|
||||
GlLoadingIcon,
|
||||
} from '@gitlab/ui';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { GlFilteredSearchSuggestion } from '@gitlab/ui';
|
||||
import createFlash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
import { DEBOUNCE_DELAY, DEFAULT_NONE_ANY } from '../constants';
|
||||
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
|
||||
import { DEFAULT_NONE_ANY } from '../constants';
|
||||
import { stripQuotes } from '../filtered_search_utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlFilteredSearchToken,
|
||||
BaseToken,
|
||||
GlFilteredSearchSuggestion,
|
||||
GlDropdownDivider,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
props: {
|
||||
active: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
required: true,
|
||||
|
@ -33,87 +28,63 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
emojis: this.config.initialEmojis || [],
|
||||
defaultEmojis: this.config.defaultEmojis || DEFAULT_NONE_ANY,
|
||||
loading: true,
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentValue() {
|
||||
return this.value.data.toLowerCase();
|
||||
},
|
||||
activeEmoji() {
|
||||
return this.emojis.find(
|
||||
(emoji) => emoji.name.toLowerCase() === stripQuotes(this.currentValue),
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
active: {
|
||||
immediate: true,
|
||||
handler(newValue) {
|
||||
if (!newValue && !this.emojis.length) {
|
||||
this.fetchEmojiBySearchTerm(this.value.data);
|
||||
}
|
||||
},
|
||||
defaultEmojis() {
|
||||
return this.config.defaultEmojis || DEFAULT_NONE_ANY;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fetchEmojiBySearchTerm(searchTerm) {
|
||||
getActiveEmoji(emojis, data) {
|
||||
return emojis.find((emoji) => emoji.name.toLowerCase() === stripQuotes(data).toLowerCase());
|
||||
},
|
||||
fetchEmojis(searchTerm) {
|
||||
this.loading = true;
|
||||
this.config
|
||||
.fetchEmojis(searchTerm)
|
||||
.then((res) => {
|
||||
this.emojis = Array.isArray(res) ? res : res.data;
|
||||
.then((response) => {
|
||||
this.emojis = Array.isArray(response) ? response : response.data;
|
||||
})
|
||||
.catch(() => {
|
||||
createFlash({ message: __('There was a problem fetching emojis.') });
|
||||
})
|
||||
.catch(() =>
|
||||
createFlash({
|
||||
message: __('There was a problem fetching emojis.'),
|
||||
}),
|
||||
)
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
searchEmojis: debounce(function debouncedSearch({ data }) {
|
||||
this.fetchEmojiBySearchTerm(data);
|
||||
}, DEBOUNCE_DELAY),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-filtered-search-token
|
||||
<base-token
|
||||
:active="active"
|
||||
:config="config"
|
||||
v-bind="{ ...$props, ...$attrs }"
|
||||
:value="value"
|
||||
:default-suggestions="defaultEmojis"
|
||||
:suggestions="emojis"
|
||||
:suggestions-loading="loading"
|
||||
:get-active-token-value="getActiveEmoji"
|
||||
@fetch-suggestions="fetchEmojis"
|
||||
v-on="$listeners"
|
||||
@input="searchEmojis"
|
||||
>
|
||||
<template #view="{ inputValue }">
|
||||
<gl-emoji v-if="activeEmoji" :data-name="activeEmoji.name" />
|
||||
<span v-else>{{ inputValue }}</span>
|
||||
<template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
|
||||
<gl-emoji v-if="activeTokenValue" :data-name="activeTokenValue.name" />
|
||||
<template v-else>{{ inputValue }}</template>
|
||||
</template>
|
||||
<template #suggestions>
|
||||
<template #suggestions-list="{ suggestions }">
|
||||
<gl-filtered-search-suggestion
|
||||
v-for="emoji in defaultEmojis"
|
||||
:key="emoji.value"
|
||||
:value="emoji.value"
|
||||
v-for="emoji in suggestions"
|
||||
:key="emoji.name"
|
||||
:value="emoji.name"
|
||||
>
|
||||
{{ emoji.value }}
|
||||
<div class="gl-display-flex">
|
||||
<gl-emoji class="gl-mr-3" :data-name="emoji.name" />
|
||||
{{ emoji.name }}
|
||||
</div>
|
||||
</gl-filtered-search-suggestion>
|
||||
<gl-dropdown-divider v-if="defaultEmojis.length" />
|
||||
<gl-loading-icon v-if="loading" size="sm" />
|
||||
<template v-else>
|
||||
<gl-filtered-search-suggestion
|
||||
v-for="emoji in emojis"
|
||||
:key="emoji.name"
|
||||
:value="emoji.name"
|
||||
>
|
||||
<div class="gl-display-flex">
|
||||
<gl-emoji :data-name="emoji.name" />
|
||||
<span class="gl-ml-3">{{ emoji.name }}</span>
|
||||
</div>
|
||||
</gl-filtered-search-suggestion>
|
||||
</template>
|
||||
</template>
|
||||
</gl-filtered-search-token>
|
||||
</base-token>
|
||||
</template>
|
||||
|
|
|
@ -1,24 +1,21 @@
|
|||
<script>
|
||||
import {
|
||||
GlDropdownDivider,
|
||||
GlFilteredSearchSuggestion,
|
||||
GlFilteredSearchToken,
|
||||
GlLoadingIcon,
|
||||
} from '@gitlab/ui';
|
||||
import { debounce } from 'lodash';
|
||||
import { GlFilteredSearchSuggestion } from '@gitlab/ui';
|
||||
import createFlash from '~/flash';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { __ } from '~/locale';
|
||||
import { DEBOUNCE_DELAY, DEFAULT_ITERATIONS } from '../constants';
|
||||
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
|
||||
import { DEFAULT_ITERATIONS } from '../constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlDropdownDivider,
|
||||
BaseToken,
|
||||
GlFilteredSearchSuggestion,
|
||||
GlFilteredSearchToken,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
props: {
|
||||
active: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
required: true,
|
||||
|
@ -35,84 +32,58 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
currentValue() {
|
||||
return this.value.data;
|
||||
},
|
||||
activeIteration() {
|
||||
return this.iterations.find(
|
||||
(iteration) => getIdFromGraphQLId(iteration.id) === Number(this.currentValue),
|
||||
);
|
||||
},
|
||||
defaultIterations() {
|
||||
return this.config.defaultIterations || DEFAULT_ITERATIONS;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
active: {
|
||||
immediate: true,
|
||||
handler(newValue) {
|
||||
if (!newValue && !this.iterations.length) {
|
||||
this.fetchIterationBySearchTerm(this.currentValue);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getValue(iteration) {
|
||||
return String(getIdFromGraphQLId(iteration.id));
|
||||
getActiveIteration(iterations, data) {
|
||||
return iterations.find((iteration) => this.getValue(iteration) === data);
|
||||
},
|
||||
fetchIterationBySearchTerm(searchTerm) {
|
||||
const fetchPromise = this.config.fetchPath
|
||||
? this.config.fetchIterations(this.config.fetchPath, searchTerm)
|
||||
: this.config.fetchIterations(searchTerm);
|
||||
|
||||
fetchIterations(searchTerm) {
|
||||
this.loading = true;
|
||||
|
||||
fetchPromise
|
||||
this.config
|
||||
.fetchIterations(searchTerm)
|
||||
.then((response) => {
|
||||
this.iterations = Array.isArray(response) ? response : response.data;
|
||||
})
|
||||
.catch(() => createFlash({ message: __('There was a problem fetching iterations.') }))
|
||||
.catch(() => {
|
||||
createFlash({ message: __('There was a problem fetching iterations.') });
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
searchIterations: debounce(function debouncedSearch({ data }) {
|
||||
this.fetchIterationBySearchTerm(data);
|
||||
}, DEBOUNCE_DELAY),
|
||||
getValue(iteration) {
|
||||
return String(getIdFromGraphQLId(iteration.id));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-filtered-search-token
|
||||
<base-token
|
||||
:active="active"
|
||||
:config="config"
|
||||
v-bind="{ ...$props, ...$attrs }"
|
||||
:value="value"
|
||||
:default-suggestions="defaultIterations"
|
||||
:suggestions="iterations"
|
||||
:suggestions-loading="loading"
|
||||
:get-active-token-value="getActiveIteration"
|
||||
@fetch-suggestions="fetchIterations"
|
||||
v-on="$listeners"
|
||||
@input="searchIterations"
|
||||
>
|
||||
<template #view="{ inputValue }">
|
||||
{{ activeIteration ? activeIteration.title : inputValue }}
|
||||
<template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
|
||||
{{ activeTokenValue ? activeTokenValue.title : inputValue }}
|
||||
</template>
|
||||
<template #suggestions>
|
||||
<template #suggestions-list="{ suggestions }">
|
||||
<gl-filtered-search-suggestion
|
||||
v-for="iteration in defaultIterations"
|
||||
:key="iteration.value"
|
||||
:value="iteration.value"
|
||||
v-for="iteration in suggestions"
|
||||
:key="iteration.id"
|
||||
:value="getValue(iteration)"
|
||||
>
|
||||
{{ iteration.text }}
|
||||
{{ iteration.title }}
|
||||
</gl-filtered-search-suggestion>
|
||||
<gl-dropdown-divider v-if="defaultIterations.length" />
|
||||
<gl-loading-icon v-if="loading" size="sm" />
|
||||
<template v-else>
|
||||
<gl-filtered-search-suggestion
|
||||
v-for="iteration in iterations"
|
||||
:key="iteration.id"
|
||||
:value="getValue(iteration)"
|
||||
>
|
||||
{{ iteration.title }}
|
||||
</gl-filtered-search-suggestion>
|
||||
</template>
|
||||
</template>
|
||||
</gl-filtered-search-token>
|
||||
</base-token>
|
||||
</template>
|
||||
|
|
|
@ -1,27 +1,22 @@
|
|||
<script>
|
||||
import {
|
||||
GlFilteredSearchToken,
|
||||
GlFilteredSearchSuggestion,
|
||||
GlDropdownDivider,
|
||||
GlLoadingIcon,
|
||||
} from '@gitlab/ui';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { GlFilteredSearchSuggestion } from '@gitlab/ui';
|
||||
import createFlash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
import { sortMilestonesByDueDate } from '~/milestones/milestone_utils';
|
||||
|
||||
import { DEFAULT_MILESTONES, DEBOUNCE_DELAY } from '../constants';
|
||||
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
|
||||
import { DEFAULT_MILESTONES } from '../constants';
|
||||
import { stripQuotes } from '../filtered_search_utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlFilteredSearchToken,
|
||||
BaseToken,
|
||||
GlFilteredSearchSuggestion,
|
||||
GlDropdownDivider,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
props: {
|
||||
active: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
required: true,
|
||||
|
@ -34,36 +29,21 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
milestones: this.config.initialMilestones || [],
|
||||
defaultMilestones: this.config.defaultMilestones || DEFAULT_MILESTONES,
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentValue() {
|
||||
return this.value.data.toLowerCase();
|
||||
},
|
||||
activeMilestone() {
|
||||
return this.milestones.find(
|
||||
(milestone) => milestone.title.toLowerCase() === stripQuotes(this.currentValue),
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
active: {
|
||||
immediate: true,
|
||||
handler(newValue) {
|
||||
if (!newValue && !this.milestones.length) {
|
||||
this.fetchMilestoneBySearchTerm(this.value.data);
|
||||
}
|
||||
},
|
||||
defaultMilestones() {
|
||||
return this.config.defaultMilestones || DEFAULT_MILESTONES;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fetchMilestoneBySearchTerm(searchTerm = '') {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
getActiveMilestone(milestones, data) {
|
||||
return milestones.find(
|
||||
(milestone) => milestone.title.toLowerCase() === stripQuotes(data).toLowerCase(),
|
||||
);
|
||||
},
|
||||
fetchMilestones(searchTerm) {
|
||||
this.loading = true;
|
||||
this.config
|
||||
.fetchMilestones(searchTerm)
|
||||
|
@ -71,47 +51,40 @@ export default {
|
|||
const data = Array.isArray(response) ? response : response.data;
|
||||
this.milestones = data.slice().sort(sortMilestonesByDueDate);
|
||||
})
|
||||
.catch(() => createFlash({ message: __('There was a problem fetching milestones.') }))
|
||||
.catch(() => {
|
||||
createFlash({ message: __('There was a problem fetching milestones.') });
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
searchMilestones: debounce(function debouncedSearch({ data }) {
|
||||
this.fetchMilestoneBySearchTerm(data);
|
||||
}, DEBOUNCE_DELAY),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-filtered-search-token
|
||||
<base-token
|
||||
:active="active"
|
||||
:config="config"
|
||||
v-bind="{ ...$props, ...$attrs }"
|
||||
:value="value"
|
||||
:default-suggestions="defaultMilestones"
|
||||
:suggestions="milestones"
|
||||
:suggestions-loading="loading"
|
||||
:get-active-token-value="getActiveMilestone"
|
||||
@fetch-suggestions="fetchMilestones"
|
||||
v-on="$listeners"
|
||||
@input="searchMilestones"
|
||||
>
|
||||
<template #view="{ inputValue }">
|
||||
<span>%{{ activeMilestone ? activeMilestone.title : inputValue }}</span>
|
||||
<template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
|
||||
%{{ activeTokenValue ? activeTokenValue.title : inputValue }}
|
||||
</template>
|
||||
<template #suggestions>
|
||||
<template #suggestions-list="{ suggestions }">
|
||||
<gl-filtered-search-suggestion
|
||||
v-for="milestone in defaultMilestones"
|
||||
:key="milestone.value"
|
||||
:value="milestone.value"
|
||||
v-for="milestone in suggestions"
|
||||
:key="milestone.id"
|
||||
:value="milestone.title"
|
||||
>
|
||||
{{ milestone.text }}
|
||||
{{ milestone.title }}
|
||||
</gl-filtered-search-suggestion>
|
||||
<gl-dropdown-divider v-if="defaultMilestones.length" />
|
||||
<gl-loading-icon v-if="loading" size="sm" />
|
||||
<template v-else>
|
||||
<gl-filtered-search-suggestion
|
||||
v-for="milestone in milestones"
|
||||
:key="milestone.id"
|
||||
:value="milestone.title"
|
||||
>
|
||||
<div>{{ milestone.title }}</div>
|
||||
</gl-filtered-search-suggestion>
|
||||
</template>
|
||||
</template>
|
||||
</gl-filtered-search-token>
|
||||
</base-token>
|
||||
</template>
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
<script>
|
||||
import { GlDropdownDivider, GlFilteredSearchSuggestion, GlFilteredSearchToken } from '@gitlab/ui';
|
||||
import { GlFilteredSearchSuggestion } from '@gitlab/ui';
|
||||
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
|
||||
import { DEFAULT_NONE_ANY, WEIGHT_TOKEN_SUGGESTIONS_SIZE } from '../constants';
|
||||
|
||||
const weights = Array.from(Array(WEIGHT_TOKEN_SUGGESTIONS_SIZE), (_, index) => index.toString());
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlDropdownDivider,
|
||||
BaseToken,
|
||||
GlFilteredSearchSuggestion,
|
||||
GlFilteredSearchToken,
|
||||
},
|
||||
props: {
|
||||
active: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
required: true,
|
||||
|
@ -23,12 +27,19 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
weights,
|
||||
defaultWeights: this.config.defaultWeights || DEFAULT_NONE_ANY,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
defaultWeights() {
|
||||
return this.config.defaultWeights || DEFAULT_NONE_ANY;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateWeights({ data }) {
|
||||
const weight = parseInt(data, 10);
|
||||
getActiveWeight(weightSuggestions, data) {
|
||||
return weightSuggestions.find((weight) => weight === data);
|
||||
},
|
||||
updateWeights(searchTerm) {
|
||||
const weight = parseInt(searchTerm, 10);
|
||||
this.weights = Number.isNaN(weight) ? weights : [String(weight)];
|
||||
},
|
||||
},
|
||||
|
@ -36,24 +47,20 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<gl-filtered-search-token
|
||||
<base-token
|
||||
:active="active"
|
||||
:config="config"
|
||||
v-bind="{ ...$props, ...$attrs }"
|
||||
:value="value"
|
||||
:default-suggestions="defaultWeights"
|
||||
:suggestions="weights"
|
||||
:get-active-token-value="getActiveWeight"
|
||||
@fetch-suggestions="updateWeights"
|
||||
v-on="$listeners"
|
||||
@input="updateWeights"
|
||||
>
|
||||
<template #suggestions>
|
||||
<gl-filtered-search-suggestion
|
||||
v-for="weight in defaultWeights"
|
||||
:key="weight.value"
|
||||
:value="weight.value"
|
||||
>
|
||||
{{ weight.text }}
|
||||
</gl-filtered-search-suggestion>
|
||||
<gl-dropdown-divider v-if="defaultWeights.length" />
|
||||
<gl-filtered-search-suggestion v-for="weight of weights" :key="weight" :value="weight">
|
||||
<template #suggestions-list="{ suggestions }">
|
||||
<gl-filtered-search-suggestion v-for="weight of suggestions" :key="weight" :value="weight">
|
||||
{{ weight }}
|
||||
</gl-filtered-search-suggestion>
|
||||
</template>
|
||||
</gl-filtered-search-token>
|
||||
</base-token>
|
||||
</template>
|
||||
|
|
|
@ -296,7 +296,7 @@ class Group < Namespace
|
|||
end
|
||||
|
||||
def add_users(users, access_level, current_user: nil, expires_at: nil)
|
||||
Members::Groups::CreatorService.add_users( # rubocop:todo CodeReuse/ServiceClass
|
||||
Members::Groups::CreatorService.add_users( # rubocop:disable CodeReuse/ServiceClass
|
||||
self,
|
||||
users,
|
||||
access_level,
|
||||
|
@ -306,7 +306,7 @@ class Group < Namespace
|
|||
end
|
||||
|
||||
def add_user(user, access_level, current_user: nil, expires_at: nil, ldap: false)
|
||||
Members::Groups::CreatorService.new(self, # rubocop:todo CodeReuse/ServiceClass
|
||||
Members::Groups::CreatorService.new(self, # rubocop:disable CodeReuse/ServiceClass
|
||||
user,
|
||||
access_level,
|
||||
current_user: current_user,
|
||||
|
|
|
@ -44,7 +44,7 @@ class ProjectMember < Member
|
|||
project_ids.each do |project_id|
|
||||
project = Project.find(project_id)
|
||||
|
||||
Members::Projects::CreatorService.add_users( # rubocop:todo CodeReuse/ServiceClass
|
||||
Members::Projects::CreatorService.add_users( # rubocop:disable CodeReuse/ServiceClass
|
||||
project,
|
||||
users,
|
||||
access_level,
|
||||
|
|
|
@ -16,7 +16,7 @@ class NotificationSetting < ApplicationRecord
|
|||
validates :user_id, uniqueness: { scope: [:source_type, :source_id],
|
||||
message: "already exists in source",
|
||||
allow_nil: true }
|
||||
validate :owns_notification_email, if: :notification_email_changed?
|
||||
validate :notification_email_verified, if: :notification_email_changed?
|
||||
|
||||
scope :for_groups, -> { where(source_type: 'Namespace') }
|
||||
|
||||
|
@ -110,11 +110,11 @@ class NotificationSetting < ApplicationRecord
|
|||
has_attribute?(event) && !!read_attribute(event)
|
||||
end
|
||||
|
||||
def owns_notification_email
|
||||
def notification_email_verified
|
||||
return if user.temp_oauth_email?
|
||||
return if notification_email.empty?
|
||||
|
||||
errors.add(:notification_email, _("is not an email you own")) unless user.verified_emails.include?(notification_email)
|
||||
errors.add(:notification_email, _("must be an email you have verified")) unless user.verified_emails.include?(notification_email)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ class ProjectTeam
|
|||
end
|
||||
|
||||
def add_users(users, access_level, current_user: nil, expires_at: nil)
|
||||
Members::Projects::CreatorService.add_users( # rubocop:todo CodeReuse/ServiceClass
|
||||
Members::Projects::CreatorService.add_users( # rubocop:disable CodeReuse/ServiceClass
|
||||
project,
|
||||
users,
|
||||
access_level,
|
||||
|
@ -52,7 +52,7 @@ class ProjectTeam
|
|||
end
|
||||
|
||||
def add_user(user, access_level, current_user: nil, expires_at: nil)
|
||||
Members::Projects::CreatorService.new(project, # rubocop:todo CodeReuse/ServiceClass
|
||||
Members::Projects::CreatorService.new(project, # rubocop:disable CodeReuse/ServiceClass
|
||||
user,
|
||||
access_level,
|
||||
current_user: current_user,
|
||||
|
|
|
@ -224,7 +224,7 @@ class User < ApplicationRecord
|
|||
validates :email, confirmation: true
|
||||
validates :notification_email, presence: true
|
||||
validates :notification_email, devise_email: true, if: ->(user) { user.notification_email != user.email }
|
||||
validates :public_email, presence: true, uniqueness: true, devise_email: true, allow_blank: true
|
||||
validates :public_email, uniqueness: true, devise_email: true, allow_blank: true
|
||||
validates :commit_email, devise_email: true, allow_nil: true, if: ->(user) { user.commit_email != user.email }
|
||||
validates :projects_limit,
|
||||
presence: true,
|
||||
|
@ -235,9 +235,9 @@ class User < ApplicationRecord
|
|||
validate :namespace_move_dir_allowed, if: :username_changed?
|
||||
|
||||
validate :unique_email, if: :email_changed?
|
||||
validate :owns_notification_email, if: :notification_email_changed?
|
||||
validate :owns_public_email, if: :public_email_changed?
|
||||
validate :owns_commit_email, if: :commit_email_changed?
|
||||
validate :notification_email_verified, if: :notification_email_changed?
|
||||
validate :public_email_verified, if: :public_email_changed?
|
||||
validate :commit_email_verified, if: :commit_email_changed?
|
||||
validate :signup_email_valid?, on: :create, if: ->(user) { !user.created_by_id }
|
||||
validate :check_username_format, if: :username_changed?
|
||||
|
||||
|
@ -928,22 +928,22 @@ class User < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def owns_notification_email
|
||||
def notification_email_verified
|
||||
return if new_record? || temp_oauth_email?
|
||||
|
||||
errors.add(:notification_email, _("is not an email you own")) unless verified_emails.include?(notification_email)
|
||||
errors.add(:notification_email, _("must be an email you have verified")) unless verified_emails.include?(notification_email)
|
||||
end
|
||||
|
||||
def owns_public_email
|
||||
def public_email_verified
|
||||
return if public_email.blank?
|
||||
|
||||
errors.add(:public_email, _("is not an email you own")) unless verified_emails.include?(public_email)
|
||||
errors.add(:public_email, _("must be an email you have verified")) unless verified_emails.include?(public_email)
|
||||
end
|
||||
|
||||
def owns_commit_email
|
||||
def commit_email_verified
|
||||
return if read_attribute(:commit_email).blank?
|
||||
|
||||
errors.add(:commit_email, _("is not an email you own")) unless verified_emails.include?(commit_email)
|
||||
errors.add(:commit_email, _("must be an email you have verified")) unless verified_emails.include?(commit_email)
|
||||
end
|
||||
|
||||
# Define commit_email-related attribute methods explicitly instead of relying
|
||||
|
|
|
@ -70,10 +70,10 @@
|
|||
= render 'projects/mirrors/disabled_mirror_badge'
|
||||
- if mirror.last_error.present?
|
||||
.badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true', qa_selector: 'mirror_error_badge' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error')
|
||||
%td
|
||||
%td.gl-display-flex
|
||||
- if mirror_settings_enabled
|
||||
.btn-group.mirror-actions-group.float-right{ role: 'group' }
|
||||
%button.js-delete-mirror.qa-delete-mirror.rspec-delete-mirror.btn.btn-icon.gl-button.btn-danger.gl-mr-3{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= sprite_icon('remove')
|
||||
.btn-group.mirror-actions-group{ role: 'group' }
|
||||
- if mirror.ssh_key_auth?
|
||||
= clipboard_button(text: mirror.ssh_public_key, class: 'gl-button btn btn-default', title: _('Copy SSH public key'), qa_selector: 'copy_public_key_button')
|
||||
= render 'shared/remote_mirror_update_button', remote_mirror: mirror
|
||||
%button.js-delete-mirror.qa-delete-mirror.rspec-delete-mirror.btn.btn-icon.gl-button.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= sprite_icon('remove')
|
||||
|
|
|
@ -450,11 +450,11 @@ You can learn more about the different ways Git can undo changes in the
|
|||
### Merge a branch with default branch
|
||||
|
||||
When you are ready to add your changes to
|
||||
the default branch, you `merge` the two together:
|
||||
the default branch, you merge the feature branch into it:
|
||||
|
||||
```shell
|
||||
git checkout <feature-branch>
|
||||
git merge <default-branch>
|
||||
git checkout <default-branch>
|
||||
git merge <feature-branch>
|
||||
```
|
||||
|
||||
In GitLab, you typically use a [merge request](../user/project/merge_requests/) to merge your changes, instead of using the command line.
|
||||
|
|
|
@ -22179,9 +22179,6 @@ msgstr ""
|
|||
msgid "NetworkPolicies|Save changes"
|
||||
msgstr ""
|
||||
|
||||
msgid "NetworkPolicies|Scan Execution"
|
||||
msgstr ""
|
||||
|
||||
msgid "NetworkPolicies|Something went wrong, failed to update policy"
|
||||
msgstr ""
|
||||
|
||||
|
@ -29556,6 +29553,9 @@ msgstr ""
|
|||
msgid "SecurityConfiguration|Vulnerability details and statistics in the merge request"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|All policies"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|An error occurred assigning your security policy project"
|
||||
msgstr ""
|
||||
|
||||
|
@ -29568,6 +29568,9 @@ msgstr ""
|
|||
msgid "SecurityOrchestration|Enforce security for this project. %{linkStart}More information.%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|Network"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|New policy"
|
||||
msgstr ""
|
||||
|
||||
|
@ -29580,6 +29583,12 @@ msgstr ""
|
|||
msgid "SecurityOrchestration|Policy editor"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|Scan Execution"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|Scan execution"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|Security policy project was linked successfully"
|
||||
msgstr ""
|
||||
|
||||
|
@ -29598,9 +29607,6 @@ msgstr ""
|
|||
msgid "SecurityPolicies|+%{count} more"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityPolicies|All policies"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityPolicies|Description"
|
||||
msgstr ""
|
||||
|
||||
|
@ -29613,9 +29619,6 @@ msgstr ""
|
|||
msgid "SecurityPolicies|Latest scan"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityPolicies|Network"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityPolicies|Policy type"
|
||||
msgstr ""
|
||||
|
||||
|
@ -39476,9 +39479,6 @@ msgstr ""
|
|||
msgid "is not allowed. We do not currently support project-level iterations"
|
||||
msgstr ""
|
||||
|
||||
msgid "is not an email you own"
|
||||
msgstr ""
|
||||
|
||||
msgid "is not from an allowed domain."
|
||||
msgstr ""
|
||||
|
||||
|
@ -39917,6 +39917,9 @@ msgstr ""
|
|||
msgid "must be after start"
|
||||
msgstr ""
|
||||
|
||||
msgid "must be an email you have verified"
|
||||
msgstr ""
|
||||
|
||||
msgid "must be greater than start date"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -57,9 +57,9 @@
|
|||
"@babel/preset-env": "^7.10.1",
|
||||
"@gitlab/at.js": "1.5.7",
|
||||
"@gitlab/favicon-overlay": "2.0.0",
|
||||
"@gitlab/svgs": "1.210.0",
|
||||
"@gitlab/svgs": "1.211.0",
|
||||
"@gitlab/tributejs": "1.0.0",
|
||||
"@gitlab/ui": "32.2.0",
|
||||
"@gitlab/ui": "32.2.1",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "6.1.3-2",
|
||||
"@rails/ujs": "6.1.3-2",
|
||||
|
|
27
spec/frontend/content_editor/extensions/inline_diff_spec.js
Normal file
27
spec/frontend/content_editor/extensions/inline_diff_spec.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { inputRegexAddition, inputRegexDeletion } from '~/content_editor/extensions/inline_diff';
|
||||
|
||||
describe('content_editor/extensions/inline_diff', () => {
|
||||
describe.each`
|
||||
inputRegex | description | input | matches
|
||||
${inputRegexAddition} | ${'inputRegexAddition'} | ${'hello{+world+}'} | ${true}
|
||||
${inputRegexAddition} | ${'inputRegexAddition'} | ${'hello{+ world +}'} | ${true}
|
||||
${inputRegexAddition} | ${'inputRegexAddition'} | ${'hello {+ world+}'} | ${true}
|
||||
${inputRegexAddition} | ${'inputRegexAddition'} | ${'{+hello world +}'} | ${true}
|
||||
${inputRegexAddition} | ${'inputRegexAddition'} | ${'{+hello with \nnewline+}'} | ${false}
|
||||
${inputRegexAddition} | ${'inputRegexAddition'} | ${'{+open only'} | ${false}
|
||||
${inputRegexAddition} | ${'inputRegexAddition'} | ${'close only+}'} | ${false}
|
||||
${inputRegexDeletion} | ${'inputRegexDeletion'} | ${'hello{-world-}'} | ${true}
|
||||
${inputRegexDeletion} | ${'inputRegexDeletion'} | ${'hello{- world -}'} | ${true}
|
||||
${inputRegexDeletion} | ${'inputRegexDeletion'} | ${'hello {- world-}'} | ${true}
|
||||
${inputRegexDeletion} | ${'inputRegexDeletion'} | ${'{-hello world -}'} | ${true}
|
||||
${inputRegexDeletion} | ${'inputRegexDeletion'} | ${'{+hello with \nnewline+}'} | ${false}
|
||||
${inputRegexDeletion} | ${'inputRegexDeletion'} | ${'{-open only'} | ${false}
|
||||
${inputRegexDeletion} | ${'inputRegexDeletion'} | ${'close only-}'} | ${false}
|
||||
`('$description', ({ inputRegex, input, matches }) => {
|
||||
it(`${matches ? 'matches' : 'does not match'}: "${input}"`, () => {
|
||||
const match = new RegExp(inputRegex).test(input);
|
||||
|
||||
expect(match).toBe(matches);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -8,6 +8,10 @@
|
|||
markdown: '_emphasized text_'
|
||||
- name: inline_code
|
||||
markdown: '`code`'
|
||||
- name: inline_diff
|
||||
markdown: |-
|
||||
* {-deleted-}
|
||||
* {+added+}
|
||||
- name: subscript
|
||||
markdown: H<sub>2</sub>O
|
||||
- name: superscript
|
||||
|
|
|
@ -61,40 +61,16 @@ describe('BranchToken', () => {
|
|||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('computed', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper = createComponent({ value: { data: mockBranches[0].name } });
|
||||
|
||||
wrapper.setData({
|
||||
branches: mockBranches,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
describe('currentValue', () => {
|
||||
it('returns lowercase string for `value.data`', () => {
|
||||
expect(wrapper.vm.currentValue).toBe('main');
|
||||
});
|
||||
});
|
||||
|
||||
describe('activeBranch', () => {
|
||||
it('returns object for currently present `value.data`', () => {
|
||||
expect(wrapper.vm.activeBranch).toEqual(mockBranches[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = createComponent();
|
||||
});
|
||||
|
||||
describe('fetchBranchBySearchTerm', () => {
|
||||
describe('fetchBranches', () => {
|
||||
it('calls `config.fetchBranches` with provided searchTerm param', () => {
|
||||
jest.spyOn(wrapper.vm.config, 'fetchBranches');
|
||||
|
||||
wrapper.vm.fetchBranchBySearchTerm('foo');
|
||||
wrapper.vm.fetchBranches('foo');
|
||||
|
||||
expect(wrapper.vm.config.fetchBranches).toHaveBeenCalledWith('foo');
|
||||
});
|
||||
|
@ -102,7 +78,7 @@ describe('BranchToken', () => {
|
|||
it('sets response to `branches` when request is succesful', () => {
|
||||
jest.spyOn(wrapper.vm.config, 'fetchBranches').mockResolvedValue({ data: mockBranches });
|
||||
|
||||
wrapper.vm.fetchBranchBySearchTerm('foo');
|
||||
wrapper.vm.fetchBranches('foo');
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(wrapper.vm.branches).toEqual(mockBranches);
|
||||
|
@ -112,7 +88,7 @@ describe('BranchToken', () => {
|
|||
it('calls `createFlash` with flash error message when request fails', () => {
|
||||
jest.spyOn(wrapper.vm.config, 'fetchBranches').mockRejectedValue({});
|
||||
|
||||
wrapper.vm.fetchBranchBySearchTerm('foo');
|
||||
wrapper.vm.fetchBranches('foo');
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
|
@ -124,7 +100,7 @@ describe('BranchToken', () => {
|
|||
it('sets `loading` to false when request completes', () => {
|
||||
jest.spyOn(wrapper.vm.config, 'fetchBranches').mockRejectedValue({});
|
||||
|
||||
wrapper.vm.fetchBranchBySearchTerm('foo');
|
||||
wrapper.vm.fetchBranches('foo');
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(wrapper.vm.loading).toBe(false);
|
||||
|
|
|
@ -67,40 +67,16 @@ describe('EmojiToken', () => {
|
|||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('computed', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper = createComponent({ value: { data: mockEmojis[0].name } });
|
||||
|
||||
wrapper.setData({
|
||||
emojis: mockEmojis,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
describe('currentValue', () => {
|
||||
it('returns lowercase string for `value.data`', () => {
|
||||
expect(wrapper.vm.currentValue).toBe(mockEmojis[0].name);
|
||||
});
|
||||
});
|
||||
|
||||
describe('activeEmoji', () => {
|
||||
it('returns object for currently present `value.data`', () => {
|
||||
expect(wrapper.vm.activeEmoji).toEqual(mockEmojis[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = createComponent();
|
||||
});
|
||||
|
||||
describe('fetchEmojiBySearchTerm', () => {
|
||||
describe('fetchEmojis', () => {
|
||||
it('calls `config.fetchEmojis` with provided searchTerm param', () => {
|
||||
jest.spyOn(wrapper.vm.config, 'fetchEmojis');
|
||||
|
||||
wrapper.vm.fetchEmojiBySearchTerm('foo');
|
||||
wrapper.vm.fetchEmojis('foo');
|
||||
|
||||
expect(wrapper.vm.config.fetchEmojis).toHaveBeenCalledWith('foo');
|
||||
});
|
||||
|
@ -108,7 +84,7 @@ describe('EmojiToken', () => {
|
|||
it('sets response to `emojis` when request is successful', () => {
|
||||
jest.spyOn(wrapper.vm.config, 'fetchEmojis').mockResolvedValue(mockEmojis);
|
||||
|
||||
wrapper.vm.fetchEmojiBySearchTerm('foo');
|
||||
wrapper.vm.fetchEmojis('foo');
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(wrapper.vm.emojis).toEqual(mockEmojis);
|
||||
|
@ -118,7 +94,7 @@ describe('EmojiToken', () => {
|
|||
it('calls `createFlash` with flash error message when request fails', () => {
|
||||
jest.spyOn(wrapper.vm.config, 'fetchEmojis').mockRejectedValue({});
|
||||
|
||||
wrapper.vm.fetchEmojiBySearchTerm('foo');
|
||||
wrapper.vm.fetchEmojis('foo');
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
|
@ -130,7 +106,7 @@ describe('EmojiToken', () => {
|
|||
it('sets `loading` to false when request completes', () => {
|
||||
jest.spyOn(wrapper.vm.config, 'fetchEmojis').mockRejectedValue({});
|
||||
|
||||
wrapper.vm.fetchEmojiBySearchTerm('foo');
|
||||
wrapper.vm.fetchEmojis('foo');
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(wrapper.vm.loading).toBe(false);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { GlFilteredSearchToken, GlFilteredSearchTokenSegment } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createFlash from '~/flash';
|
||||
import IterationToken from '~/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue';
|
||||
import { mockIterationToken } from '../mock_data';
|
||||
|
@ -13,6 +14,7 @@ describe('IterationToken', () => {
|
|||
const createComponent = ({ config = mockIterationToken, value = { data: '' } } = {}) =>
|
||||
mount(IterationToken, {
|
||||
propsData: {
|
||||
active: false,
|
||||
config,
|
||||
value,
|
||||
},
|
||||
|
@ -69,7 +71,7 @@ describe('IterationToken', () => {
|
|||
config: { ...mockIterationToken, fetchIterations: fetchIterationsSpy },
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
await waitForPromises();
|
||||
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
message: 'There was a problem fetching iterations.',
|
||||
|
|
|
@ -14,12 +14,7 @@ import { sortMilestonesByDueDate } from '~/milestones/milestone_utils';
|
|||
import { DEFAULT_MILESTONES } from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
|
||||
|
||||
import {
|
||||
mockMilestoneToken,
|
||||
mockMilestones,
|
||||
mockRegularMilestone,
|
||||
mockEscapedMilestone,
|
||||
} from '../mock_data';
|
||||
import { mockMilestoneToken, mockMilestones, mockRegularMilestone } from '../mock_data';
|
||||
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/milestones/milestone_utils');
|
||||
|
@ -70,37 +65,12 @@ describe('MilestoneToken', () => {
|
|||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('computed', () => {
|
||||
beforeEach(async () => {
|
||||
// Milestone title with spaces is always enclosed in quotations by component.
|
||||
wrapper = createComponent({ value: { data: `"${mockEscapedMilestone.title}"` } });
|
||||
|
||||
wrapper.setData({
|
||||
milestones: mockMilestones,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
describe('currentValue', () => {
|
||||
it('returns lowercase string for `value.data`', () => {
|
||||
expect(wrapper.vm.currentValue).toBe('"5.0 rc1"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('activeMilestone', () => {
|
||||
it('returns object for currently present `value.data`', () => {
|
||||
expect(wrapper.vm.activeMilestone).toEqual(mockEscapedMilestone);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
describe('fetchMilestoneBySearchTerm', () => {
|
||||
describe('fetchMilestones', () => {
|
||||
it('calls `config.fetchMilestones` with provided searchTerm param', () => {
|
||||
jest.spyOn(wrapper.vm.config, 'fetchMilestones');
|
||||
|
||||
wrapper.vm.fetchMilestoneBySearchTerm('foo');
|
||||
wrapper.vm.fetchMilestones('foo');
|
||||
|
||||
expect(wrapper.vm.config.fetchMilestones).toHaveBeenCalledWith('foo');
|
||||
});
|
||||
|
@ -110,7 +80,7 @@ describe('MilestoneToken', () => {
|
|||
data: mockMilestones,
|
||||
});
|
||||
|
||||
wrapper.vm.fetchMilestoneBySearchTerm();
|
||||
wrapper.vm.fetchMilestones();
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(wrapper.vm.milestones).toEqual(mockMilestones);
|
||||
|
@ -121,7 +91,7 @@ describe('MilestoneToken', () => {
|
|||
it('calls `createFlash` with flash error message when request fails', () => {
|
||||
jest.spyOn(wrapper.vm.config, 'fetchMilestones').mockRejectedValue({});
|
||||
|
||||
wrapper.vm.fetchMilestoneBySearchTerm('foo');
|
||||
wrapper.vm.fetchMilestones('foo');
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
|
@ -133,7 +103,7 @@ describe('MilestoneToken', () => {
|
|||
it('sets `loading` to false when request completes', () => {
|
||||
jest.spyOn(wrapper.vm.config, 'fetchMilestones').mockRejectedValue({});
|
||||
|
||||
wrapper.vm.fetchMilestoneBySearchTerm('foo');
|
||||
wrapper.vm.fetchMilestones('foo');
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(wrapper.vm.loading).toBe(false);
|
||||
|
|
|
@ -12,6 +12,7 @@ describe('WeightToken', () => {
|
|||
const createComponent = ({ config = mockWeightToken, value = { data: '' } } = {}) =>
|
||||
mount(WeightToken, {
|
||||
propsData: {
|
||||
active: false,
|
||||
config,
|
||||
value,
|
||||
},
|
||||
|
|
|
@ -704,7 +704,7 @@ RSpec.describe User do
|
|||
user.notification_email = email.email
|
||||
|
||||
expect(user).to be_invalid
|
||||
expect(user.errors[:notification_email]).to include('is not an email you own')
|
||||
expect(user.errors[:notification_email]).to include(_('must be an email you have verified'))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -723,7 +723,7 @@ RSpec.describe User do
|
|||
user.public_email = email.email
|
||||
|
||||
expect(user).to be_invalid
|
||||
expect(user.errors[:public_email]).to include('is not an email you own')
|
||||
expect(user.errors[:public_email]).to include(_('must be an email you have verified'))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
20
yarn.lock
20
yarn.lock
|
@ -898,25 +898,25 @@
|
|||
stylelint-declaration-strict-value "1.7.7"
|
||||
stylelint-scss "3.18.0"
|
||||
|
||||
"@gitlab/svgs@1.210.0":
|
||||
version "1.210.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.210.0.tgz#f2d36a2073eb5059fe48a08130145d740c220efa"
|
||||
integrity sha512-IVALHZKM4QB0djEWJbwgWlpYFD4UF9sqml2SLS5vS/p/FVDeMe7fz7hYOH42xZaZn2iWE6XE9DOVyd9IDNWzPg==
|
||||
"@gitlab/svgs@1.211.0":
|
||||
version "1.211.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.211.0.tgz#0351fa4cc008c4830f366aede535df0a8e63dda6"
|
||||
integrity sha512-fkHJfmKiy7lDwLFQ6z64sbGL+/hDDLzcMTj8O+VBC1xnlBVAIxe2eIs2DZLJcJwgLWncf4Uovp8+CeEfCY12sw==
|
||||
|
||||
"@gitlab/tributejs@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
|
||||
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
|
||||
|
||||
"@gitlab/ui@32.2.0":
|
||||
version "32.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.2.0.tgz#cfff74221c62292bfe329274381352f2335ca493"
|
||||
integrity sha512-ASezPNr97rGmIsSbUWkmY9kDBtat/FSnErQYRx/xGYOf9KkCHzVvYV6s8536abAux7LyIIMv5iwtg2U39wEv9A==
|
||||
"@gitlab/ui@32.2.1":
|
||||
version "32.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.2.1.tgz#e019124af981e8ceffd39f30cf08d315c53d4ac8"
|
||||
integrity sha512-19qe30gHtBG7g7wJy36bvS+ji1puJwVzAwqJOFXAh3axSa0Getyjyl9t5gmfwmZ2HehFTWLlThXppclnJVCEGA==
|
||||
dependencies:
|
||||
"@babel/standalone" "^7.0.0"
|
||||
bootstrap-vue "2.18.1"
|
||||
copy-to-clipboard "^3.0.8"
|
||||
dompurify "^2.3.0"
|
||||
dompurify "^2.3.1"
|
||||
echarts "^4.9.0"
|
||||
highlight.js "^10.6.0"
|
||||
js-beautify "^1.8.8"
|
||||
|
@ -4568,7 +4568,7 @@ domhandler@^4.0.0, domhandler@^4.2.0:
|
|||
dependencies:
|
||||
domelementtype "^2.2.0"
|
||||
|
||||
dompurify@^2.3.0, dompurify@^2.3.1:
|
||||
dompurify@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.1.tgz#a47059ca21fd1212d3c8f71fdea6943b8bfbdf6a"
|
||||
integrity sha512-xGWt+NHAQS+4tpgbOAI08yxW0Pr256Gu/FNE2frZVTbgrBUn8M7tz7/ktS/LZ2MHeGqz6topj0/xY+y8R5FBFw==
|
||||
|
|
Loading…
Reference in a new issue