Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5683a027b7
commit
0a319374e7
84 changed files with 527 additions and 447 deletions
|
@ -369,10 +369,8 @@ db:rollback geo:
|
|||
# EE: Canonical MR pipelines
|
||||
rspec foss-impact:
|
||||
extends:
|
||||
- .rspec-base
|
||||
- .as-if-foss
|
||||
- .rspec-base-pg11-as-if-foss
|
||||
- .rails:rules:ee-mr-only
|
||||
- .use-pg11
|
||||
script:
|
||||
- install_gitlab_gem
|
||||
- run_timed_command "scripts/gitaly-test-build"
|
||||
|
|
|
@ -322,11 +322,8 @@
|
|||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
- <<: *if-merge-request # Always run for MRs since `compile-test-assets as-if-foss` is either needed by `rspec foss-impact` or the `rspec * as-if-foss` jobs.
|
||||
changes: *code-backstage-qa-patterns
|
||||
- <<: *if-merge-request-title-as-if-foss
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
|
||||
.frontend:rules:default-frontend-jobs:
|
||||
rules:
|
||||
|
|
|
@ -9,6 +9,7 @@ import { updateTooltipTitle } from './lib/utils/common_utils';
|
|||
import { isInVueNoteablePage } from './lib/utils/dom_utils';
|
||||
import flash from './flash';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import * as Emoji from '~/emoji';
|
||||
|
||||
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
|
||||
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
|
||||
|
@ -619,7 +620,7 @@ export class AwardsHandler {
|
|||
let awardsHandlerPromise = null;
|
||||
export default function loadAwardsHandler(reload = false) {
|
||||
if (!awardsHandlerPromise || reload) {
|
||||
awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji').then(Emoji => {
|
||||
awardsHandlerPromise = Emoji.initEmojiMap().then(() => {
|
||||
const awardsHandler = new AwardsHandler(Emoji);
|
||||
awardsHandler.bindEvents();
|
||||
return awardsHandler;
|
||||
|
|
|
@ -1,47 +1,69 @@
|
|||
import 'document-register-element';
|
||||
import isEmojiUnicodeSupported from '../emoji/support';
|
||||
import { initEmojiMap, getEmojiInfo, emojiFallbackImageSrc, emojiImageTag } from '../emoji';
|
||||
|
||||
class GlEmoji extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const emojiUnicode = this.textContent.trim();
|
||||
const { name, unicodeVersion, fallbackSrc, fallbackSpriteClass } = this.dataset;
|
||||
this.initialize();
|
||||
}
|
||||
initialize() {
|
||||
let emojiUnicode = this.textContent.trim();
|
||||
const { fallbackSpriteClass, fallbackSrc } = this.dataset;
|
||||
let { name, unicodeVersion } = this.dataset;
|
||||
|
||||
const isEmojiUnicode =
|
||||
this.childNodes &&
|
||||
Array.prototype.every.call(this.childNodes, childNode => childNode.nodeType === 3);
|
||||
const hasImageFallback = fallbackSrc && fallbackSrc.length > 0;
|
||||
const hasCssSpriteFalback = fallbackSpriteClass && fallbackSpriteClass.length > 0;
|
||||
return initEmojiMap().then(() => {
|
||||
if (!unicodeVersion) {
|
||||
const emojiInfo = getEmojiInfo(name);
|
||||
|
||||
if (emojiUnicode && isEmojiUnicode && !isEmojiUnicodeSupported(emojiUnicode, unicodeVersion)) {
|
||||
// CSS sprite fallback takes precedence over image fallback
|
||||
if (hasCssSpriteFalback) {
|
||||
if (!gon.emoji_sprites_css_added && gon.emoji_sprites_css_path) {
|
||||
const emojiSpriteLinkTag = document.createElement('link');
|
||||
emojiSpriteLinkTag.setAttribute('rel', 'stylesheet');
|
||||
emojiSpriteLinkTag.setAttribute('href', gon.emoji_sprites_css_path);
|
||||
document.head.appendChild(emojiSpriteLinkTag);
|
||||
gon.emoji_sprites_css_added = true;
|
||||
if (emojiInfo) {
|
||||
if (name !== emojiInfo.name) {
|
||||
({ name } = emojiInfo);
|
||||
this.dataset.name = emojiInfo.name;
|
||||
}
|
||||
unicodeVersion = emojiInfo.u;
|
||||
this.dataset.unicodeVersion = unicodeVersion;
|
||||
|
||||
emojiUnicode = emojiInfo.e;
|
||||
this.innerHTML = emojiInfo.e;
|
||||
|
||||
this.title = emojiInfo.d;
|
||||
}
|
||||
// IE 11 doesn't like adding multiple at once :(
|
||||
this.classList.add('emoji-icon');
|
||||
this.classList.add(fallbackSpriteClass);
|
||||
} else {
|
||||
import(/* webpackChunkName: 'emoji' */ '../emoji')
|
||||
.then(({ emojiImageTag, emojiFallbackImageSrc }) => {
|
||||
if (hasImageFallback) {
|
||||
this.innerHTML = emojiImageTag(name, fallbackSrc);
|
||||
} else {
|
||||
const src = emojiFallbackImageSrc(name);
|
||||
this.innerHTML = emojiImageTag(name, src);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// do nothing
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const isEmojiUnicode =
|
||||
this.childNodes &&
|
||||
Array.prototype.every.call(this.childNodes, childNode => childNode.nodeType === 3);
|
||||
|
||||
if (
|
||||
emojiUnicode &&
|
||||
isEmojiUnicode &&
|
||||
!isEmojiUnicodeSupported(emojiUnicode, unicodeVersion)
|
||||
) {
|
||||
const hasImageFallback = fallbackSrc && fallbackSrc.length > 0;
|
||||
const hasCssSpriteFallback = fallbackSpriteClass && fallbackSpriteClass.length > 0;
|
||||
|
||||
// CSS sprite fallback takes precedence over image fallback
|
||||
if (hasCssSpriteFallback) {
|
||||
if (!gon.emoji_sprites_css_added && gon.emoji_sprites_css_path) {
|
||||
const emojiSpriteLinkTag = document.createElement('link');
|
||||
emojiSpriteLinkTag.setAttribute('rel', 'stylesheet');
|
||||
emojiSpriteLinkTag.setAttribute('href', gon.emoji_sprites_css_path);
|
||||
document.head.appendChild(emojiSpriteLinkTag);
|
||||
gon.emoji_sprites_css_added = true;
|
||||
}
|
||||
// IE 11 doesn't like adding multiple at once :(
|
||||
this.classList.add('emoji-icon');
|
||||
this.classList.add(fallbackSpriteClass);
|
||||
} else if (hasImageFallback) {
|
||||
this.innerHTML = emojiImageTag(name, fallbackSrc);
|
||||
} else {
|
||||
const src = emojiFallbackImageSrc(name);
|
||||
this.innerHTML = emojiImageTag(name, src);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,63 @@
|
|||
import { uniq } from 'lodash';
|
||||
import emojiMap from 'emojis/digests.json';
|
||||
import emojiAliases from 'emojis/aliases.json';
|
||||
import axios from '../lib/utils/axios_utils';
|
||||
|
||||
export const validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)];
|
||||
import AccessorUtilities from '../lib/utils/accessor';
|
||||
|
||||
let emojiMap = null;
|
||||
let emojiPromise = null;
|
||||
let validEmojiNames = null;
|
||||
|
||||
export const EMOJI_VERSION = '1';
|
||||
|
||||
const isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
|
||||
|
||||
export function initEmojiMap() {
|
||||
emojiPromise =
|
||||
emojiPromise ||
|
||||
new Promise((resolve, reject) => {
|
||||
if (emojiMap) {
|
||||
resolve(emojiMap);
|
||||
} else if (
|
||||
isLocalStorageAvailable &&
|
||||
window.localStorage.getItem('gl-emoji-map-version') === EMOJI_VERSION &&
|
||||
window.localStorage.getItem('gl-emoji-map')
|
||||
) {
|
||||
emojiMap = JSON.parse(window.localStorage.getItem('gl-emoji-map'));
|
||||
validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)];
|
||||
resolve(emojiMap);
|
||||
} else {
|
||||
// We load the JSON file direct from the server
|
||||
// because it can't be loaded from a CDN due to
|
||||
// cross domain problems with JSON
|
||||
axios
|
||||
.get(`${gon.relative_url_root || ''}/-/emojis/${EMOJI_VERSION}/emojis.json`)
|
||||
.then(({ data }) => {
|
||||
emojiMap = data;
|
||||
validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)];
|
||||
resolve(emojiMap);
|
||||
if (isLocalStorageAvailable) {
|
||||
window.localStorage.setItem('gl-emoji-map-version', EMOJI_VERSION);
|
||||
window.localStorage.setItem('gl-emoji-map', JSON.stringify(emojiMap));
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return emojiPromise;
|
||||
}
|
||||
|
||||
export function normalizeEmojiName(name) {
|
||||
return Object.prototype.hasOwnProperty.call(emojiAliases, name) ? emojiAliases[name] : name;
|
||||
}
|
||||
|
||||
export function getValidEmojiNames() {
|
||||
return validEmojiNames;
|
||||
}
|
||||
|
||||
export function isEmojiNameValid(name) {
|
||||
return validEmojiNames.indexOf(name) >= 0;
|
||||
}
|
||||
|
@ -36,8 +86,8 @@ export function getEmojiCategoryMap() {
|
|||
};
|
||||
Object.keys(emojiMap).forEach(name => {
|
||||
const emoji = emojiMap[name];
|
||||
if (emojiCategoryMap[emoji.category]) {
|
||||
emojiCategoryMap[emoji.category].push(name);
|
||||
if (emojiCategoryMap[emoji.c]) {
|
||||
emojiCategoryMap[emoji.c].push(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -58,8 +108,9 @@ export function getEmojiInfo(query) {
|
|||
}
|
||||
|
||||
export function emojiFallbackImageSrc(inputName) {
|
||||
const { name, digest } = getEmojiInfo(inputName);
|
||||
return `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${digest}.png`;
|
||||
const { name } = getEmojiInfo(inputName);
|
||||
return `${gon.asset_host || ''}${gon.relative_url_root ||
|
||||
''}/-/emojis/${EMOJI_VERSION}/${name}.png`;
|
||||
}
|
||||
|
||||
export function emojiImageTag(name, src) {
|
||||
|
@ -67,36 +118,17 @@ export function emojiImageTag(name, src) {
|
|||
}
|
||||
|
||||
export function glEmojiTag(inputName, options) {
|
||||
const opts = { sprite: false, forceFallback: false, ...options };
|
||||
const { name, ...emojiInfo } = getEmojiInfo(inputName);
|
||||
|
||||
const fallbackImageSrc = emojiFallbackImageSrc(name);
|
||||
const opts = { sprite: false, ...options };
|
||||
const name = normalizeEmojiName(inputName);
|
||||
const fallbackSpriteClass = `emoji-${name}`;
|
||||
|
||||
const classList = [];
|
||||
if (opts.forceFallback && opts.sprite) {
|
||||
classList.push('emoji-icon');
|
||||
classList.push(fallbackSpriteClass);
|
||||
}
|
||||
const classAttribute = classList.length > 0 ? `class="${classList.join(' ')}"` : '';
|
||||
const fallbackSpriteAttribute = opts.sprite
|
||||
? `data-fallback-sprite-class="${fallbackSpriteClass}"`
|
||||
: '';
|
||||
let contents = emojiInfo.moji;
|
||||
if (opts.forceFallback && !opts.sprite) {
|
||||
contents = emojiImageTag(name, fallbackImageSrc);
|
||||
}
|
||||
|
||||
return `
|
||||
<gl-emoji
|
||||
${classAttribute}
|
||||
data-name="${name}"
|
||||
data-fallback-src="${fallbackImageSrc}"
|
||||
${fallbackSpriteAttribute}
|
||||
data-unicode-version="${emojiInfo.unicodeVersion}"
|
||||
title="${emojiInfo.description}"
|
||||
>
|
||||
${contents}
|
||||
</gl-emoji>
|
||||
data-name="${name}"></gl-emoji>
|
||||
`;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import DropdownUtils from '~/filtered_search/dropdown_utils';
|
|||
import Flash from '~/flash';
|
||||
import UsersCache from '~/lib/utils/users_cache';
|
||||
import { __ } from '~/locale';
|
||||
import * as Emoji from '~/emoji';
|
||||
|
||||
export default class VisualTokenValue {
|
||||
constructor(tokenValue, tokenType, tokenOperator) {
|
||||
|
@ -137,18 +138,13 @@ export default class VisualTokenValue {
|
|||
const element = tokenValueElement;
|
||||
const value = this.tokenValue;
|
||||
|
||||
return (
|
||||
import(/* webpackChunkName: 'emoji' */ '../emoji')
|
||||
.then(Emoji => {
|
||||
if (!Emoji.isEmojiNameValid(value)) {
|
||||
return;
|
||||
}
|
||||
return Emoji.initEmojiMap().then(() => {
|
||||
if (!Emoji.isEmojiNameValid(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
container.dataset.originalValue = value;
|
||||
element.innerHTML = Emoji.glEmojiTag(value);
|
||||
})
|
||||
// ignore error and leave emoji name in the search bar
|
||||
.catch(() => {})
|
||||
);
|
||||
container.dataset.originalValue = value;
|
||||
element.innerHTML = Emoji.glEmojiTag(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import SidebarMediator from '~/sidebar/sidebar_mediator';
|
|||
import glRegexp from './lib/utils/regexp';
|
||||
import AjaxCache from './lib/utils/ajax_cache';
|
||||
import { spriteIcon } from './lib/utils/common_utils';
|
||||
import * as Emoji from '~/emoji';
|
||||
|
||||
function sanitize(str) {
|
||||
return str.replace(/<(?:.|\n)*?>/gm, '');
|
||||
|
@ -586,14 +587,12 @@ class GfmAutoComplete {
|
|||
if (this.cachedData[at]) {
|
||||
this.loadData($input, at, this.cachedData[at]);
|
||||
} else if (GfmAutoComplete.atTypeMap[at] === 'emojis') {
|
||||
import(/* webpackChunkName: 'emoji' */ './emoji')
|
||||
.then(({ validEmojiNames, glEmojiTag }) => {
|
||||
this.loadData($input, at, validEmojiNames);
|
||||
GfmAutoComplete.glEmojiTag = glEmojiTag;
|
||||
Emoji.initEmojiMap()
|
||||
.then(() => {
|
||||
this.loadData($input, at, Emoji.getValidEmojiNames());
|
||||
GfmAutoComplete.glEmojiTag = Emoji.glEmojiTag;
|
||||
})
|
||||
.catch(() => {
|
||||
this.isLoadingData[at] = false;
|
||||
});
|
||||
.catch(() => {});
|
||||
} else if (dataSource) {
|
||||
AjaxCache.retrieve(dataSource, true)
|
||||
.then(data => {
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
|
||||
import TerminalSyncStatusSafe from './terminal_sync/terminal_sync_status_safe.vue';
|
||||
import { getFileEOL } from '../utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlLink,
|
||||
TerminalSyncStatusSafe,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['activeFile']),
|
||||
activeFileEOL() {
|
||||
|
@ -19,12 +24,14 @@ export default {
|
|||
<template>
|
||||
<div class="ide-status-list d-flex">
|
||||
<template v-if="activeFile">
|
||||
<div class="ide-status-file">{{ activeFile.name }}</div>
|
||||
<div class="ide-status-file">{{ activeFileEOL }}</div>
|
||||
<div v-if="!activeFile.binary" class="ide-status-file">
|
||||
{{ activeFile.editorRow }}:{{ activeFile.editorColumn }}
|
||||
<div>
|
||||
<gl-link v-gl-tooltip.hover :href="activeFile.permalink" :title="__('Open in file view')">
|
||||
{{ activeFile.name }}
|
||||
</gl-link>
|
||||
</div>
|
||||
<div class="ide-status-file">{{ activeFile.fileLanguage }}</div>
|
||||
<div>{{ activeFileEOL }}</div>
|
||||
<div v-if="!activeFile.binary">{{ activeFile.editorRow }}:{{ activeFile.editorColumn }}</div>
|
||||
<div>{{ activeFile.fileLanguage }}</div>
|
||||
</template>
|
||||
<terminal-sync-status-safe />
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
// TODO: need to move this component to graphql - https://gitlab.com/gitlab-org/gitlab/-/issues/221246
|
||||
import { escape, isNumber } from 'lodash';
|
||||
import { GlLink, GlTooltipDirective as GlTooltip, GlSprintf, GlLabel } from '@gitlab/ui';
|
||||
import { GlLink, GlTooltipDirective as GlTooltip, GlSprintf, GlLabel, GlIcon } from '@gitlab/ui';
|
||||
import {
|
||||
dateInWords,
|
||||
formatDate,
|
||||
|
@ -18,7 +18,6 @@ import {
|
|||
import { sprintf, __ } from '~/locale';
|
||||
import initUserPopovers from '~/user_popovers';
|
||||
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
|
||||
import { isScopedLabel } from '~/lib/utils/common_utils';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
|
@ -28,10 +27,10 @@ export default {
|
|||
openedAgo: __('opened %{timeAgoString} by %{user}'),
|
||||
},
|
||||
components: {
|
||||
Icon,
|
||||
IssueAssignees,
|
||||
GlLink,
|
||||
GlLabel,
|
||||
GlIcon,
|
||||
GlSprintf,
|
||||
},
|
||||
directives: {
|
||||
|
@ -153,14 +152,14 @@ export default {
|
|||
value: this.issuable.upvotes,
|
||||
title: __('Upvotes'),
|
||||
class: 'js-upvotes',
|
||||
faicon: 'fa-thumbs-up',
|
||||
icon: 'thumb-up',
|
||||
},
|
||||
{
|
||||
key: 'downvotes',
|
||||
value: this.issuable.downvotes,
|
||||
title: __('Downvotes'),
|
||||
class: 'js-downvotes',
|
||||
faicon: 'fa-thumbs-down',
|
||||
icon: 'thumb-down',
|
||||
},
|
||||
];
|
||||
},
|
||||
|
@ -294,7 +293,7 @@ export default {
|
|||
:title="__('Weight')"
|
||||
class="d-none d-sm-inline-block js-weight"
|
||||
>
|
||||
<icon name="weight" class="align-text-bottom" />
|
||||
<gl-icon name="weight" class="align-text-bottom" />
|
||||
{{ issuable.weight }}
|
||||
</span>
|
||||
</div>
|
||||
|
@ -318,11 +317,10 @@ export default {
|
|||
v-if="meta.value"
|
||||
:key="meta.key"
|
||||
v-gl-tooltip
|
||||
:class="['d-none d-sm-inline-block ml-2', meta.class]"
|
||||
:class="['d-none d-sm-inline-block ml-2 vertical-align-middle', meta.class]"
|
||||
:title="meta.title"
|
||||
>
|
||||
<icon v-if="meta.icon" :name="meta.icon" />
|
||||
<i v-else :class="['fa', meta.faicon]"></i>
|
||||
<gl-icon v-if="meta.icon" :name="meta.icon" />
|
||||
{{ meta.value }}
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
@ -4,6 +4,7 @@ import emojiRegex from 'emoji-regex';
|
|||
import createFlash from '~/flash';
|
||||
import EmojiMenu from './emoji_menu';
|
||||
import { __ } from '~/locale';
|
||||
import * as Emoji from '~/emoji';
|
||||
|
||||
const defaultStatusEmoji = 'speech_balloon';
|
||||
|
||||
|
@ -55,8 +56,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
}
|
||||
});
|
||||
|
||||
import(/* webpackChunkName: 'emoji' */ '~/emoji')
|
||||
.then(Emoji => {
|
||||
Emoji.initEmojiMap()
|
||||
.then(() => {
|
||||
const emojiMenu = new EmojiMenu(
|
||||
Emoji,
|
||||
toggleEmojiMenuButtonSelector,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import TestSuiteTable from './test_suite_table.vue';
|
||||
import TestSummary from './test_summary.vue';
|
||||
|
@ -14,9 +14,10 @@ export default {
|
|||
TestSummaryTable,
|
||||
},
|
||||
computed: {
|
||||
...mapState(['isLoading', 'selectedSuite', 'testReports']),
|
||||
...mapState(['isLoading', 'selectedSuiteIndex', 'testReports']),
|
||||
...mapGetters(['getSelectedSuite']),
|
||||
showSuite() {
|
||||
return this.selectedSuite.total_count > 0;
|
||||
return this.selectedSuiteIndex !== null;
|
||||
},
|
||||
showTests() {
|
||||
const { test_suites: testSuites = [] } = this.testReports;
|
||||
|
@ -27,12 +28,12 @@ export default {
|
|||
this.fetchSummary();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['fetchSummary', 'setSelectedSuite', 'removeSelectedSuite']),
|
||||
...mapActions(['fetchSummary', 'setSelectedSuiteIndex', 'removeSelectedSuiteIndex']),
|
||||
summaryBackClick() {
|
||||
this.removeSelectedSuite();
|
||||
this.removeSelectedSuiteIndex();
|
||||
},
|
||||
summaryTableRowClick(suite) {
|
||||
this.setSelectedSuite(suite);
|
||||
summaryTableRowClick(index) {
|
||||
this.setSelectedSuiteIndex(index);
|
||||
},
|
||||
beforeEnterTransition() {
|
||||
document.documentElement.style.overflowX = 'hidden';
|
||||
|
@ -60,7 +61,7 @@ export default {
|
|||
@after-leave="afterLeaveTransition"
|
||||
>
|
||||
<div v-if="showSuite" key="detail" class="w-100 position-absolute slide-enter-to-element">
|
||||
<test-summary :report="selectedSuite" show-back @on-back-click="summaryBackClick" />
|
||||
<test-summary :report="getSelectedSuite" show-back @on-back-click="summaryBackClick" />
|
||||
|
||||
<test-suite-table />
|
||||
</div>
|
||||
|
|
|
@ -27,8 +27,8 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
tableRowClick(suite) {
|
||||
this.$emit('row-click', suite);
|
||||
tableRowClick(index) {
|
||||
this.$emit('row-click', index);
|
||||
},
|
||||
},
|
||||
maxShownRows: 20,
|
||||
|
@ -82,7 +82,7 @@ export default {
|
|||
:class="{
|
||||
'gl-responsive-table-row-clickable cursor-pointer': !testSuite.suite_error,
|
||||
}"
|
||||
@click="tableRowClick(testSuite)"
|
||||
@click="tableRowClick(index)"
|
||||
>
|
||||
<div class="table-section section-25">
|
||||
<div role="rowheader" class="table-mobile-header font-weight-bold">
|
||||
|
|
|
@ -35,8 +35,10 @@ export const fetchFullReport = ({ state, commit, dispatch }) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const setSelectedSuite = ({ commit }, data) => commit(types.SET_SELECTED_SUITE, data);
|
||||
export const removeSelectedSuite = ({ commit }) => commit(types.SET_SELECTED_SUITE, {});
|
||||
export const setSelectedSuiteIndex = ({ commit }, data) =>
|
||||
commit(types.SET_SELECTED_SUITE_INDEX, data);
|
||||
export const removeSelectedSuiteIndex = ({ commit }) =>
|
||||
commit(types.SET_SELECTED_SUITE_INDEX, null);
|
||||
export const toggleLoading = ({ commit }) => commit(types.TOGGLE_LOADING);
|
||||
|
||||
// prevent babel-plugin-rewire from generating an invalid default during karma tests
|
||||
|
|
|
@ -9,14 +9,12 @@ export const getTestSuites = state => {
|
|||
}));
|
||||
};
|
||||
|
||||
export const getSelectedSuite = state =>
|
||||
state.testReports?.test_suites?.[state.selectedSuiteIndex] || {};
|
||||
|
||||
export const getSuiteTests = state => {
|
||||
const { selectedSuite } = state;
|
||||
|
||||
if (selectedSuite.test_cases) {
|
||||
return selectedSuite.test_cases.sort(sortTestCases).map(addIconStatus);
|
||||
}
|
||||
|
||||
return [];
|
||||
const { test_cases: testCases = [] } = getSelectedSuite(state);
|
||||
return testCases.sort(sortTestCases).map(addIconStatus);
|
||||
};
|
||||
|
||||
// prevent babel-plugin-rewire from generating an invalid default during karma tests
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export const SET_REPORTS = 'SET_REPORTS';
|
||||
export const SET_SELECTED_SUITE = 'SET_SELECTED_SUITE';
|
||||
export const SET_SELECTED_SUITE_INDEX = 'SET_SELECTED_SUITE_INDEX';
|
||||
export const SET_SUMMARY = 'SET_SUMMARY';
|
||||
export const TOGGLE_LOADING = 'TOGGLE_LOADING';
|
||||
|
|
|
@ -5,8 +5,8 @@ export default {
|
|||
Object.assign(state, { testReports });
|
||||
},
|
||||
|
||||
[types.SET_SELECTED_SUITE](state, selectedSuite) {
|
||||
Object.assign(state, { selectedSuite });
|
||||
[types.SET_SELECTED_SUITE_INDEX](state, selectedSuiteIndex) {
|
||||
Object.assign(state, { selectedSuiteIndex });
|
||||
},
|
||||
|
||||
[types.SET_SUMMARY](state, summary) {
|
||||
|
|
|
@ -2,7 +2,7 @@ export default ({ fullReportEndpoint = '', summaryEndpoint = '' }) => ({
|
|||
summaryEndpoint,
|
||||
fullReportEndpoint,
|
||||
testReports: {},
|
||||
selectedSuite: {},
|
||||
selectedSuiteIndex: null,
|
||||
summary: {},
|
||||
isLoading: false,
|
||||
});
|
||||
|
|
|
@ -5,7 +5,6 @@ import FileTable from './table/index.vue';
|
|||
import getRefMixin from '../mixins/get_ref';
|
||||
import filesQuery from '../queries/files.query.graphql';
|
||||
import projectPathQuery from '../queries/project_path.query.graphql';
|
||||
import vueFileListLfsBadgeQuery from '../queries/vue_file_list_lfs_badge.query.graphql';
|
||||
import FilePreview from './preview/index.vue';
|
||||
import { readmeFile } from '../utils/readme';
|
||||
|
||||
|
@ -21,9 +20,6 @@ export default {
|
|||
projectPath: {
|
||||
query: projectPathQuery,
|
||||
},
|
||||
vueFileListLfsBadge: {
|
||||
query: vueFileListLfsBadgeQuery,
|
||||
},
|
||||
},
|
||||
props: {
|
||||
path: {
|
||||
|
@ -47,7 +43,6 @@ export default {
|
|||
blobs: [],
|
||||
},
|
||||
isLoadingFiles: false,
|
||||
vueFileListLfsBadge: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -82,7 +77,6 @@ export default {
|
|||
path: this.path || '/',
|
||||
nextPageCursor: this.nextPageCursor,
|
||||
pageSize: PAGE_SIZE,
|
||||
vueLfsEnabled: this.vueFileListLfsBadge,
|
||||
},
|
||||
})
|
||||
.then(({ data }) => {
|
||||
|
|
|
@ -24,7 +24,6 @@ export default function setupVueRepositoryList() {
|
|||
projectShortPath,
|
||||
ref,
|
||||
escapedRef,
|
||||
vueFileListLfsBadge: gon.features?.vueFileListLfsBadge || false,
|
||||
commits: [],
|
||||
},
|
||||
});
|
||||
|
|
|
@ -14,7 +14,6 @@ query Files(
|
|||
$ref: String!
|
||||
$pageSize: Int!
|
||||
$nextPageCursor: String
|
||||
$vueLfsEnabled: Boolean = false
|
||||
) {
|
||||
project(fullPath: $projectPath) {
|
||||
repository {
|
||||
|
@ -47,7 +46,7 @@ query Files(
|
|||
node {
|
||||
...TreeEntry
|
||||
webPath
|
||||
lfsOid @include(if: $vueLfsEnabled)
|
||||
lfsOid
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
query VueFileListLfsBadge {
|
||||
vueFileListLfsBadge @client
|
||||
}
|
|
@ -8,6 +8,7 @@ import { __, s__ } from '~/locale';
|
|||
import Api from '~/api';
|
||||
import eventHub from './event_hub';
|
||||
import EmojiMenuInModal from './emoji_menu_in_modal';
|
||||
import * as Emoji from '~/emoji';
|
||||
|
||||
const emojiMenuClass = 'js-modal-status-emoji-menu';
|
||||
|
||||
|
@ -64,8 +65,8 @@ export default {
|
|||
const emojiAutocomplete = new GfmAutoComplete();
|
||||
emojiAutocomplete.setup($(this.$refs.statusMessageField), { emojis: true });
|
||||
|
||||
import(/* webpackChunkName: 'emoji' */ '~/emoji')
|
||||
.then(Emoji => {
|
||||
Emoji.initEmojiMap()
|
||||
.then(() => {
|
||||
if (this.emoji) {
|
||||
this.emojiTag = Emoji.glEmojiTag(this.emoji);
|
||||
}
|
||||
|
|
|
@ -251,10 +251,6 @@ $ide-commit-header-height: 48px;
|
|||
padding-left: $gl-padding;
|
||||
}
|
||||
}
|
||||
|
||||
.ide-status-file {
|
||||
text-align: right;
|
||||
}
|
||||
// Not great, but this is to deal with our current output
|
||||
.multi-file-preview-holder {
|
||||
height: 100%;
|
||||
|
|
|
@ -11,10 +11,6 @@ class Projects::RefsController < Projects::ApplicationController
|
|||
before_action :assign_ref_vars
|
||||
before_action :authorize_download_code!
|
||||
|
||||
before_action only: [:logs_tree] do
|
||||
push_frontend_feature_flag(:vue_file_list_lfs_badge, default_enabled: true)
|
||||
end
|
||||
|
||||
def switch
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
|
|
|
@ -15,10 +15,6 @@ class Projects::TreeController < Projects::ApplicationController
|
|||
before_action :authorize_download_code!
|
||||
before_action :authorize_edit_tree!, only: [:create_dir]
|
||||
|
||||
before_action only: [:show] do
|
||||
push_frontend_feature_flag(:vue_file_list_lfs_badge, default_enabled: true)
|
||||
end
|
||||
|
||||
def show
|
||||
return render_404 unless @commit
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ module SearchHelper
|
|||
}).html_safe
|
||||
end
|
||||
|
||||
# Overriden in EE
|
||||
# Overridden in EE
|
||||
def search_blob_title(project, path)
|
||||
path
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# of directly having a repository, like project or snippet.
|
||||
#
|
||||
# It also includes `Referable`, therefore the method
|
||||
# `to_reference` should be overriden in case the object
|
||||
# `to_reference` should be overridden in case the object
|
||||
# needs any special behavior.
|
||||
module HasRepository
|
||||
extend ActiveSupport::Concern
|
||||
|
|
|
@ -65,7 +65,7 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
|
|||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# Will be overidden in EE
|
||||
# Will be overridden in EE
|
||||
def environments_cluster_path(cluster)
|
||||
nil
|
||||
end
|
||||
|
|
|
@ -19,7 +19,7 @@ module Users
|
|||
private
|
||||
|
||||
def after_block_hook(user)
|
||||
# overriden by EE module
|
||||
# overridden by EE module
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,12 +11,12 @@
|
|||
|
||||
- if upvotes > 0
|
||||
%li.issuable-upvotes.d-none.d-sm-block.has-tooltip{ title: _('Upvotes') }
|
||||
= icon('thumbs-up')
|
||||
= sprite_icon('thumb-up', size: 16, css_class: "vertical-align-middle")
|
||||
= upvotes
|
||||
|
||||
- if downvotes > 0
|
||||
%li.issuable-downvotes.d-none.d-sm-block.has-tooltip{ title: _('Downvotes') }
|
||||
= icon('thumbs-down')
|
||||
= sprite_icon('thumb-down', size: 16, css_class: "vertical-align-middle")
|
||||
= downvotes
|
||||
|
||||
%li.issuable-comments.d-none.d-sm-block
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Normalize the 'thumb-up', 'thumb-down' icon.
|
||||
merge_request: 35988
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Move file link to bottom in Web IDE
|
||||
merge_request: 35847
|
||||
author:
|
||||
type: changed
|
|
@ -17,6 +17,7 @@ module Gitlab
|
|||
class Application < Rails::Application
|
||||
require_dependency Rails.root.join('lib/gitlab')
|
||||
require_dependency Rails.root.join('lib/gitlab/utils')
|
||||
require_dependency Rails.root.join('lib/gitlab/action_cable/config')
|
||||
require_dependency Rails.root.join('lib/gitlab/redis/wrapper')
|
||||
require_dependency Rails.root.join('lib/gitlab/redis/cache')
|
||||
require_dependency Rails.root.join('lib/gitlab/redis/queues')
|
||||
|
|
|
@ -49,8 +49,6 @@ Rails.application.configure do
|
|||
# Do not log asset requests
|
||||
config.assets.quiet = true
|
||||
|
||||
config.allow_concurrency = Gitlab::Runtime.multi_threaded?
|
||||
|
||||
# BetterErrors live shell (REPL) on every stack frame
|
||||
BetterErrors::Middleware.allow_ip!("127.0.0.1/0")
|
||||
|
||||
|
|
|
@ -77,6 +77,4 @@ Rails.application.configure do
|
|||
config.action_mailer.raise_delivery_errors = true
|
||||
|
||||
config.eager_load = true
|
||||
|
||||
config.allow_concurrency = Gitlab::Runtime.multi_threaded?
|
||||
end
|
||||
|
|
|
@ -54,4 +54,8 @@ Rails.application.configure do
|
|||
config.logger = ActiveSupport::TaggedLogging.new(Logger.new(nil))
|
||||
config.log_level = :fatal
|
||||
end
|
||||
|
||||
# Mount the ActionCable Engine in-app so that we don't have to spawn another Puma
|
||||
# process for feature specs
|
||||
ENV['ACTION_CABLE_IN_APP'] = 'true'
|
||||
end
|
||||
|
|
|
@ -1105,11 +1105,6 @@ production: &base
|
|||
# host: localhost
|
||||
# port: 3808
|
||||
|
||||
## ActionCable settings
|
||||
action_cable:
|
||||
# Number of threads used to process ActionCable connection callbacks and channel actions
|
||||
# worker_pool_size: 4
|
||||
|
||||
## Monitoring
|
||||
# Built in monitoring settings
|
||||
monitoring:
|
||||
|
|
|
@ -737,12 +737,6 @@ Settings.webpack.dev_server['enabled'] ||= false
|
|||
Settings.webpack.dev_server['host'] ||= 'localhost'
|
||||
Settings.webpack.dev_server['port'] ||= 3808
|
||||
|
||||
#
|
||||
# ActionCable settings
|
||||
#
|
||||
Settings['action_cable'] ||= Settingslogic.new({})
|
||||
Settings.action_cable['worker_pool_size'] ||= 4
|
||||
|
||||
#
|
||||
# Monitoring settings
|
||||
#
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
require 'action_cable/subscription_adapter/redis'
|
||||
|
||||
Rails.application.configure do
|
||||
# We only mount the ActionCable engine in tests where we run it in-app
|
||||
# For other environments, we run it on a standalone Puma server
|
||||
config.action_cable.mount_path = Rails.env.test? ? '/-/cable' : nil
|
||||
# Mount the ActionCable engine when in-app mode is enabled
|
||||
config.action_cable.mount_path = Gitlab::ActionCable::Config.in_app? ? '/-/cable' : nil
|
||||
|
||||
config.action_cable.url = Gitlab::Utils.append_path(Gitlab.config.gitlab.relative_url_root, '/-/cable')
|
||||
config.action_cable.worker_pool_size = Gitlab.config.action_cable.worker_pool_size
|
||||
config.action_cable.worker_pool_size = Gitlab::ActionCable::Config.worker_pool_size
|
||||
end
|
||||
|
||||
# https://github.com/rails/rails/blob/bb5ac1623e8de08c1b7b62b1368758f0d3bb6379/actioncable/lib/action_cable/subscription_adapter/redis.rb#L18
|
||||
|
|
|
@ -4,13 +4,15 @@ group: Portfolio Management
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||
---
|
||||
|
||||
# Epic Issues API **(ULTIMATE)**
|
||||
# Epic Issues API **(PREMIUM)**
|
||||
|
||||
Every API call to epic_issues must be authenticated.
|
||||
|
||||
If a user is not a member of a group and the group is private, a `GET` request on that group will result to a `404` status code.
|
||||
If a user is not a member of a group and the group is private, a `GET` request on that group will
|
||||
result in a `404` status code.
|
||||
|
||||
Epics are available only in Ultimate. If epics feature is not available a `403` status code will be returned.
|
||||
Epics are available only in GitLab [Premium and higher](https://about.gitlab.com/pricing/).
|
||||
If the Epics feature is not available, a `403` status code will be returned.
|
||||
|
||||
## List issues for an epic
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@ Every API call to `epic_links` must be authenticated.
|
|||
|
||||
If a user is not a member of a group and the group is private, a `GET` request on that group will result to a `404` status code.
|
||||
|
||||
Epics are available only in the [Ultimate/Gold tier](https://about.gitlab.com/pricing/). If the epics feature is not available, a `403` status code will be returned.
|
||||
Multi-level Epics are available only in GitLab [Ultimate/Gold](https://about.gitlab.com/pricing/).
|
||||
If the Multi-level Epics feature is not available, a `403` status code will be returned.
|
||||
|
||||
## List epics related to a given epic
|
||||
|
||||
|
|
|
@ -620,7 +620,7 @@ the `weight` parameter:
|
|||
}
|
||||
```
|
||||
|
||||
Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) will additionally see
|
||||
Users on GitLab [Premium](https://about.gitlab.com/pricing/) will additionally see
|
||||
the `epic` property:
|
||||
|
||||
```javascript
|
||||
|
@ -669,8 +669,8 @@ POST /projects/:id/issues
|
|||
| `merge_request_to_resolve_discussions_of` | integer | no | The IID of a merge request in which to resolve all issues. This will fill the issue with a default description and mark all discussions as resolved. When passing a description or title, these values will take precedence over the default values.|
|
||||
| `discussion_to_resolve` | string | no | The ID of a discussion to resolve. This will fill in the issue with a default description and mark the discussion as resolved. Use in combination with `merge_request_to_resolve_discussions_of`. |
|
||||
| `weight` **(STARTER)** | integer | no | The weight of the issue. Valid values are greater than or equal to 0. |
|
||||
| `epic_id` **(ULTIMATE)** | integer | no | ID of the epic to add the issue to. Valid values are greater than or equal to 0. |
|
||||
| `epic_iid` **(ULTIMATE)** | integer | no | IID of the epic to add the issue to. Valid values are greater than or equal to 0. (deprecated, [will be removed in version 5](https://gitlab.com/gitlab-org/gitlab/-/issues/35157)) |
|
||||
| `epic_id` **(PREMIUM)** | integer | no | ID of the epic to add the issue to. Valid values are greater than or equal to 0. |
|
||||
| `epic_iid` **(PREMIUM)** | integer | no | IID of the epic to add the issue to. Valid values are greater than or equal to 0. (deprecated, [will be removed in version 5](https://gitlab.com/gitlab-org/gitlab/-/issues/35157)) |
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/4/issues?title=Issues%20with%20auth&labels=bug"
|
||||
|
@ -787,8 +787,8 @@ PUT /projects/:id/issues/:issue_iid
|
|||
| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, for example `2016-03-11` |
|
||||
| `weight` **(STARTER)** | integer | no | The weight of the issue. Valid values are greater than or equal to 0. 0 |
|
||||
| `discussion_locked` | boolean | no | Flag indicating if the issue's discussion is locked. If the discussion is locked only project members can add or edit comments. |
|
||||
| `epic_id` **(ULTIMATE)** | integer | no | ID of the epic to add the issue to. Valid values are greater than or equal to 0. |
|
||||
| `epic_iid` **(ULTIMATE)** | integer | no | IID of the epic to add the issue to. Valid values are greater than or equal to 0. (deprecated, [will be removed in version 5](https://gitlab.com/gitlab-org/gitlab/-/issues/35157)) |
|
||||
| `epic_id` **(PREMIUM)** | integer | no | ID of the epic to add the issue to. Valid values are greater than or equal to 0. |
|
||||
| `epic_iid` **(PREMIUM)** | integer | no | IID of the epic to add the issue to. Valid values are greater than or equal to 0. (deprecated, [will be removed in version 5](https://gitlab.com/gitlab-org/gitlab/-/issues/35157)) |
|
||||
|
||||
```shell
|
||||
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/4/issues/85?state_event=close"
|
||||
|
|
17
lib/gitlab/action_cable/config.rb
Normal file
17
lib/gitlab/action_cable/config.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module ActionCable
|
||||
class Config
|
||||
class << self
|
||||
def in_app?
|
||||
Gitlab::Utils.to_boolean(ENV.fetch('ACTION_CABLE_IN_APP', false))
|
||||
end
|
||||
|
||||
def worker_pool_size
|
||||
ENV.fetch('ACTION_CABLE_WORKER_POOL_SIZE', 4).to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -93,7 +93,7 @@ module Gitlab
|
|||
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(error, @context.sentry_payload)
|
||||
end
|
||||
|
||||
# Overriden in EE
|
||||
# Overridden in EE
|
||||
def rescue_errors
|
||||
RESCUE_ERRORS
|
||||
end
|
||||
|
|
|
@ -42,7 +42,7 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
# Overriden in Gitlab::WikiFileFinder
|
||||
# Overridden in Gitlab::WikiFileFinder
|
||||
def search_paths(query)
|
||||
repository.search_files_by_name(query, ref)
|
||||
end
|
||||
|
|
|
@ -37,7 +37,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def puma?
|
||||
!!defined?(::Puma) && !defined?(ACTION_CABLE_SERVER)
|
||||
!!defined?(::Puma)
|
||||
end
|
||||
|
||||
# For unicorn, we need to check for actual server instances to avoid false positives.
|
||||
|
@ -70,11 +70,11 @@ module Gitlab
|
|||
end
|
||||
|
||||
def web_server?
|
||||
puma? || unicorn? || action_cable?
|
||||
puma? || unicorn?
|
||||
end
|
||||
|
||||
def action_cable?
|
||||
!!defined?(ACTION_CABLE_SERVER)
|
||||
web_server? && (!!defined?(ACTION_CABLE_SERVER) || Gitlab::ActionCable::Config.in_app?)
|
||||
end
|
||||
|
||||
def multi_threaded?
|
||||
|
@ -82,19 +82,21 @@ module Gitlab
|
|||
end
|
||||
|
||||
def max_threads
|
||||
main_thread = 1
|
||||
threads = 1 # main thread
|
||||
|
||||
if action_cable?
|
||||
Gitlab::Application.config.action_cable.worker_pool_size
|
||||
elsif puma?
|
||||
Puma.cli_config.options[:max_threads]
|
||||
if puma?
|
||||
threads += Puma.cli_config.options[:max_threads]
|
||||
elsif sidekiq?
|
||||
# An extra thread for the poller in Sidekiq Cron:
|
||||
# https://github.com/ondrejbartas/sidekiq-cron#under-the-hood
|
||||
Sidekiq.options[:concurrency] + 1
|
||||
else
|
||||
0
|
||||
end + main_thread
|
||||
threads += Sidekiq.options[:concurrency] + 1
|
||||
end
|
||||
|
||||
if action_cable?
|
||||
threads += Gitlab::ActionCable::Config.worker_pool_size
|
||||
end
|
||||
|
||||
threads
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2862,6 +2862,9 @@ msgstr ""
|
|||
msgid "ApprovalRule|e.g. QA, Security, etc."
|
||||
msgstr ""
|
||||
|
||||
msgid "Approvals|Section: %section"
|
||||
msgstr ""
|
||||
|
||||
msgid "Approve"
|
||||
msgstr ""
|
||||
|
||||
|
@ -15931,6 +15934,9 @@ msgstr ""
|
|||
msgid "Open in Xcode"
|
||||
msgstr ""
|
||||
|
||||
msgid "Open in file view"
|
||||
msgstr ""
|
||||
|
||||
msgid "Open issues"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ RSpec.describe "Admin::Users" do
|
|||
end
|
||||
|
||||
describe "view extra user information" do
|
||||
it 'shows the user popover on hover', :js, :quarantine do
|
||||
it 'shows the user popover on hover', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/11290' do
|
||||
expect(page).not_to have_selector('#__BV_popover_1__')
|
||||
|
||||
first_user_link = page.first('.js-user-link')
|
||||
|
|
|
@ -24,7 +24,7 @@ RSpec.describe 'Thread Comments Commit', :js do
|
|||
expect(page).to have_css('.js-note-emoji')
|
||||
end
|
||||
|
||||
it 'adds award to the correct note', quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/207973' do
|
||||
it 'adds award to the correct note', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/207973' do
|
||||
find("#note_#{commit_discussion_note2.id} .js-note-emoji").click
|
||||
first('.emoji-menu .js-emoji-btn').click
|
||||
|
||||
|
|
|
@ -28,8 +28,8 @@ RSpec.describe 'issuable list', :js do
|
|||
it "counts upvotes, downvotes and notes count for each #{issuable_type.to_s.humanize}" do
|
||||
visit_issuable_list(issuable_type)
|
||||
|
||||
expect(first('.fa-thumbs-up').find(:xpath, '..')).to have_content(1)
|
||||
expect(first('.fa-thumbs-down').find(:xpath, '..')).to have_content(1)
|
||||
expect(first('.issuable-upvotes')).to have_content(1)
|
||||
expect(first('.issuable-downvotes')).to have_content(1)
|
||||
expect(first('.fa-comments').find(:xpath, '..')).to have_content(2)
|
||||
end
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ RSpec.describe 'Search bar', :js do
|
|||
expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: original_size)
|
||||
end
|
||||
|
||||
it 'resets the dropdown filters', :quarantine do
|
||||
it 'resets the dropdown filters', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/9985' do
|
||||
filtered_search.click
|
||||
|
||||
hint_offset = get_left_style(find('#js-dropdown-hint')['style'])
|
||||
|
|
|
@ -28,7 +28,7 @@ RSpec.describe 'Issue Detail', :js do
|
|||
visit project_issue_path(project, issue)
|
||||
end
|
||||
|
||||
it 'encodes the description to prevent xss issues', quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/207951' do
|
||||
it 'encodes the description to prevent xss issues', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/207951' do
|
||||
page.within('.issuable-details .detail-page-description') do
|
||||
image = find('img.js-lazy-loaded')
|
||||
|
||||
|
|
|
@ -195,7 +195,7 @@ RSpec.describe 'Issue Sidebar' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'creating a project label', :js, :quarantine do
|
||||
context 'creating a project label', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27992' do
|
||||
before do
|
||||
page.within('.block.labels') do
|
||||
click_link 'Create project'
|
||||
|
|
|
@ -31,7 +31,7 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do
|
|||
end
|
||||
|
||||
# In order to improve tests performance, all UI checks are placed in this test.
|
||||
it 'shows elements', :quarantine do
|
||||
it 'shows elements', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27993' do
|
||||
button_create_merge_request = find('.js-create-merge-request')
|
||||
button_toggle_dropdown = find('.create-mr-dropdown-wrap .dropdown-toggle')
|
||||
|
||||
|
@ -141,7 +141,7 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do
|
|||
visit project_issue_path(project, issue)
|
||||
end
|
||||
|
||||
it 'disables the create branch button', :quarantine do
|
||||
it 'disables the create branch button', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27985' do
|
||||
expect(page).to have_css('.create-mr-dropdown-wrap .unavailable:not(.hidden)')
|
||||
expect(page).to have_css('.create-mr-dropdown-wrap .available.hidden', visible: false)
|
||||
expect(page).to have_content /Related merge requests/
|
||||
|
|
|
@ -16,7 +16,7 @@ RSpec.describe 'User interacts with awards' do
|
|||
visit(project_issue_path(project, issue))
|
||||
end
|
||||
|
||||
it 'toggles the thumbsup award emoji', :quarantine do
|
||||
it 'toggles the thumbsup award emoji', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27959' do
|
||||
page.within('.awards') do
|
||||
thumbsup = page.first('.award-control')
|
||||
thumbsup.click
|
||||
|
@ -77,7 +77,7 @@ RSpec.describe 'User interacts with awards' do
|
|||
end
|
||||
end
|
||||
|
||||
it 'shows the list of award emoji categories', :quarantine do
|
||||
it 'shows the list of award emoji categories', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27991' do
|
||||
page.within('.awards') do
|
||||
page.find('.js-add-award').click
|
||||
end
|
||||
|
|
|
@ -23,7 +23,7 @@ RSpec.describe 'Labels Hierarchy', :js do
|
|||
end
|
||||
|
||||
shared_examples 'assigning labels from sidebar' do
|
||||
it 'can assign all ancestors labels', :quarantine do
|
||||
it 'can assign all ancestors labels', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27952' do
|
||||
[grandparent_group_label, parent_group_label, project_label_1].each do |label|
|
||||
page.within('.block.labels') do
|
||||
find('.edit-link').click
|
||||
|
|
|
@ -88,7 +88,7 @@ RSpec.describe 'Merge request > User creates image diff notes', :js do
|
|||
create_image_diff_note
|
||||
end
|
||||
|
||||
it 'shows indicator and avatar badges, and allows collapsing/expanding the discussion notes', :quarantine do
|
||||
it 'shows indicator and avatar badges, and allows collapsing/expanding the discussion notes', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27950' do
|
||||
indicator = find('.js-image-badge', match: :first)
|
||||
badge = find('.user-avatar-link .badge', match: :first)
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ RSpec.describe 'Merge request > User posts diff notes', :js do
|
|||
end
|
||||
|
||||
context 'with an old line on the left and a new line on the right' do
|
||||
it 'allows commenting on the left side', quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/199050' do
|
||||
it 'allows commenting on the left side', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/199050' do
|
||||
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'left')
|
||||
end
|
||||
|
||||
|
@ -56,7 +56,7 @@ RSpec.describe 'Merge request > User posts diff notes', :js do
|
|||
end
|
||||
|
||||
context 'with an unchanged line on the left and an unchanged line on the right' do
|
||||
it 'allows commenting on the left side', quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/196826' do
|
||||
it 'allows commenting on the left side', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/196826' do
|
||||
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]', match: :first).find(:xpath, '..'), 'left')
|
||||
end
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ RSpec.describe 'Merge request > User sees diff', :js do
|
|||
end
|
||||
|
||||
context 'as user who needs to fork' do
|
||||
it 'shows fork/cancel confirmation', :sidekiq_might_not_need_inline, quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/196749' do
|
||||
it 'shows fork/cancel confirmation', :sidekiq_might_not_need_inline, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/196749' do
|
||||
sign_in(user)
|
||||
visit diffs_project_merge_request_path(project, merge_request)
|
||||
|
||||
|
|
|
@ -188,8 +188,7 @@ RSpec.describe 'User comments on a diff', :js do
|
|||
end
|
||||
|
||||
context 'multiple suggestions in expanded lines' do
|
||||
# https://gitlab.com/gitlab-org/gitlab/issues/38277
|
||||
it 'suggestions are appliable', :quarantine do
|
||||
it 'suggestions are appliable', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/38277' do
|
||||
diff_file = merge_request.diffs(paths: ['files/ruby/popen.rb']).diff_files.first
|
||||
hash = Digest::SHA1.hexdigest(diff_file.file_path)
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ RSpec.describe 'User visits the profile preferences page' do
|
|||
end
|
||||
|
||||
describe 'User changes their language', :js do
|
||||
it 'creates a flash message', :quarantine do
|
||||
it 'creates a flash message', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/31404' do
|
||||
select2('en', from: '#user_preferred_language')
|
||||
click_button 'Save'
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ RSpec.describe 'Projects > Members > Member leaves project' do
|
|||
expect(project.users.exists?(user.id)).to be_falsey
|
||||
end
|
||||
|
||||
it 'user leaves project by url param', :js, :quarantine do
|
||||
it 'user leaves project by url param', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/35925' do
|
||||
visit project_path(project, leave: 1)
|
||||
|
||||
page.accept_confirm
|
||||
|
|
|
@ -98,7 +98,7 @@ RSpec.shared_examples 'Signup' do
|
|||
expect(page).to have_content("Invalid input, please avoid emojis")
|
||||
end
|
||||
|
||||
it 'shows a pending message if the username availability is being fetched', :quarantine do
|
||||
it 'shows a pending message if the username availability is being fetched', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/31484' do
|
||||
fill_in 'new_user_username', with: 'new-user'
|
||||
|
||||
expect(find('.username > .validation-pending')).not_to have_css '.hide'
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export default () => {};
|
|
@ -1,13 +1,17 @@
|
|||
import $ from 'jquery';
|
||||
import Cookies from 'js-cookie';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import loadAwardsHandler from '~/awards_handler';
|
||||
import '~/lib/utils/common_utils';
|
||||
import waitForPromises from './helpers/wait_for_promises';
|
||||
import { EMOJI_VERSION } from '~/emoji';
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gon = window.gon || {};
|
||||
|
||||
let openAndWaitForEmojiMenu;
|
||||
let mock;
|
||||
let awardsHandler = null;
|
||||
const urlRoot = gon.relative_url_root;
|
||||
|
||||
|
@ -24,8 +28,13 @@ const lazyAssert = (done, assertFn) => {
|
|||
};
|
||||
|
||||
describe('AwardsHandler', () => {
|
||||
const emojiData = getJSONFixture('emojis/emojis.json');
|
||||
preloadFixtures('snippets/show.html');
|
||||
|
||||
beforeEach(done => {
|
||||
mock = new MockAdapter(axios);
|
||||
mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData);
|
||||
|
||||
loadFixtures('snippets/show.html');
|
||||
loadAwardsHandler(true)
|
||||
.then(obj => {
|
||||
|
@ -58,6 +67,8 @@ describe('AwardsHandler', () => {
|
|||
// restore original url root value
|
||||
gon.relative_url_root = urlRoot;
|
||||
|
||||
mock.restore();
|
||||
|
||||
// Undo what we did to the shared <body>
|
||||
$('body').removeAttr('data-page');
|
||||
|
||||
|
|
110
spec/frontend/behaviors/gl_emoji_spec.js
Normal file
110
spec/frontend/behaviors/gl_emoji_spec.js
Normal file
|
@ -0,0 +1,110 @@
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { initEmojiMap, EMOJI_VERSION } from '~/emoji';
|
||||
import installGlEmojiElement from '~/behaviors/gl_emoji';
|
||||
|
||||
import * as EmojiUnicodeSupport from '~/emoji/support';
|
||||
import waitForPromises from 'jest/helpers/wait_for_promises';
|
||||
|
||||
jest.mock('~/emoji/support');
|
||||
|
||||
describe('gl_emoji', () => {
|
||||
let mock;
|
||||
const emojiData = getJSONFixture('emojis/emojis.json');
|
||||
|
||||
beforeAll(() => {
|
||||
jest.spyOn(EmojiUnicodeSupport, 'default').mockReturnValue(true);
|
||||
installGlEmojiElement();
|
||||
});
|
||||
|
||||
function markupToDomElement(markup) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = markup;
|
||||
document.body.appendChild(div);
|
||||
|
||||
return div.firstElementChild;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData);
|
||||
|
||||
return initEmojiMap().catch(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
describe.each([
|
||||
[
|
||||
'bomb emoji just with name attribute',
|
||||
'<gl-emoji data-name="bomb"></gl-emoji>',
|
||||
'<gl-emoji data-name="bomb" data-unicode-version="6.0" title="bomb">💣</gl-emoji>',
|
||||
'<gl-emoji data-name="bomb" data-unicode-version="6.0" title="bomb"><img class="emoji" title=":bomb:" alt=":bomb:" src="/-/emojis/1/bomb.png" width="20" height="20" align="absmiddle"></gl-emoji>',
|
||||
],
|
||||
[
|
||||
'bomb emoji with name attribute and unicode version',
|
||||
'<gl-emoji data-name="bomb" data-unicode-version="6.0">💣</gl-emoji>',
|
||||
'<gl-emoji data-name="bomb" data-unicode-version="6.0">💣</gl-emoji>',
|
||||
'<gl-emoji data-name="bomb" data-unicode-version="6.0"><img class="emoji" title=":bomb:" alt=":bomb:" src="/-/emojis/1/bomb.png" width="20" height="20" align="absmiddle"></gl-emoji>',
|
||||
],
|
||||
[
|
||||
'bomb emoji with sprite fallback',
|
||||
'<gl-emoji data-fallback-sprite-class="emoji-bomb" data-name="bomb"></gl-emoji>',
|
||||
'<gl-emoji data-fallback-sprite-class="emoji-bomb" data-name="bomb" data-unicode-version="6.0" title="bomb">💣</gl-emoji>',
|
||||
'<gl-emoji data-fallback-sprite-class="emoji-bomb" data-name="bomb" data-unicode-version="6.0" title="bomb" class="emoji-icon emoji-bomb">💣</gl-emoji>',
|
||||
],
|
||||
[
|
||||
'bomb emoji with image fallback',
|
||||
'<gl-emoji data-fallback-src="/bomb.png" data-name="bomb"></gl-emoji>',
|
||||
'<gl-emoji data-fallback-src="/bomb.png" data-name="bomb" data-unicode-version="6.0" title="bomb">💣</gl-emoji>',
|
||||
'<gl-emoji data-fallback-src="/bomb.png" data-name="bomb" data-unicode-version="6.0" title="bomb"><img class="emoji" title=":bomb:" alt=":bomb:" src="/bomb.png" width="20" height="20" align="absmiddle"></gl-emoji>',
|
||||
],
|
||||
[
|
||||
'invalid emoji',
|
||||
'<gl-emoji data-name="invalid_emoji"></gl-emoji>',
|
||||
'<gl-emoji data-name="grey_question" data-unicode-version="6.0" title="white question mark ornament">❔</gl-emoji>',
|
||||
'<gl-emoji data-name="grey_question" data-unicode-version="6.0" title="white question mark ornament"><img class="emoji" title=":grey_question:" alt=":grey_question:" src="/-/emojis/1/grey_question.png" width="20" height="20" align="absmiddle"></gl-emoji>',
|
||||
],
|
||||
])('%s', (name, markup, withEmojiSupport, withoutEmojiSupport) => {
|
||||
it(`renders correctly with emoji support`, async () => {
|
||||
jest.spyOn(EmojiUnicodeSupport, 'default').mockReturnValue(true);
|
||||
const glEmojiElement = markupToDomElement(markup);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(glEmojiElement.outerHTML).toBe(withEmojiSupport);
|
||||
});
|
||||
|
||||
it(`renders correctly without emoji support`, async () => {
|
||||
jest.spyOn(EmojiUnicodeSupport, 'default').mockReturnValue(false);
|
||||
const glEmojiElement = markupToDomElement(markup);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(glEmojiElement.outerHTML).toBe(withoutEmojiSupport);
|
||||
});
|
||||
});
|
||||
|
||||
it('Adds sprite CSS if emojis are not supported', async () => {
|
||||
const testPath = '/test-path.css';
|
||||
jest.spyOn(EmojiUnicodeSupport, 'default').mockReturnValue(false);
|
||||
window.gon.emoji_sprites_css_path = testPath;
|
||||
|
||||
expect(document.head.querySelector(`link[href="${testPath}"]`)).toBe(null);
|
||||
expect(window.gon.emoji_sprites_css_added).toBeFalsy();
|
||||
|
||||
markupToDomElement(
|
||||
'<gl-emoji data-fallback-sprite-class="emoji-bomb" data-name="bomb"></gl-emoji>',
|
||||
);
|
||||
await waitForPromises();
|
||||
|
||||
expect(document.head.querySelector(`link[href="${testPath}"]`).outerHTML).toBe(
|
||||
'<link rel="stylesheet" href="/test-path.css">',
|
||||
);
|
||||
expect(window.gon.emoji_sprites_css_added).toBe(true);
|
||||
});
|
||||
});
|
|
@ -1,4 +1,6 @@
|
|||
import { glEmojiTag } from '~/emoji';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { initEmojiMap, glEmojiTag, EMOJI_VERSION } from '~/emoji';
|
||||
import isEmojiUnicodeSupported, {
|
||||
isFlagEmoji,
|
||||
isRainbowFlagEmoji,
|
||||
|
@ -7,6 +9,7 @@ import isEmojiUnicodeSupported, {
|
|||
isHorceRacingSkinToneComboEmoji,
|
||||
isPersonZwjEmoji,
|
||||
} from '~/emoji/support/is_emoji_unicode_supported';
|
||||
import { trimText } from 'helpers/text_helper';
|
||||
|
||||
const emptySupportMap = {
|
||||
personZwj: false,
|
||||
|
@ -50,77 +53,28 @@ const emojiFixtureMap = {
|
|||
},
|
||||
};
|
||||
|
||||
function markupToDomElement(markup) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = markup;
|
||||
return div.firstElementChild;
|
||||
}
|
||||
|
||||
function testGlEmojiImageFallback(element, name, src) {
|
||||
expect(element.tagName.toLowerCase()).toBe('img');
|
||||
expect(element.getAttribute('src')).toBe(src);
|
||||
expect(element.getAttribute('title')).toBe(`:${name}:`);
|
||||
expect(element.getAttribute('alt')).toBe(`:${name}:`);
|
||||
}
|
||||
|
||||
const defaults = {
|
||||
forceFallback: false,
|
||||
sprite: false,
|
||||
};
|
||||
|
||||
function testGlEmojiElement(element, name, unicodeVersion, unicodeMoji, options = {}) {
|
||||
const opts = { ...defaults, ...options };
|
||||
expect(element.tagName.toLowerCase()).toBe('gl-emoji');
|
||||
expect(element.dataset.name).toBe(name);
|
||||
expect(element.dataset.fallbackSrc.length).toBeGreaterThan(0);
|
||||
expect(element.dataset.unicodeVersion).toBe(unicodeVersion);
|
||||
|
||||
const fallbackSpriteClass = `emoji-${name}`;
|
||||
if (opts.sprite) {
|
||||
expect(element.dataset.fallbackSpriteClass).toBe(fallbackSpriteClass);
|
||||
}
|
||||
|
||||
if (opts.forceFallback && opts.sprite) {
|
||||
expect(element.getAttribute('class')).toBe(`emoji-icon ${fallbackSpriteClass}`);
|
||||
}
|
||||
|
||||
if (opts.forceFallback && !opts.sprite) {
|
||||
// Check for image fallback
|
||||
testGlEmojiImageFallback(element.firstElementChild, name, element.dataset.fallbackSrc);
|
||||
} else {
|
||||
// Otherwise make sure things are still unicode text
|
||||
expect(element.textContent.trim()).toBe(unicodeMoji);
|
||||
}
|
||||
}
|
||||
|
||||
describe('gl_emoji', () => {
|
||||
let mock;
|
||||
const emojiData = getJSONFixture('emojis/emojis.json');
|
||||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData);
|
||||
|
||||
return initEmojiMap().catch(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
describe('glEmojiTag', () => {
|
||||
it('bomb emoji', () => {
|
||||
const emojiKey = 'bomb';
|
||||
const markup = glEmojiTag(emojiFixtureMap[emojiKey].name);
|
||||
const glEmojiElement = markupToDomElement(markup);
|
||||
testGlEmojiElement(
|
||||
glEmojiElement,
|
||||
emojiFixtureMap[emojiKey].name,
|
||||
emojiFixtureMap[emojiKey].unicodeVersion,
|
||||
emojiFixtureMap[emojiKey].moji,
|
||||
);
|
||||
});
|
||||
|
||||
it('bomb emoji with image fallback', () => {
|
||||
const emojiKey = 'bomb';
|
||||
const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, {
|
||||
forceFallback: true,
|
||||
});
|
||||
const glEmojiElement = markupToDomElement(markup);
|
||||
testGlEmojiElement(
|
||||
glEmojiElement,
|
||||
emojiFixtureMap[emojiKey].name,
|
||||
emojiFixtureMap[emojiKey].unicodeVersion,
|
||||
emojiFixtureMap[emojiKey].moji,
|
||||
{
|
||||
forceFallback: true,
|
||||
},
|
||||
expect(trimText(markup)).toMatchInlineSnapshot(
|
||||
`"<gl-emoji data-name=\\"bomb\\"></gl-emoji>"`,
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -129,65 +83,8 @@ describe('gl_emoji', () => {
|
|||
const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, {
|
||||
sprite: true,
|
||||
});
|
||||
const glEmojiElement = markupToDomElement(markup);
|
||||
testGlEmojiElement(
|
||||
glEmojiElement,
|
||||
emojiFixtureMap[emojiKey].name,
|
||||
emojiFixtureMap[emojiKey].unicodeVersion,
|
||||
emojiFixtureMap[emojiKey].moji,
|
||||
{
|
||||
sprite: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('bomb emoji with sprite fallback', () => {
|
||||
const emojiKey = 'bomb';
|
||||
const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, {
|
||||
forceFallback: true,
|
||||
sprite: true,
|
||||
});
|
||||
const glEmojiElement = markupToDomElement(markup);
|
||||
testGlEmojiElement(
|
||||
glEmojiElement,
|
||||
emojiFixtureMap[emojiKey].name,
|
||||
emojiFixtureMap[emojiKey].unicodeVersion,
|
||||
emojiFixtureMap[emojiKey].moji,
|
||||
{
|
||||
forceFallback: true,
|
||||
sprite: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('question mark when invalid emoji name given', () => {
|
||||
const name = 'invalid_emoji';
|
||||
const emojiKey = 'grey_question';
|
||||
const markup = glEmojiTag(name);
|
||||
const glEmojiElement = markupToDomElement(markup);
|
||||
testGlEmojiElement(
|
||||
glEmojiElement,
|
||||
emojiFixtureMap[emojiKey].name,
|
||||
emojiFixtureMap[emojiKey].unicodeVersion,
|
||||
emojiFixtureMap[emojiKey].moji,
|
||||
);
|
||||
});
|
||||
|
||||
it('question mark with image fallback when invalid emoji name given', () => {
|
||||
const name = 'invalid_emoji';
|
||||
const emojiKey = 'grey_question';
|
||||
const markup = glEmojiTag(name, {
|
||||
forceFallback: true,
|
||||
});
|
||||
const glEmojiElement = markupToDomElement(markup);
|
||||
testGlEmojiElement(
|
||||
glEmojiElement,
|
||||
emojiFixtureMap[emojiKey].name,
|
||||
emojiFixtureMap[emojiKey].unicodeVersion,
|
||||
emojiFixtureMap[emojiKey].moji,
|
||||
{
|
||||
forceFallback: true,
|
||||
},
|
||||
expect(trimText(markup)).toMatchInlineSnapshot(
|
||||
`"<gl-emoji data-fallback-sprite-class=\\"emoji-bomb\\" data-name=\\"bomb\\"></gl-emoji>"`,
|
||||
);
|
||||
});
|
||||
});
|
17
spec/frontend/fixtures/emojis.rb
Normal file
17
spec/frontend/fixtures/emojis.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Emojis (JavaScript fixtures)', type: :request do
|
||||
include JavaScriptFixturesHelpers
|
||||
|
||||
before(:all) do
|
||||
clean_frontend_fixtures('emojis/')
|
||||
end
|
||||
|
||||
it 'emojis/emojis.json' do |example|
|
||||
get '/-/emojis/1/emojis.json'
|
||||
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
|
@ -1,5 +1,6 @@
|
|||
import Vuex from 'vuex';
|
||||
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||
import { GlLink } from '@gitlab/ui';
|
||||
import IdeStatusList from '~/ide/components/ide_status_list.vue';
|
||||
import TerminalSyncStatusSafe from '~/ide/components/terminal_sync/terminal_sync_status_safe.vue';
|
||||
|
||||
|
@ -9,6 +10,7 @@ const TEST_FILE = {
|
|||
editorColumn: 23,
|
||||
fileLanguage: 'markdown',
|
||||
content: 'abc\nndef',
|
||||
permalink: '/lorem.md',
|
||||
};
|
||||
|
||||
const localVue = createLocalVue();
|
||||
|
@ -19,6 +21,7 @@ describe('ide/components/ide_status_list', () => {
|
|||
let store;
|
||||
let wrapper;
|
||||
|
||||
const findLink = () => wrapper.find(GlLink);
|
||||
const createComponent = (options = {}) => {
|
||||
store = new Vuex.Store({
|
||||
getters: {
|
||||
|
@ -51,8 +54,9 @@ describe('ide/components/ide_status_list', () => {
|
|||
createComponent();
|
||||
});
|
||||
|
||||
it('shows file name', () => {
|
||||
expect(wrapper.text()).toContain(TEST_FILE.name);
|
||||
it('shows a link to the file that contains the file name', () => {
|
||||
expect(findLink().attributes('href')).toBe(TEST_FILE.permalink);
|
||||
expect(findLink().text()).toBe(TEST_FILE.name);
|
||||
});
|
||||
|
||||
it('shows file eol', () => {
|
||||
|
|
|
@ -369,7 +369,7 @@ describe('Dashboard Panel', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('it is overriden when a datazoom event is received', () => {
|
||||
it('it is overridden when a datazoom event is received', () => {
|
||||
state.logsPath = mockLogsPath;
|
||||
state.timeRange = mockTimeRange;
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ describe('Actions TestReports Store', () => {
|
|||
fullReportEndpoint,
|
||||
summaryEndpoint,
|
||||
testReports: {},
|
||||
selectedSuite: {},
|
||||
selectedSuite: null,
|
||||
summary: {},
|
||||
};
|
||||
|
||||
|
@ -101,28 +101,28 @@ describe('Actions TestReports Store', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('set selected suite', () => {
|
||||
const selectedSuite = testReports.test_suites[0];
|
||||
describe('set selected suite index', () => {
|
||||
const selectedSuiteIndex = 0;
|
||||
|
||||
it('sets selectedSuite', done => {
|
||||
it('sets selectedSuiteIndex', done => {
|
||||
testAction(
|
||||
actions.setSelectedSuite,
|
||||
selectedSuite,
|
||||
actions.setSelectedSuiteIndex,
|
||||
selectedSuiteIndex,
|
||||
state,
|
||||
[{ type: types.SET_SELECTED_SUITE, payload: selectedSuite }],
|
||||
[{ type: types.SET_SELECTED_SUITE_INDEX, payload: selectedSuiteIndex }],
|
||||
[],
|
||||
done,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove selected suite', () => {
|
||||
it('sets selectedSuite to {}', done => {
|
||||
describe('remove selected suite index', () => {
|
||||
it('sets selectedSuiteIndex to null', done => {
|
||||
testAction(
|
||||
actions.removeSelectedSuite,
|
||||
actions.removeSelectedSuiteIndex,
|
||||
{},
|
||||
state,
|
||||
[{ type: types.SET_SELECTED_SUITE, payload: {} }],
|
||||
[{ type: types.SET_SELECTED_SUITE_INDEX, payload: null }],
|
||||
[],
|
||||
done,
|
||||
);
|
||||
|
|
|
@ -9,12 +9,12 @@ describe('Getters TestReports Store', () => {
|
|||
|
||||
const defaultState = {
|
||||
testReports,
|
||||
selectedSuite: testReports.test_suites[0],
|
||||
selectedSuiteIndex: 0,
|
||||
};
|
||||
|
||||
const emptyState = {
|
||||
testReports: {},
|
||||
selectedSuite: {},
|
||||
selectedSuite: null,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -47,6 +47,17 @@ describe('Getters TestReports Store', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getSelectedSuite', () => {
|
||||
it('should return the selected suite', () => {
|
||||
setupState();
|
||||
|
||||
const selectedSuite = getters.getSelectedSuite(state);
|
||||
const expected = testReports.test_suites[state.selectedSuiteIndex];
|
||||
|
||||
expect(selectedSuite).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSuiteTests', () => {
|
||||
it('should return the test cases inside the suite', () => {
|
||||
setupState();
|
||||
|
|
|
@ -10,7 +10,7 @@ describe('Mutations TestReports Store', () => {
|
|||
const defaultState = {
|
||||
endpoint: '',
|
||||
testReports: {},
|
||||
selectedSuite: {},
|
||||
selectedSuite: null,
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
|
@ -27,12 +27,12 @@ describe('Mutations TestReports Store', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('set selected suite', () => {
|
||||
it('should set selectedSuite', () => {
|
||||
const selectedSuite = testReports.test_suites[0];
|
||||
mutations[types.SET_SELECTED_SUITE](mockState, selectedSuite);
|
||||
describe('set selected suite index', () => {
|
||||
it('should set selectedSuiteIndex', () => {
|
||||
const selectedSuiteIndex = 0;
|
||||
mutations[types.SET_SELECTED_SUITE_INDEX](mockState, selectedSuiteIndex);
|
||||
|
||||
expect(mockState.selectedSuite).toEqual(selectedSuite);
|
||||
expect(mockState.selectedSuiteIndex).toEqual(selectedSuiteIndex);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
|
|||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import TestReports from '~/pipelines/components/test_reports/test_reports.vue';
|
||||
import * as actions from '~/pipelines/stores/test_reports/actions';
|
||||
import * as getters from '~/pipelines/stores/test_reports/getters';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
@ -29,6 +30,7 @@ describe('Test reports app', () => {
|
|||
...actions,
|
||||
fetchSummary: () => {},
|
||||
},
|
||||
getters,
|
||||
});
|
||||
|
||||
wrapper = shallowMount(TestReports, {
|
||||
|
|
|
@ -28,7 +28,10 @@ describe('Test reports suite table', () => {
|
|||
const createComponent = (suite = testSuite) => {
|
||||
store = new Vuex.Store({
|
||||
state: {
|
||||
selectedSuite: suite,
|
||||
testReports: {
|
||||
test_suites: [suite],
|
||||
},
|
||||
selectedSuiteIndex: 0,
|
||||
},
|
||||
getters,
|
||||
});
|
||||
|
|
|
@ -18,15 +18,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
|
|||
|
||||
|
||||
<gl-emoji
|
||||
data-fallback-src="/assets/emoji/thumbsup-59ec2457ab33e8897261d01a495f6cf5c668d0004807dc541c3b1be5294b1e61.png"
|
||||
data-name="thumbsup"
|
||||
data-unicode-version="6.0"
|
||||
title="thumbs up sign"
|
||||
>
|
||||
|
||||
👍
|
||||
|
||||
</gl-emoji>
|
||||
/>
|
||||
|
||||
|
||||
</span>
|
||||
|
@ -51,15 +44,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
|
|||
|
||||
|
||||
<gl-emoji
|
||||
data-fallback-src="/assets/emoji/thumbsdown-5954334e2dae5357312b3d629f10a496c728029e02216f8c8b887f9b51561c61.png"
|
||||
data-name="thumbsdown"
|
||||
data-unicode-version="6.0"
|
||||
title="thumbs down sign"
|
||||
>
|
||||
|
||||
👎
|
||||
|
||||
</gl-emoji>
|
||||
/>
|
||||
|
||||
|
||||
</span>
|
||||
|
@ -84,15 +70,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
|
|||
|
||||
|
||||
<gl-emoji
|
||||
data-fallback-src="/assets/emoji/smile-14905c372d5bf7719bd727c9efae31a03291acec79801652a23710c6848c5d14.png"
|
||||
data-name="smile"
|
||||
data-unicode-version="6.0"
|
||||
title="smiling face with open mouth and smiling eyes"
|
||||
>
|
||||
|
||||
😄
|
||||
|
||||
</gl-emoji>
|
||||
/>
|
||||
|
||||
|
||||
</span>
|
||||
|
@ -117,15 +96,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
|
|||
|
||||
|
||||
<gl-emoji
|
||||
data-fallback-src="/assets/emoji/ok_hand-d63002dce3cc3655b67b8765b7c28d370edba0e3758b2329b60e0e61c4d8e78d.png"
|
||||
data-name="ok_hand"
|
||||
data-unicode-version="6.0"
|
||||
title="ok hand sign"
|
||||
>
|
||||
|
||||
👌
|
||||
|
||||
</gl-emoji>
|
||||
/>
|
||||
|
||||
|
||||
</span>
|
||||
|
@ -150,15 +122,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
|
|||
|
||||
|
||||
<gl-emoji
|
||||
data-fallback-src="/assets/emoji/cactus-2c5c4c35f26c7046fdc002b337e0d939729b33a26980e675950f9934c91e40fd.png"
|
||||
data-name="cactus"
|
||||
data-unicode-version="6.0"
|
||||
title="cactus"
|
||||
>
|
||||
|
||||
🌵
|
||||
|
||||
</gl-emoji>
|
||||
/>
|
||||
|
||||
|
||||
</span>
|
||||
|
@ -183,15 +148,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
|
|||
|
||||
|
||||
<gl-emoji
|
||||
data-fallback-src="/assets/emoji/a-bddbb39e8a1d35d42b7c08e7d47f63988cb4d8614b79f74e70b9c67c221896cc.png"
|
||||
data-name="a"
|
||||
data-unicode-version="6.0"
|
||||
title="negative squared latin capital letter a"
|
||||
>
|
||||
|
||||
🅰
|
||||
|
||||
</gl-emoji>
|
||||
/>
|
||||
|
||||
|
||||
</span>
|
||||
|
@ -216,15 +174,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
|
|||
|
||||
|
||||
<gl-emoji
|
||||
data-fallback-src="/assets/emoji/b-722f9db9442e7c0fc0d0ac0f5291fbf47c6a0ac4d8abd42e97957da705fb82bf.png"
|
||||
data-name="b"
|
||||
data-unicode-version="6.0"
|
||||
title="negative squared latin capital letter b"
|
||||
>
|
||||
|
||||
🅱
|
||||
|
||||
</gl-emoji>
|
||||
/>
|
||||
|
||||
|
||||
</span>
|
||||
|
|
|
@ -55,7 +55,7 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateBuildStage do
|
|||
statuses[:pending]]
|
||||
end
|
||||
|
||||
it 'recovers from unique constraint violation only twice', :quarantine do
|
||||
it 'recovers from unique constraint violation only twice', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/28128' do
|
||||
allow(described_class::Migratable::Stage)
|
||||
.to receive(:find_by).and_return(nil)
|
||||
|
||||
|
|
|
@ -65,14 +65,14 @@ RSpec.describe Gitlab::Lograge::CustomOptions do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when correlation_id is overriden' do
|
||||
context 'when correlation_id is overridden' do
|
||||
let(:correlation_id_key) { Labkit::Correlation::CorrelationId::LOG_KEY }
|
||||
|
||||
before do
|
||||
event_payload[correlation_id_key] = '123456'
|
||||
end
|
||||
|
||||
it 'sets the overriden value' do
|
||||
it 'sets the overridden value' do
|
||||
expect(subject[correlation_id_key]).to eq('123456')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -48,18 +48,47 @@ RSpec.describe Gitlab::Runtime do
|
|||
before do
|
||||
stub_const('::Puma', puma_type)
|
||||
allow(puma_type).to receive_message_chain(:cli_config, :options).and_return(max_threads: 2)
|
||||
stub_env('ACTION_CABLE_IN_APP', 'false')
|
||||
end
|
||||
|
||||
it_behaves_like "valid runtime", :puma, 3
|
||||
|
||||
context "when ActionCable in-app mode is enabled" do
|
||||
before do
|
||||
stub_env('ACTION_CABLE_IN_APP', 'true')
|
||||
stub_env('ACTION_CABLE_WORKER_POOL_SIZE', '3')
|
||||
end
|
||||
|
||||
it_behaves_like "valid runtime", :puma, 6
|
||||
end
|
||||
|
||||
context "when ActionCable standalone is run" do
|
||||
before do
|
||||
stub_const('ACTION_CABLE_SERVER', true)
|
||||
stub_env('ACTION_CABLE_WORKER_POOL_SIZE', '8')
|
||||
end
|
||||
|
||||
it_behaves_like "valid runtime", :puma, 11
|
||||
end
|
||||
end
|
||||
|
||||
context "unicorn" do
|
||||
before do
|
||||
stub_const('::Unicorn', Module.new)
|
||||
stub_const('::Unicorn::HttpServer', Class.new)
|
||||
stub_env('ACTION_CABLE_IN_APP', 'false')
|
||||
end
|
||||
|
||||
it_behaves_like "valid runtime", :unicorn, 1
|
||||
|
||||
context "when ActionCable in-app mode is enabled" do
|
||||
before do
|
||||
stub_env('ACTION_CABLE_IN_APP', 'true')
|
||||
stub_env('ACTION_CABLE_WORKER_POOL_SIZE', '3')
|
||||
end
|
||||
|
||||
it_behaves_like "valid runtime", :unicorn, 4
|
||||
end
|
||||
end
|
||||
|
||||
context "sidekiq" do
|
||||
|
@ -105,17 +134,4 @@ RSpec.describe Gitlab::Runtime do
|
|||
|
||||
it_behaves_like "valid runtime", :rails_runner, 1
|
||||
end
|
||||
|
||||
context "action_cable" do
|
||||
before do
|
||||
stub_const('ACTION_CABLE_SERVER', true)
|
||||
stub_const('::Puma', Module.new)
|
||||
|
||||
allow(Gitlab::Application).to receive_message_chain(:config, :action_cable, :worker_pool_size).and_return(8)
|
||||
end
|
||||
|
||||
it "reports its maximum concurrency based on ActionCable's worker pool size" do
|
||||
expect(subject.max_threads).to eq(9)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -298,7 +298,7 @@ RSpec.describe ReactiveCaching, :use_clean_rails_memory_store_caching do
|
|||
expect(read_reactive_cache(instance)).not_to eq(calculation.call)
|
||||
end
|
||||
|
||||
context 'when reactive_cache_limit_enabled? is overriden to return false' do
|
||||
context 'when reactive_cache_limit_enabled? is overridden to return false' do
|
||||
before do
|
||||
allow(instance).to receive(:reactive_cache_limit_enabled?).and_return(false)
|
||||
end
|
||||
|
|
|
@ -204,7 +204,7 @@ RSpec.describe PersonalAccessToken do
|
|||
end
|
||||
|
||||
describe '.simple_sorts' do
|
||||
it 'includes overriden keys' do
|
||||
it 'includes overridden keys' do
|
||||
expect(described_class.simple_sorts.keys).to include(*%w(expires_at_asc expires_at_desc))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -792,7 +792,7 @@ RSpec.describe QuickActions::InterpretService do
|
|||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'assign command', :quarantine do
|
||||
it_behaves_like 'assign command', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27989' do
|
||||
let(:content) { "/assign @#{developer.username} @#{developer2.username}" }
|
||||
let(:issuable) { merge_request }
|
||||
end
|
||||
|
|
|
@ -266,7 +266,7 @@ RSpec.shared_examples 'thread comments' do |resource_name|
|
|||
end
|
||||
end
|
||||
|
||||
it 'has "Comment" selected when opening the menu', quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/196825' do
|
||||
it 'has "Comment" selected when opening the menu', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/196825' do
|
||||
find(toggle_selector).click
|
||||
|
||||
find("#{menu_selector} li", match: :first)
|
||||
|
|
Loading…
Reference in a new issue