diff options
84 files changed, 528 insertions, 448 deletions
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 084143f1695..e424c4c9f6d 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -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" diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 4a65e9950cd..dc38dba8e94 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -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: diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 8381b050900..217577de7cb 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -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; diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index d1d75658181..bcf732e9522 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -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; - - 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; - - 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; + this.initialize(); + } + initialize() { + let emojiUnicode = this.textContent.trim(); + const { fallbackSpriteClass, fallbackSrc } = this.dataset; + let { name, unicodeVersion } = this.dataset; + + return initEmojiMap().then(() => { + if (!unicodeVersion) { + const emojiInfo = getEmojiInfo(name); + + 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; + } + } + + 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); } - // 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 - }); } - } + }); } } diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js index 27dff8cf9aa..4567c807c40 100644 --- a/app/assets/javascripts/emoji/index.js +++ b/app/assets/javascripts/emoji/index.js @@ -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> `; } diff --git a/app/assets/javascripts/filtered_search/visual_token_value.js b/app/assets/javascripts/filtered_search/visual_token_value.js index 02caf0851af..b1e6c4142e9 100644 --- a/app/assets/javascripts/filtered_search/visual_token_value.js +++ b/app/assets/javascripts/filtered_search/visual_token_value.js @@ -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); + }); } } diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index fcd535cad73..0121fcf2859 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -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 => { diff --git a/app/assets/javascripts/ide/components/ide_status_list.vue b/app/assets/javascripts/ide/components/ide_status_list.vue index 92d25709bd5..1354fdc3d98 100644 --- a/app/assets/javascripts/ide/components/ide_status_list.vue +++ b/app/assets/javascripts/ide/components/ide_status_list.vue @@ -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> diff --git a/app/assets/javascripts/issuables_list/components/issuable.vue b/app/assets/javascripts/issuables_list/components/issuable.vue index 2ee735fd123..b36a83c4974 100644 --- a/app/assets/javascripts/issuables_list/components/issuable.vue +++ b/app/assets/javascripts/issuables_list/components/issuable.vue @@ -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> diff --git a/app/assets/javascripts/pages/profiles/show/index.js b/app/assets/javascripts/pages/profiles/show/index.js index ad003181728..74ab1bc13a9 100644 --- a/app/assets/javascripts/pages/profiles/show/index.js +++ b/app/assets/javascripts/pages/profiles/show/index.js @@ -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, diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue b/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue index 6f2d3ad42ea..a73b7ccbc12 100644 --- a/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue +++ b/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue @@ -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> diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue index aa53bbb0e97..e6a5fa4fa3e 100644 --- a/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue +++ b/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue @@ -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"> diff --git a/app/assets/javascripts/pipelines/stores/test_reports/actions.js b/app/assets/javascripts/pipelines/stores/test_reports/actions.js index e7f7995ef96..f31c315db79 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/actions.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/actions.js @@ -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 diff --git a/app/assets/javascripts/pipelines/stores/test_reports/getters.js b/app/assets/javascripts/pipelines/stores/test_reports/getters.js index 788c1d32987..877762b77c9 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/getters.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/getters.js @@ -9,14 +9,12 @@ export const getTestSuites = state => { })); }; -export const getSuiteTests = state => { - const { selectedSuite } = state; - - if (selectedSuite.test_cases) { - return selectedSuite.test_cases.sort(sortTestCases).map(addIconStatus); - } +export const getSelectedSuite = state => + state.testReports?.test_suites?.[state.selectedSuiteIndex] || {}; - return []; +export const getSuiteTests = state => { + const { test_cases: testCases = [] } = getSelectedSuite(state); + return testCases.sort(sortTestCases).map(addIconStatus); }; // prevent babel-plugin-rewire from generating an invalid default during karma tests diff --git a/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js b/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js index b9c23bf6059..76405557b51 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js @@ -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'; diff --git a/app/assets/javascripts/pipelines/stores/test_reports/mutations.js b/app/assets/javascripts/pipelines/stores/test_reports/mutations.js index 62770246c12..9d820e571af 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/mutations.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/mutations.js @@ -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) { diff --git a/app/assets/javascripts/pipelines/stores/test_reports/state.js b/app/assets/javascripts/pipelines/stores/test_reports/state.js index 0fc49c63ab1..18aefab04f1 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/state.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/state.js @@ -2,7 +2,7 @@ export default ({ fullReportEndpoint = '', summaryEndpoint = '' }) => ({ summaryEndpoint, fullReportEndpoint, testReports: {}, - selectedSuite: {}, + selectedSuiteIndex: null, summary: {}, isLoading: false, }); diff --git a/app/assets/javascripts/repository/components/tree_content.vue b/app/assets/javascripts/repository/components/tree_content.vue index 858b776ce2f..721cc6787dc 100644 --- a/app/assets/javascripts/repository/components/tree_content.vue +++ b/app/assets/javascripts/repository/components/tree_content.vue @@ -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 }) => { diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js index 6528e283372..01db4e363ba 100644 --- a/app/assets/javascripts/repository/index.js +++ b/app/assets/javascripts/repository/index.js @@ -24,7 +24,6 @@ export default function setupVueRepositoryList() { projectShortPath, ref, escapedRef, - vueFileListLfsBadge: gon.features?.vueFileListLfsBadge || false, commits: [], }, }); diff --git a/app/assets/javascripts/repository/queries/files.query.graphql b/app/assets/javascripts/repository/queries/files.query.graphql index 2815bce9239..10843b0450f 100644 --- a/app/assets/javascripts/repository/queries/files.query.graphql +++ b/app/assets/javascripts/repository/queries/files.query.graphql @@ -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 { diff --git a/app/assets/javascripts/repository/queries/vue_file_list_lfs_badge.query.graphql b/app/assets/javascripts/repository/queries/vue_file_list_lfs_badge.query.graphql deleted file mode 100644 index 603e3d3bd81..00000000000 --- a/app/assets/javascripts/repository/queries/vue_file_list_lfs_badge.query.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query VueFileListLfsBadge { - vueFileListLfsBadge @client -} diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue index fd1f9eae152..d5ae9b04090 100644 --- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue +++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue @@ -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); } diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss index 9c92f891834..83623cbbc65 100644 --- a/app/assets/stylesheets/page_bundles/ide.scss +++ b/app/assets/stylesheets/page_bundles/ide.scss @@ -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%; diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 0d1583e05e5..db770d3e438 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -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 diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index 18e08db2268..aadc766313d 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -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 diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 4e3b6aad8cc..58ce063dbd4 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -62,7 +62,7 @@ module SearchHelper }).html_safe end - # Overriden in EE + # Overridden in EE def search_blob_title(project, path) path end diff --git a/app/models/concerns/has_repository.rb b/app/models/concerns/has_repository.rb index 29d31b8bb4f..f7f8aac861e 100644 --- a/app/models/concerns/has_repository.rb +++ b/app/models/concerns/has_repository.rb @@ -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 diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb index 5e669ff2e50..4eb0e244e54 100644 --- a/app/presenters/clusterable_presenter.rb +++ b/app/presenters/clusterable_presenter.rb @@ -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 diff --git a/app/services/users/block_service.rb b/app/services/users/block_service.rb index 9c393832d8f..041db731875 100644 --- a/app/services/users/block_service.rb +++ b/app/services/users/block_service.rb @@ -19,7 +19,7 @@ module Users private def after_block_hook(user) - # overriden by EE module + # overridden by EE module end end end diff --git a/app/views/shared/_issuable_meta_data.html.haml b/app/views/shared/_issuable_meta_data.html.haml index 191e6c132f8..dee6dc1e078 100644 --- a/app/views/shared/_issuable_meta_data.html.haml +++ b/app/views/shared/_issuable_meta_data.html.haml @@ -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 diff --git a/changelogs/unreleased/222534-replace-fa-thumbs-up-and-fa-thumbs-down-with-gitlab-svg-thumbs-ico.yml b/changelogs/unreleased/222534-replace-fa-thumbs-up-and-fa-thumbs-down-with-gitlab-svg-thumbs-ico.yml new file mode 100644 index 00000000000..c484417b12a --- /dev/null +++ b/changelogs/unreleased/222534-replace-fa-thumbs-up-and-fa-thumbs-down-with-gitlab-svg-thumbs-ico.yml @@ -0,0 +1,5 @@ +--- +title: Normalize the 'thumb-up', 'thumb-down' icon. +merge_request: 35988 +author: +type: other diff --git a/changelogs/unreleased/225640-move-link-to-file-view-to-bottom-bar-in-web-ide.yml b/changelogs/unreleased/225640-move-link-to-file-view-to-bottom-bar-in-web-ide.yml new file mode 100644 index 00000000000..e45513ca16f --- /dev/null +++ b/changelogs/unreleased/225640-move-link-to-file-view-to-bottom-bar-in-web-ide.yml @@ -0,0 +1,5 @@ +--- +title: Move file link to bottom in Web IDE +merge_request: 35847 +author: +type: changed diff --git a/config/application.rb b/config/application.rb index 524827226e7..47f8ca48d91 100644 --- a/config/application.rb +++ b/config/application.rb @@ -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') diff --git a/config/environments/development.rb b/config/environments/development.rb index 25d57467060..9d4fc6ba5e9 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -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") diff --git a/config/environments/production.rb b/config/environments/production.rb index c03421040a3..393a274606e 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -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 diff --git a/config/environments/test.rb b/config/environments/test.rb index c130eb84baa..e08e2a34ff4 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -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 diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 57296e3ceae..ce22f636184 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -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: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 8d7d51f1e32..ff7090c3fa3 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -738,12 +738,6 @@ 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 # Settings['monitoring'] ||= Settingslogic.new({}) diff --git a/config/initializers/action_cable.rb b/config/initializers/action_cable.rb index c549dd45ad9..5530e7d64a2 100644 --- a/config/initializers/action_cable.rb +++ b/config/initializers/action_cable.rb @@ -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 diff --git a/doc/api/epic_issues.md b/doc/api/epic_issues.md index 86f88b0322f..4ab505f3627 100644 --- a/doc/api/epic_issues.md +++ b/doc/api/epic_issues.md @@ -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 diff --git a/doc/api/epic_links.md b/doc/api/epic_links.md index 756a41a0680..1d54bfe01e3 100644 --- a/doc/api/epic_links.md +++ b/doc/api/epic_links.md @@ -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 diff --git a/doc/api/issues.md b/doc/api/issues.md index 6078c77e45a..8bf78facfc9 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -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" diff --git a/lib/gitlab/action_cable/config.rb b/lib/gitlab/action_cable/config.rb new file mode 100644 index 00000000000..38e870353eb --- /dev/null +++ b/lib/gitlab/action_cable/config.rb @@ -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 diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index 10e0f4b8e4d..c01b08d703d 100644 --- a/lib/gitlab/ci/config.rb +++ b/lib/gitlab/ci/config.rb @@ -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 diff --git a/lib/gitlab/file_finder.rb b/lib/gitlab/file_finder.rb index d438b0415fa..6225955a930 100644 --- a/lib/gitlab/file_finder.rb +++ b/lib/gitlab/file_finder.rb @@ -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 diff --git a/lib/gitlab/runtime.rb b/lib/gitlab/runtime.rb index abf6ee07d53..8b40aaa101a 100644 --- a/lib/gitlab/runtime.rb +++ b/lib/gitlab/runtime.rb @@ -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 diff --git a/locale/gitlab.pot b/locale/gitlab.pot index bf3b5b9d3d3..cbf29779c11 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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 "" diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index 21dfca134e3..6cd18f2755c 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -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') diff --git a/spec/features/discussion_comments/commit_spec.rb b/spec/features/discussion_comments/commit_spec.rb index 5a744e43bb6..32c0ba2a9a7 100644 --- a/spec/features/discussion_comments/commit_spec.rb +++ b/spec/features/discussion_comments/commit_spec.rb @@ -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 diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb index 1481c4d07a2..8cf4dac9afe 100644 --- a/spec/features/issuables/issuable_list_spec.rb +++ b/spec/features/issuables/issuable_list_spec.rb @@ -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 diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb index 167fecc5ab1..2a094281133 100644 --- a/spec/features/issues/filtered_search/search_bar_spec.rb +++ b/spec/features/issues/filtered_search/search_bar_spec.rb @@ -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']) diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb index ab319daec71..9879703c8bf 100644 --- a/spec/features/issues/issue_detail_spec.rb +++ b/spec/features/issues/issue_detail_spec.rb @@ -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') diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb index 9e4362bf0e5..ecda80f2483 100644 --- a/spec/features/issues/issue_sidebar_spec.rb +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -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' diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb index a546fb3e85b..900ef435d1d 100644 --- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb +++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb @@ -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/ diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb index 7b7e087a6d6..35d9db68d32 100644 --- a/spec/features/issues/user_interacts_with_awards_spec.rb +++ b/spec/features/issues/user_interacts_with_awards_spec.rb @@ -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 diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb index 3ab7fbea198..1545cb36e9b 100644 --- a/spec/features/labels_hierarchy_spec.rb +++ b/spec/features/labels_hierarchy_spec.rb @@ -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 diff --git a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb index 34eaca24a01..3cd23764382 100644 --- a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb +++ b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb @@ -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) diff --git a/spec/features/merge_request/user_posts_diff_notes_spec.rb b/spec/features/merge_request/user_posts_diff_notes_spec.rb index dbad2f191a1..6ecffb05009 100644 --- a/spec/features/merge_request/user_posts_diff_notes_spec.rb +++ b/spec/features/merge_request/user_posts_diff_notes_spec.rb @@ -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 diff --git a/spec/features/merge_request/user_sees_diff_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb index 2229b242d5b..d067fc0ada4 100644 --- a/spec/features/merge_request/user_sees_diff_spec.rb +++ b/spec/features/merge_request/user_sees_diff_spec.rb @@ -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) diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb index e7eb4bb41cc..0506d190487 100644 --- a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb +++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb @@ -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) diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb index d9421631b32..2747b5894dc 100644 --- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb +++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb @@ -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' diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb index aa7633c3b28..c4bd0b81dc0 100644 --- a/spec/features/projects/members/member_leaves_project_spec.rb +++ b/spec/features/projects/members/member_leaves_project_spec.rb @@ -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 diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb index ef65b3a2cc6..af2ecfec498 100644 --- a/spec/features/users/signup_spec.rb +++ b/spec/features/users/signup_spec.rb @@ -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' diff --git a/spec/frontend/__mocks__/document-register-element/index.js b/spec/frontend/__mocks__/document-register-element/index.js new file mode 100644 index 00000000000..2d1ec238274 --- /dev/null +++ b/spec/frontend/__mocks__/document-register-element/index.js @@ -0,0 +1 @@ +export default () => {}; diff --git a/spec/frontend/awards_handler_spec.js b/spec/frontend/awards_handler_spec.js index 754f0702b84..ef09c59b5f6 100644 --- a/spec/frontend/awards_handler_spec.js +++ b/spec/frontend/awards_handler_spec.js @@ -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'); diff --git a/spec/frontend/behaviors/gl_emoji_spec.js b/spec/frontend/behaviors/gl_emoji_spec.js new file mode 100644 index 00000000000..7ea0bafc328 --- /dev/null +++ b/spec/frontend/behaviors/gl_emoji_spec.js @@ -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); + }); +}); diff --git a/spec/frontend/emoji_spec.js b/spec/frontend/emoji/emoji_spec.js index 25bc95e0dd6..c6a15d5976a 100644 --- a/spec/frontend/emoji_spec.js +++ b/spec/frontend/emoji/emoji_spec.js @@ -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, -}; +describe('gl_emoji', () => { + let mock; + const emojiData = getJSONFixture('emojis/emojis.json'); -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); - } -} + beforeEach(() => { + mock = new MockAdapter(axios); + mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData); + + return initEmojiMap().catch(() => {}); + }); + + afterEach(() => { + mock.restore(); + }); -describe('gl_emoji', () => { 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>"`, ); }); }); diff --git a/spec/frontend/behaviors/gl_emoji/unicode_support_map_spec.js b/spec/frontend/emoji/support/unicode_support_map_spec.js index aaee9c30cac..aaee9c30cac 100644 --- a/spec/frontend/behaviors/gl_emoji/unicode_support_map_spec.js +++ b/spec/frontend/emoji/support/unicode_support_map_spec.js diff --git a/spec/frontend/fixtures/emojis.rb b/spec/frontend/fixtures/emojis.rb new file mode 100644 index 00000000000..b95c7632917 --- /dev/null +++ b/spec/frontend/fixtures/emojis.rb @@ -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 diff --git a/spec/frontend/ide/components/ide_status_list_spec.js b/spec/frontend/ide/components/ide_status_list_spec.js index 847464ed806..fed61233e55 100644 --- a/spec/frontend/ide/components/ide_status_list_spec.js +++ b/spec/frontend/ide/components/ide_status_list_spec.js @@ -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', () => { diff --git a/spec/frontend/monitoring/components/dashboard_panel_spec.js b/spec/frontend/monitoring/components/dashboard_panel_spec.js index 96e0c39cb27..a2499c84ff7 100644 --- a/spec/frontend/monitoring/components/dashboard_panel_spec.js +++ b/spec/frontend/monitoring/components/dashboard_panel_spec.js @@ -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; diff --git a/spec/frontend/pipelines/test_reports/stores/actions_spec.js b/spec/frontend/pipelines/test_reports/stores/actions_spec.js index bd42db36645..c47c6e99103 100644 --- a/spec/frontend/pipelines/test_reports/stores/actions_spec.js +++ b/spec/frontend/pipelines/test_reports/stores/actions_spec.js @@ -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, ); diff --git a/spec/frontend/pipelines/test_reports/stores/getters_spec.js b/spec/frontend/pipelines/test_reports/stores/getters_spec.js index 011a7e68908..ca9ebb54138 100644 --- a/spec/frontend/pipelines/test_reports/stores/getters_spec.js +++ b/spec/frontend/pipelines/test_reports/stores/getters_spec.js @@ -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(); diff --git a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js index cf54cacab60..2320f84b3bb 100644 --- a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js +++ b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js @@ -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); }); }); diff --git a/spec/frontend/pipelines/test_reports/test_reports_spec.js b/spec/frontend/pipelines/test_reports/test_reports_spec.js index dd2b7220d7c..7bd71716cb0 100644 --- a/spec/frontend/pipelines/test_reports/test_reports_spec.js +++ b/spec/frontend/pipelines/test_reports/test_reports_spec.js @@ -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, { diff --git a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js index 5d448bcb439..65bffe7039a 100644 --- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js +++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js @@ -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, }); diff --git a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap index df4b30f1cb8..19671d425a9 100644 --- a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap +++ b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap @@ -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> diff --git a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb index 78a4ec34cf2..65d45ec694f 100644 --- a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb @@ -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) diff --git a/spec/lib/gitlab/lograge/custom_options_spec.rb b/spec/lib/gitlab/lograge/custom_options_spec.rb index 550f8c17f04..218007c6e2a 100644 --- a/spec/lib/gitlab/lograge/custom_options_spec.rb +++ b/spec/lib/gitlab/lograge/custom_options_spec.rb @@ -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 diff --git a/spec/lib/gitlab/runtime_spec.rb b/spec/lib/gitlab/runtime_spec.rb index 8c1f17d070e..8ed7cc141cd 100644 --- a/spec/lib/gitlab/runtime_spec.rb +++ b/spec/lib/gitlab/runtime_spec.rb @@ -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 diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb index 425dc70e7a1..b12ad82920f 100644 --- a/spec/models/concerns/reactive_caching_spec.rb +++ b/spec/models/concerns/reactive_caching_spec.rb @@ -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 diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb index b9cb5477c54..7fae9cc60fc 100644 --- a/spec/models/personal_access_token_spec.rb +++ b/spec/models/personal_access_token_spec.rb @@ -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 diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index 66601d26bd2..57e32b1aea9 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -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 diff --git a/spec/support/shared_examples/features/discussion_comments_shared_example.rb b/spec/support/shared_examples/features/discussion_comments_shared_example.rb index 6007798c290..9fc5d8933e5 100644 --- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb +++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb @@ -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) |