Import modules instead of using the ones in global namespace

Removes set favicon related methods from global scope
Improves test related with favicon

Removes convertPermissionToBoolean from global scope.
Adds tests for convertPermissionToBoolean - were non existant

Removes setParamInURL from gl.utils

Removes parseIntPagination from gl.utils namespace

Remove normalizeCRLFHeaders from gl.utils namespace

Removes normalizeHeaders from gl.utils namespace

Use gl.utils for filtered search

Fix bad import

Fix broken test by cleaning window.history namespace

Adds changelog
This commit is contained in:
Filipa Lacerda 2017-09-06 17:14:34 +01:00
parent 6a1b84c7b4
commit 96e6fc70b4
32 changed files with 234 additions and 171 deletions

View File

@ -1,4 +1,6 @@
/* global Flash */
import { handleLocationHash } from '../../lib/utils/common_utils';
export default class BlobViewer {
constructor() {
BlobViewer.initAuxiliaryViewer();
@ -114,7 +116,7 @@ export default class BlobViewer {
$(viewer).renderGFM();
this.$fileHolder.trigger('highlight:line');
gl.utils.handleLocationHash();
handleLocationHash();
this.toggleCopyButtonState();
})

View File

@ -2,6 +2,7 @@
/* global List */
import _ from 'underscore';
import Cookies from 'js-cookie';
import { getUrlParamsArray } from '../../lib/utils/common_utils';
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
@ -21,7 +22,7 @@ gl.issueBoards.BoardsStore = {
},
create () {
this.state.lists = [];
this.filter.path = gl.utils.getUrlParamsArray().join('&');
this.filter.path = getUrlParamsArray().join('&');
this.detail = { issue: {} };
},
addList (listObj, defaultAvatar) {

View File

@ -3,6 +3,7 @@ consistent-return, prefer-rest-params */
import _ from 'underscore';
import bp from './breakpoints';
import { bytesToKiB } from './lib/utils/number_utils';
import { setCiStatusFavicon } from './lib/utils/common_utils';
window.Build = (function () {
Build.timeout = null;
@ -169,7 +170,7 @@ window.Build = (function () {
data: this.state,
})
.done((log) => {
gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`);
setCiStatusFavicon(`${this.pageUrl}/status.json`);
if (log.state) {
this.state = log.state;

View File

@ -1,6 +1,6 @@
/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
import _ from 'underscore';
import './lib/utils/common_utils';
import { insertText, getSelectedFragment, nodeMatchesSelector } from './lib/utils/common_utils';
import { placeholderImage } from './lazy_loader';
const gfmRules = {
@ -295,7 +295,7 @@ class CopyAsGFM {
const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return;
const documentFragment = window.gl.utils.getSelectedFragment();
const documentFragment = getSelectedFragment();
if (!documentFragment) return;
const el = transformer(documentFragment.cloneNode(true));
@ -412,7 +412,7 @@ class CopyAsGFM {
for (const selector in rules) {
const func = rules[selector];
if (!window.gl.utils.nodeMatchesSelector(node, selector)) continue;
if (!nodeMatchesSelector(node, selector)) continue;
let result;
if (func.length === 2) {

View File

@ -77,7 +77,7 @@ import initProjectVisibilitySelector from './project_visibility';
import GpgBadges from './gpg_badges';
import UserFeatureHelper from './helpers/user_feature_helper';
import initChangesDropdown from './init_changes_dropdown';
import { ajaxGet } from './lib/utils/common_utils';
import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
(function() {
var Dispatcher;
@ -101,7 +101,7 @@ import { ajaxGet } from './lib/utils/common_utils';
$('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => {
const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
const enableGFM = gl.utils.convertPermissionToBoolean(el.dataset.supportsAutocomplete);
const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete);
gfm.setup($(el), {
emojis: true,
members: enableGFM,

View File

@ -6,7 +6,7 @@ import environmentTable from './environments_table.vue';
import EnvironmentsStore from '../stores/environments_store';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import '../../lib/utils/common_utils';
import { convertPermissionToBoolean, getParameterByName, setParamInURL } from '../../lib/utils/common_utils';
import eventHub from '../event_hub';
import Poll from '../../lib/utils/poll';
import environmentsMixin from '../mixins/environments_mixin';
@ -51,19 +51,19 @@ export default {
computed: {
scope() {
return gl.utils.getParameterByName('scope');
return getParameterByName('scope');
},
canReadEnvironmentParsed() {
return gl.utils.convertPermissionToBoolean(this.canReadEnvironment);
return convertPermissionToBoolean(this.canReadEnvironment);
},
canCreateDeploymentParsed() {
return gl.utils.convertPermissionToBoolean(this.canCreateDeployment);
return convertPermissionToBoolean(this.canCreateDeployment);
},
canCreateEnvironmentParsed() {
return gl.utils.convertPermissionToBoolean(this.canCreateEnvironment);
return convertPermissionToBoolean(this.canCreateEnvironment);
},
},
@ -72,8 +72,8 @@ export default {
* Toggles loading property.
*/
created() {
const scope = gl.utils.getParameterByName('scope') || this.visibility;
const page = gl.utils.getParameterByName('page') || this.pageNumber;
const scope = getParameterByName('scope') || this.visibility;
const page = getParameterByName('page') || this.pageNumber;
this.service = new EnvironmentsService(this.endpoint);
@ -126,15 +126,15 @@ export default {
* @return {String}
*/
changePage(pageNumber) {
const param = gl.utils.setParamInURL('page', pageNumber);
const param = setParamInURL('page', pageNumber);
gl.utils.visitUrl(param);
return param;
},
fetchEnvironments() {
const scope = gl.utils.getParameterByName('scope') || this.visibility;
const page = gl.utils.getParameterByName('page') || this.pageNumber;
const scope = getParameterByName('scope') || this.visibility;
const page = getParameterByName('page') || this.pageNumber;
this.isLoading = true;

View File

@ -9,7 +9,7 @@ import tablePagination from '../../vue_shared/components/table_pagination.vue';
import Poll from '../../lib/utils/poll';
import eventHub from '../event_hub';
import environmentsMixin from '../mixins/environments_mixin';
import '../../lib/utils/common_utils';
import { convertPermissionToBoolean, getParameterByName, setParamInURL } from '../../lib/utils/common_utils';
export default {
components: {
@ -47,15 +47,15 @@ export default {
computed: {
scope() {
return gl.utils.getParameterByName('scope');
return getParameterByName('scope');
},
canReadEnvironmentParsed() {
return gl.utils.convertPermissionToBoolean(this.canReadEnvironment);
return convertPermissionToBoolean(this.canReadEnvironment);
},
canCreateDeploymentParsed() {
return gl.utils.convertPermissionToBoolean(this.canCreateDeployment);
return convertPermissionToBoolean(this.canCreateDeployment);
},
/**
@ -82,8 +82,8 @@ export default {
* Toggles loading property.
*/
created() {
const scope = gl.utils.getParameterByName('scope') || this.visibility;
const page = gl.utils.getParameterByName('page') || this.pageNumber;
const scope = getParameterByName('scope') || this.visibility;
const page = getParameterByName('page') || this.pageNumber;
this.service = new EnvironmentsService(this.endpoint);
@ -125,15 +125,15 @@ export default {
* @param {Number} pageNumber desired page to go to.
*/
changePage(pageNumber) {
const param = gl.utils.setParamInURL('page', pageNumber);
const param = setParamInURL('page', pageNumber);
gl.utils.visitUrl(param);
return param;
},
fetchEnvironments() {
const scope = gl.utils.getParameterByName('scope') || this.visibility;
const page = gl.utils.getParameterByName('page') || this.pageNumber;
const scope = getParameterByName('scope') || this.visibility;
const page = getParameterByName('page') || this.pageNumber;
this.isLoading = true;

View File

@ -1,4 +1,4 @@
import '~/lib/utils/common_utils';
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
/**
* Environments Store.
*
@ -66,8 +66,8 @@ export default class EnvironmentsStore {
}
setPagination(pagination = {}) {
const normalizedHeaders = gl.utils.normalizeHeaders(pagination);
const paginationInformation = gl.utils.parseIntPagination(normalizedHeaders);
const normalizedHeaders = normalizeHeaders(pagination);
const paginationInformation = parseIntPagination(normalizedHeaders);
this.state.paginationInformation = paginationInformation;
return paginationInformation;

View File

@ -1,6 +1,7 @@
<script>
import tablePagination from '~/vue_shared/components/table_pagination.vue';
import eventHub from '../event_hub';
import { getParameterByName } from '../../lib/utils/common_utils';
export default {
props: {
@ -18,8 +19,8 @@ export default {
},
methods: {
change(page) {
const filterGroupsParam = gl.utils.getParameterByName('filter_groups');
const sortParam = gl.utils.getParameterByName('sort');
const filterGroupsParam = getParameterByName('filter_groups');
const sortParam = getParameterByName('sort');
eventHub.$emit('fetchPage', page, filterGroupsParam, sortParam);
},
},

View File

@ -1,5 +1,6 @@
import FilterableList from '~/filterable_list';
import eventHub from './event_hub';
import { getParameterByName } from '../lib/utils/common_utils';
export default class GroupFilterableList extends FilterableList {
constructor({ form, filter, holder, filterEndpoint, pagePath }) {
@ -54,7 +55,7 @@ export default class GroupFilterableList extends FilterableList {
e.preventDefault();
const queryData = {};
const sortParam = gl.utils.getParameterByName('sort', e.currentTarget.href);
const sortParam = getParameterByName('sort', e.currentTarget.href);
if (sortParam) {
queryData.sort = sortParam;

View File

@ -8,6 +8,7 @@ import GroupItem from './components/group_item.vue';
import GroupsStore from './stores/groups_store';
import GroupsService from './services/groups_service';
import eventHub from './event_hub';
import { getParameterByName } from '../lib/utils/common_utils';
document.addEventListener('DOMContentLoaded', () => {
const el = document.getElementById('dashboard-group-app');
@ -58,17 +59,17 @@ document.addEventListener('DOMContentLoaded', () => {
this.isLoading = true;
}
pageParam = gl.utils.getParameterByName('page');
pageParam = getParameterByName('page');
if (pageParam) {
page = pageParam;
}
filterGroupsParam = gl.utils.getParameterByName('filter_groups');
filterGroupsParam = getParameterByName('filter_groups');
if (filterGroupsParam) {
filterGroups = filterGroupsParam;
}
sortParam = gl.utils.getParameterByName('sort');
sortParam = getParameterByName('sort');
if (sortParam) {
sort = sortParam;
}

View File

@ -1,4 +1,5 @@
import Vue from 'vue';
import { parseIntPagination, normalizeHeaders } from '../../lib/utils/common_utils';
export default class GroupsStore {
constructor() {
@ -30,8 +31,8 @@ export default class GroupsStore {
let paginationInfo;
if (Object.keys(pagination).length) {
const normalizedHeaders = gl.utils.normalizeHeaders(pagination);
paginationInfo = gl.utils.parseIntPagination(normalizedHeaders);
const normalizedHeaders = normalizeHeaders(pagination);
paginationInfo = parseIntPagination(normalizedHeaders);
} else {
paginationInfo = pagination;
}

View File

@ -4,6 +4,7 @@
prefer-rest-params, prefer-spread, no-unused-vars, prefer-template,
promise/catch-or-return */
import Api from './api';
import { normalizeCRLFHeaders } from './lib/utils/common_utils';
var slice = [].slice;
@ -30,7 +31,7 @@ window.GroupsSelect = (function() {
$.ajax(params).then((data, status, xhr) => {
const results = data || [];
const headers = gl.utils.normalizeCRLFHeaders(xhr.getAllResponseHeaders());
const headers = normalizeCRLFHeaders(xhr.getAllResponseHeaders());
const currentPage = parseInt(headers['X-PAGE'], 10) || 0;
const totalPages = parseInt(headers['X-TOTAL-PAGES'], 10) || 0;
const more = currentPage < totalPages;

View File

@ -1,4 +1,5 @@
/* eslint-disable no-param-reassing */
window.gl = window.gl || {};
window.gl.utils = window.gl.utils || {};
export const getPagePath = (index = 0) => $('body').data('page').split(':')[index];
window.gl.utils.getPagePath = getPagePath;
@ -48,7 +49,7 @@ export const ajaxPost = (url, data) => $.ajax({
window.gl.utils.ajaxPost = ajaxPost;
// TODO: This function seems not to be used anywhere
window.gl.utils.extractLast = term => this.split(term).pop();
// window.gl.utils.extractLast = term => this.split(term).pop();
export const rstrip = function rstrip(val) {
if (val) {
@ -145,10 +146,11 @@ window.gl.utils.parseUrlPathname = parseUrlPathname;
// We can trust that each param has one & since values containing & will be encoded
// Remove the first character of search as it is always ?
window.gl.utils.getUrlParamsArray = () => window.location.search.slice(1).split('&').map((param) => {
export const getUrlParamsArray = () => window.location.search.slice(1).split('&').map((param) => {
const split = param.split('=');
return [decodeURI(split[0]), split[1]].join('=');
});
window.gl.utils.getUrlParamsArray = getUrlParamsArray;
export const isMetaKey = e => e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
window.gl.utils.isMetaKey = isMetaKey;
@ -178,8 +180,8 @@ window.gl.utils.scrollToElement = scrollToElement;
*/
export const getParameterByName = (name, urlToParse) => {
const url = urlToParse || window.location.href;
name = name.replace(/[[\]]/g, '\\$&');
const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`);
const parsedName = name.replace(/[[\]]/g, '\\$&');
const regex = new RegExp(`[?&]${parsedName}(=([^&#]*)|&|#|$)`);
const results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
@ -200,6 +202,7 @@ export const getSelectedFragment = () => {
};
window.gl.utils.getSelectedFragment = getSelectedFragment;
// TODO: Update this name, there is a gl.text.insertText function.
export const insertText = (target, text) => {
// Firefox doesn't support `document.execCommand('insertText', false, text)` on textareas
const selectionStart = target.selectionStart;
@ -212,7 +215,9 @@ export const insertText = (target, text) => {
const insertedText = text instanceof Function ? text(textBefore, textAfter) : text;
const newText = textBefore + insertedText + textAfter;
// eslint-disable-next-line no-param-reassign
target.value = newText;
// eslint-disable-next-line no-param-reassign
target.selectionStart = target.selectionEnd = selectionStart + insertedText.length;
// Trigger autosave
@ -242,6 +247,7 @@ export const nodeMatchesSelector = (node, selector) => {
let parentNode = node.parentNode;
if (!parentNode) {
parentNode = document.createElement('div');
// eslint-disable-next-line no-param-reassign
node = node.cloneNode(true);
parentNode.appendChild(node);
}
@ -264,7 +270,6 @@ export const normalizeHeaders = (headers) => {
return upperCaseHeaders;
};
window.gl.utils.normalizeHeaders = normalizeHeaders;
/**
this will take in the getAllResponseHeaders result and normalize them
@ -281,7 +286,6 @@ export const normalizeCRLFHeaders = (headers) => {
return normalizeHeaders(headersObject);
};
window.gl.utils.normalizeCRLFHeaders = normalizeCRLFHeaders;
/**
* Parses pagination object string values into numbers.
@ -297,7 +301,6 @@ export const parseIntPagination = paginationInformation => ({
nextPage: parseInt(paginationInformation['X-NEXT-PAGE'], 10),
previousPage: parseInt(paginationInformation['X-PREV-PAGE'], 10),
});
window.gl.utils.parseIntPagination = parseIntPagination;
/**
* Updates the search parameter of a URL given the parameter and value provided.
@ -320,6 +323,7 @@ export const setParamInURL = (param, value) => {
.split('&')
.reduce((acc, element) => {
const val = element.split('=');
// eslint-disable-next-line no-param-reassign
acc[val[0]] = decodeURIComponent(val[1]);
return acc;
}, {});
@ -337,7 +341,6 @@ export const setParamInURL = (param, value) => {
return search;
};
window.gl.utils.setParamInURL = setParamInURL;
/**
* Converts permission provided as strings to booleans.
@ -346,7 +349,6 @@ window.gl.utils.setParamInURL = setParamInURL;
* @returns {Boolean}
*/
export const convertPermissionToBoolean = permission => permission === 'true';
window.gl.utils.convertPermissionToBoolean = convertPermissionToBoolean;
/**
* Back Off exponential algorithm
@ -400,7 +402,6 @@ export const backOff = (fn, timeout = 60000) => {
fn(next, stop);
});
};
window.gl.utils.backOff = backOff;
export const setFavicon = (faviconPath) => {
const faviconEl = document.getElementById('favicon');
@ -408,7 +409,6 @@ export const setFavicon = (faviconPath) => {
faviconEl.setAttribute('href', faviconPath);
}
};
window.gl.utils.setFavicon = setFavicon;
export const resetFavicon = () => {
const faviconEl = document.getElementById('favicon');
@ -417,7 +417,6 @@ export const resetFavicon = () => {
faviconEl.setAttribute('href', originalFavicon);
}
};
window.gl.utils.resetFavicon = resetFavicon;
export const setCiStatusFavicon = (pageUrl) => {
$.ajax({
@ -425,14 +424,13 @@ export const setCiStatusFavicon = (pageUrl) => {
dataType: 'json',
success: (data) => {
if (data && data.favicon) {
gl.utils.setFavicon(data.favicon);
setFavicon(data.favicon);
} else {
gl.utils.resetFavicon();
resetFavicon();
}
},
error: () => {
gl.utils.resetFavicon();
resetFavicon();
},
});
};
window.gl.utils.setCiStatusFavicon = setCiStatusFavicon;

View File

@ -1,4 +1,5 @@
import httpStatusCodes from './http_status';
import { normalizeHeaders } from './common_utils';
/**
* Polling utility for handling realtime updates.
@ -57,7 +58,7 @@ export default class Poll {
}
checkConditions(response) {
const headers = gl.utils.normalizeHeaders(response.headers);
const headers = normalizeHeaders(response.headers);
const pollInterval = parseInt(headers[this.intervalHeader], 10);
if (pollInterval > 0 && response.status === httpStatusCodes.OK && this.canPoll) {

View File

@ -41,7 +41,7 @@ import './commit/image_file';
// lib/utils
import './lib/utils/animate';
import './lib/utils/bootstrap_linked_tabs';
import './lib/utils/common_utils';
import { handleLocationHash } from './lib/utils/common_utils';
import './lib/utils/datetime_utility';
import './lib/utils/pretty_time';
import './lib/utils/text_utility';
@ -162,10 +162,10 @@ document.addEventListener('beforeunload', function () {
$('[data-toggle="popover"]').popover('destroy');
});
window.addEventListener('hashchange', gl.utils.handleLocationHash);
window.addEventListener('hashchange', handleLocationHash);
window.addEventListener('load', function onLoad() {
window.removeEventListener('load', onLoad, false);
gl.utils.handleLocationHash();
handleLocationHash();
}, false);
gl.lazyLoader = new LazyLoader({
@ -191,7 +191,7 @@ $(function () {
$body.on('click', 'a[href^="#"]', function() {
var href = this.getAttribute('href');
if (href.substr(1) === gl.utils.getLocationHash()) {
setTimeout(gl.utils.handleLocationHash, 1);
setTimeout(handleLocationHash, 1);
}
});

View File

@ -7,7 +7,11 @@ import './flash';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import initChangesDropdown from './init_changes_dropdown';
import bp from './breakpoints';
import parseUrlPathname from './lib/utils/common_utils';
import {
parseUrlPathname,
handleLocationHash,
isMetaClick,
} from './lib/utils/common_utils';
/* eslint-disable max-len */
// MergeRequestTabs
@ -115,7 +119,7 @@ import parseUrlPathname from './lib/utils/common_utils';
}
clickTab(e) {
if (e.currentTarget && gl.utils.isMetaClick(e)) {
if (e.currentTarget && isMetaClick(e)) {
const targetLink = e.currentTarget.getAttribute('href');
e.stopImmediatePropagation();
e.preventDefault();
@ -310,7 +314,7 @@ import parseUrlPathname from './lib/utils/common_utils';
forceShow: true,
});
anchor[0].scrollIntoView();
window.gl.utils.handleLocationHash();
handleLocationHash();
// We have multiple elements on the page with `#note_xxx`
// (discussion and diff tabs) and `:target` only applies to the first
anchor.addClass('target');

View File

@ -8,7 +8,7 @@
import EmptyState from './empty_state.vue';
import MonitoringStore from '../stores/monitoring_store';
import eventHub from '../event_hub';
import { backOff } from '../../lib/utils/common_utils';
import { backOff, convertPermissionToBoolean } from '../../lib/utils/common_utils';
export default {
@ -19,7 +19,7 @@
return {
store,
state: 'gettingStarted',
hasMetrics: gl.utils.convertPermissionToBoolean(metricsData.hasMetrics),
hasMetrics: convertPermissionToBoolean(metricsData.hasMetrics),
documentationPath: metricsData.documentationPath,
settingsPath: metricsData.settingsPath,
endpoint: metricsData.additionalMetrics,

View File

@ -23,7 +23,7 @@ import loadAwardsHandler from './awards_handler';
import './autosave';
import './dropzone_input';
import TaskList from './task_list';
import { ajaxPost, isInViewport, getPagePath } from './lib/utils/common_utils';
import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils';
window.autosize = autosize;
window.Dropzone = Dropzone;
@ -176,7 +176,7 @@ export default class Notes {
keydownNoteText(e) {
var $textarea, discussionNoteForm, editNote, myLastNote, myLastNoteEditBtn, newText, originalText;
if (gl.utils.isMetaKey(e)) {
if (isMetaKey(e)) {
return;
}
@ -1482,7 +1482,7 @@ export default class Notes {
*
* 1) Get Form metadata
* 2) Update note element with new content
* 3) Perform network request to submit the updated note using `gl.utils.ajaxPost`
* 3) Perform network request to submit the updated note using `ajaxPost`
* a) If request is successfully completed
* 1. Show submitted Note element
* b) If request failed

View File

@ -1,4 +1,4 @@
import '~/lib/utils/common_utils';
import { getParameterByName } from '~/lib/utils/common_utils';
import '~/lib/utils/url_utility';
(() => {
@ -9,7 +9,7 @@ import '~/lib/utils/url_utility';
init(limit = 0, preload = false, disable = false, prepareData = $.noop, callback = $.noop) {
this.url = $('.content_list').data('href') || gl.utils.removeParams(['limit', 'offset']);
this.limit = limit;
this.offset = parseInt(gl.utils.getParameterByName('offset'), 10) || this.limit;
this.offset = parseInt(getParameterByName('offset'), 10) || this.limit;
this.disable = disable;
this.prepareData = prepareData;
this.callback = callback;

View File

@ -1,3 +1,5 @@
import { convertPermissionToBoolean } from '../lib/utils/common_utils';
function insertRow($row) {
const $rowClone = $row.clone();
$rowClone.removeAttr('data-is-persisted');
@ -6,7 +8,7 @@ function insertRow($row) {
}
function removeRow($row) {
const isPersisted = gl.utils.convertPermissionToBoolean($row.attr('data-is-persisted'));
const isPersisted = convertPermissionToBoolean($row.attr('data-is-persisted'));
if (isPersisted) {
$row.hide();

View File

@ -1,4 +1,5 @@
import LinkedTabs from './lib/utils/bootstrap_linked_tabs';
import { setCiStatusFavicon } from './lib/utils/common_utils';
export default class Pipelines {
constructor(options = {}) {
@ -8,7 +9,7 @@ export default class Pipelines {
}
if (options.pipelineStatusUrl) {
gl.utils.setCiStatusFavicon(options.pipelineStatusUrl);
setCiStatusFavicon(options.pipelineStatusUrl);
}
}
}

View File

@ -4,6 +4,7 @@
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import navigationTabs from './navigation_tabs.vue';
import navigationControls from './nav_controls.vue';
import { convertPermissionToBoolean, getParameterByName, setParamInURL } from '../../lib/utils/common_utils';
export default {
props: {
@ -44,10 +45,10 @@
},
computed: {
canCreatePipelineParsed() {
return gl.utils.convertPermissionToBoolean(this.canCreatePipeline);
return convertPermissionToBoolean(this.canCreatePipeline);
},
scope() {
const scope = gl.utils.getParameterByName('scope');
const scope = getParameterByName('scope');
return scope === null ? 'all' : scope;
},
@ -105,10 +106,10 @@
};
},
pageParameter() {
return gl.utils.getParameterByName('page') || this.pagenum;
return getParameterByName('page') || this.pagenum;
},
scopeParameter() {
return gl.utils.getParameterByName('scope') || this.apiScope;
return getParameterByName('scope') || this.apiScope;
},
},
created() {
@ -122,7 +123,7 @@
* @param {Number} pageNumber desired page to go to.
*/
change(pageNumber) {
const param = gl.utils.setParamInURL('page', pageNumber);
const param = setParamInURL('page', pageNumber);
gl.utils.visitUrl(param);
return param;

View File

@ -1,3 +1,5 @@
import { parseIntPagination, normalizeHeaders } from '../../lib/utils/common_utils';
export default class PipelinesStore {
constructor() {
this.state = {};
@ -19,8 +21,8 @@ export default class PipelinesStore {
let paginationInfo;
if (Object.keys(pagination).length) {
const normalizedHeaders = gl.utils.normalizeHeaders(pagination);
paginationInfo = gl.utils.parseIntPagination(normalizedHeaders);
const normalizedHeaders = normalizeHeaders(pagination);
paginationInfo = parseIntPagination(normalizedHeaders);
} else {
paginationInfo = pagination;
}

View File

@ -1,6 +1,7 @@
/* eslint-disable class-methods-use-this, no-unneeded-ternary, quote-props */
import UsersSelect from './users_select';
import { isMetaClick } from './lib/utils/common_utils';
export default class Todos {
constructor() {
@ -141,7 +142,7 @@ export default class Todos {
return;
}
if (gl.utils.isMetaClick(e)) {
if (isMetaClick(e)) {
const windowTarget = '_blank';
const selected = e.target;
e.stopPropagation();

View File

@ -31,6 +31,7 @@ import {
SquashBeforeMerge,
notify,
} from './dependencies';
import { setFavicon } from '../lib/utils/common_utils';
export default {
el: '#js-vue-mr-widget',
@ -86,7 +87,7 @@ export default {
.then((res) => {
this.handleNotification(res);
this.mr.setData(res);
this.setFavicon();
this.setFaviconHelper();
if (cb) {
cb.call(null, res);
@ -115,9 +116,9 @@ export default {
immediateExecution: true,
});
},
setFavicon() {
setFaviconHelper() {
if (this.mr.ciStatusFaviconPath) {
gl.utils.setFavicon(this.mr.ciStatusFaviconPath);
setFavicon(this.mr.ciStatusFaviconPath);
}
},
fetchDeployments() {
@ -193,7 +194,7 @@ export default {
});
},
handleMounted() {
this.setFavicon();
this.setFaviconHelper();
this.initDeploymentsPolling();
},
},

View File

@ -0,0 +1,5 @@
---
title: Exports common_utils utility functions as modules
merge_request:
author:
type: other

View File

@ -14,6 +14,10 @@ describe('Environments Folder View', () => {
window.history.pushState({}, null, 'environments/folders/build');
});
afterEach(() => {
window.history.pushState({}, null, '');
});
let component;
describe('successfull request', () => {

View File

@ -18,26 +18,22 @@ describe('common_utils', () => {
});
describe('parseUrlPathname', () => {
beforeEach(() => {
spyOn(gl.utils, 'parseUrl').and.callFake(url => ({
pathname: url,
}));
});
it('returns an absolute url when given an absolute url', () => {
expect(commonUtils.parseUrlPathname('/some/absolute/url')).toEqual('/some/absolute/url');
});
it('returns an absolute url when given a relative url', () => {
expect(commonUtils.parseUrlPathname('some/relative/url')).toEqual('/some/relative/url');
});
});
describe('gl.utils.getUrlParamsArray', () => {
describe('getUrlParamsArray', () => {
it('should return params array', () => {
expect(gl.utils.getUrlParamsArray() instanceof Array).toBe(true);
expect(commonUtils.getUrlParamsArray() instanceof Array).toBe(true);
});
it('should remove the question mark from the search params', () => {
const paramsArray = gl.utils.getUrlParamsArray();
const paramsArray = commonUtils.getUrlParamsArray();
expect(paramsArray[0][0] !== '?').toBe(true);
});
@ -45,7 +41,7 @@ describe('common_utils', () => {
history.pushState('', '', '?label_name%5B%5D=test');
expect(
gl.utils.getUrlParamsArray()[0],
commonUtils.getUrlParamsArray()[0],
).toBe('label_name[]=test');
history.pushState('', '', '?');
@ -90,7 +86,7 @@ describe('common_utils', () => {
});
});
describe('gl.utils.setParamInURL', () => {
describe('setParamInURL', () => {
afterEach(() => {
window.history.pushState({}, null, '');
});
@ -98,40 +94,40 @@ describe('common_utils', () => {
it('should return the parameter', () => {
window.history.replaceState({}, null, '');
expect(gl.utils.setParamInURL('page', 156)).toBe('?page=156');
expect(gl.utils.setParamInURL('page', '156')).toBe('?page=156');
expect(commonUtils.setParamInURL('page', 156)).toBe('?page=156');
expect(commonUtils.setParamInURL('page', '156')).toBe('?page=156');
});
it('should update the existing parameter when its a number', () => {
window.history.pushState({}, null, '?page=15');
expect(gl.utils.setParamInURL('page', 16)).toBe('?page=16');
expect(gl.utils.setParamInURL('page', '16')).toBe('?page=16');
expect(gl.utils.setParamInURL('page', true)).toBe('?page=true');
expect(commonUtils.setParamInURL('page', 16)).toBe('?page=16');
expect(commonUtils.setParamInURL('page', '16')).toBe('?page=16');
expect(commonUtils.setParamInURL('page', true)).toBe('?page=true');
});
it('should update the existing parameter when its a string', () => {
window.history.pushState({}, null, '?scope=all');
expect(gl.utils.setParamInURL('scope', 'finished')).toBe('?scope=finished');
expect(commonUtils.setParamInURL('scope', 'finished')).toBe('?scope=finished');
});
it('should update the existing parameter when more than one parameter exists', () => {
window.history.pushState({}, null, '?scope=all&page=15');
expect(gl.utils.setParamInURL('scope', 'finished')).toBe('?scope=finished&page=15');
expect(commonUtils.setParamInURL('scope', 'finished')).toBe('?scope=finished&page=15');
});
it('should add a new parameter to the end of the existing ones', () => {
window.history.pushState({}, null, '?scope=all');
expect(gl.utils.setParamInURL('page', 16)).toBe('?scope=all&page=16');
expect(gl.utils.setParamInURL('page', '16')).toBe('?scope=all&page=16');
expect(gl.utils.setParamInURL('page', true)).toBe('?scope=all&page=true');
expect(commonUtils.setParamInURL('page', 16)).toBe('?scope=all&page=16');
expect(commonUtils.setParamInURL('page', '16')).toBe('?scope=all&page=16');
expect(commonUtils.setParamInURL('page', true)).toBe('?scope=all&page=true');
});
});
describe('gl.utils.getParameterByName', () => {
describe('getParameterByName', () => {
beforeEach(() => {
window.history.pushState({}, null, '?scope=all&p=2');
});
@ -141,33 +137,33 @@ describe('common_utils', () => {
});
it('should return valid parameter', () => {
const value = gl.utils.getParameterByName('scope');
expect(gl.utils.getParameterByName('p')).toEqual('2');
const value = commonUtils.getParameterByName('scope');
expect(commonUtils.getParameterByName('p')).toEqual('2');
expect(value).toBe('all');
});
it('should return invalid parameter', () => {
const value = gl.utils.getParameterByName('fakeParameter');
const value = commonUtils.getParameterByName('fakeParameter');
expect(value).toBe(null);
});
it('should return valid paramentes if URL is provided', () => {
let value = gl.utils.getParameterByName('foo', 'http://cocteau.twins/?foo=bar');
let value = commonUtils.getParameterByName('foo', 'http://cocteau.twins/?foo=bar');
expect(value).toBe('bar');
value = gl.utils.getParameterByName('manan', 'http://cocteau.twins/?foo=bar&manan=canchu');
value = commonUtils.getParameterByName('manan', 'http://cocteau.twins/?foo=bar&manan=canchu');
expect(value).toBe('canchu');
});
});
describe('gl.utils.normalizedHeaders', () => {
describe('normalizedHeaders', () => {
it('should upperCase all the header keys to keep them consistent', () => {
const apiHeaders = {
'X-Something-Workhorse': { workhorse: 'ok' },
'x-something-nginx': { nginx: 'ok' },
};
const normalized = gl.utils.normalizeHeaders(apiHeaders);
const normalized = commonUtils.normalizeHeaders(apiHeaders);
const WORKHORSE = 'X-SOMETHING-WORKHORSE';
const NGINX = 'X-SOMETHING-NGINX';
@ -177,14 +173,11 @@ describe('common_utils', () => {
});
});
describe('gl.utils.normalizeCRLFHeaders', () => {
describe('normalizeCRLFHeaders', () => {
beforeEach(function () {
this.CLRFHeaders = 'a-header: a-value\nAnother-Header: ANOTHER-VALUE\nLaSt-HeAdEr: last-VALUE';
spyOn(String.prototype, 'split').and.callThrough();
spyOn(gl.utils, 'normalizeHeaders').and.callThrough();
this.normalizeCRLFHeaders = gl.utils.normalizeCRLFHeaders(this.CLRFHeaders);
this.normalizeCRLFHeaders = commonUtils.normalizeCRLFHeaders(this.CLRFHeaders);
});
it('should split by newline', function () {
@ -195,10 +188,6 @@ describe('common_utils', () => {
expect(String.prototype.split.calls.allArgs().filter(args => args[0] === ': ').length).toBe(3);
});
it('should call gl.utils.normalizeHeaders with a parsed headers object', function () {
expect(gl.utils.normalizeHeaders).toHaveBeenCalledWith(jasmine.any(Object));
});
it('should return a normalized headers object', function () {
expect(this.normalizeCRLFHeaders).toEqual({
'A-HEADER': 'a-value',
@ -208,7 +197,7 @@ describe('common_utils', () => {
});
});
describe('gl.utils.parseIntPagination', () => {
describe('parseIntPagination', () => {
it('should parse to integers all string values and return pagination object', () => {
const pagination = {
'X-PER-PAGE': 10,
@ -228,11 +217,11 @@ describe('common_utils', () => {
previousPage: 1,
};
expect(gl.utils.parseIntPagination(pagination)).toEqual(expectedPagination);
expect(commonUtils.parseIntPagination(pagination)).toEqual(expectedPagination);
});
});
describe('gl.utils.isMetaClick', () => {
describe('isMetaClick', () => {
it('should identify meta click on Windows/Linux', () => {
const e = {
metaKey: false,
@ -240,7 +229,7 @@ describe('common_utils', () => {
which: 1,
};
expect(gl.utils.isMetaClick(e)).toBe(true);
expect(commonUtils.isMetaClick(e)).toBe(true);
});
it('should identify meta click on macOS', () => {
@ -250,7 +239,7 @@ describe('common_utils', () => {
which: 1,
};
expect(gl.utils.isMetaClick(e)).toBe(true);
expect(commonUtils.isMetaClick(e)).toBe(true);
});
it('should identify as meta click on middle-click or Mouse-wheel click', () => {
@ -260,7 +249,14 @@ describe('common_utils', () => {
which: 2,
};
expect(gl.utils.isMetaClick(e)).toBe(true);
expect(commonUtils.isMetaClick(e)).toBe(true);
});
});
describe('convertPermissionToBoolean', () => {
it('should convert a boolean in a string to a boolean', () => {
expect(commonUtils.convertPermissionToBoolean('true')).toEqual(true);
expect(commonUtils.convertPermissionToBoolean('false')).toEqual(false);
});
});
@ -333,53 +329,76 @@ describe('common_utils', () => {
});
});
describe('gl.utils.setFavicon', () => {
describe('setFavicon', () => {
beforeEach(() => {
const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon');
favicon.setAttribute('href', 'default/favicon');
document.body.appendChild(favicon);
});
afterEach(() => {
document.body.removeChild(document.getElementById('favicon'));
});
it('should set page favicon to provided favicon', () => {
const faviconPath = '//custom_favicon';
const fakeLink = {
setAttribute() {},
};
commonUtils.setFavicon(faviconPath);
spyOn(window.document, 'getElementById').and.callFake(() => fakeLink);
spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => {
expect(attr).toEqual('href');
expect(val.indexOf(faviconPath) > -1).toBe(true);
});
gl.utils.setFavicon(faviconPath);
expect(document.getElementById('favicon').getAttribute('href')).toEqual(faviconPath);
});
});
describe('gl.utils.resetFavicon', () => {
describe('resetFavicon', () => {
beforeEach(() => {
const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon');
favicon.setAttribute('href', 'default/favicon');
document.body.appendChild(favicon);
});
afterEach(() => {
document.body.removeChild(document.getElementById('favicon'));
});
it('should reset page favicon to tanuki', () => {
const fakeLink = {
setAttribute() {},
};
spyOn(window.document, 'getElementById').and.callFake(() => fakeLink);
spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => {
expect(attr).toEqual('href');
expect(val).toMatch(/favicon/);
});
gl.utils.resetFavicon();
commonUtils.resetFavicon();
expect(document.getElementById('favicon').getAttribute('href')).toEqual('default/favicon');
});
});
describe('gl.utils.setCiStatusFavicon', () => {
describe('setCiStatusFavicon', () => {
const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1/status.json`;
beforeEach(() => {
const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon');
document.body.appendChild(favicon);
});
afterEach(() => {
document.body.removeChild(document.getElementById('favicon'));
});
it('should reset favicon in case of error', () => {
const favicon = document.getElementById('favicon');
spyOn($, 'ajax').and.callFake(function (options) {
options.error();
expect(favicon.getAttribute('href')).toEqual('null');
});
commonUtils.setCiStatusFavicon(BUILD_URL);
});
it('should set page favicon to CI status favicon based on provided status', () => {
const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1/status.json`;
const FAVICON_PATH = '//icon_status_success';
const spySetFavicon = spyOn(gl.utils, 'setFavicon').and.stub();
const spyResetFavicon = spyOn(gl.utils, 'resetFavicon').and.stub();
const favicon = document.getElementById('favicon');
spyOn($, 'ajax').and.callFake(function (options) {
options.success({ favicon: FAVICON_PATH });
expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH);
options.success();
expect(spyResetFavicon).toHaveBeenCalled();
options.error();
expect(spyResetFavicon).toHaveBeenCalled();
expect(favicon.getAttribute('href')).toEqual(FAVICON_PATH);
});
gl.utils.setCiStatusFavicon(BUILD_URL);
commonUtils.setCiStatusFavicon(BUILD_URL);
});
});

View File

@ -78,8 +78,9 @@ import 'vendor/jquery.scrollTo';
});
describe('meta click', () => {
let metakeyEvent;
beforeEach(function () {
spyOn(gl.utils, 'isMetaClick').and.returnValue(true);
metakeyEvent = $.Event('click', { keyCode: 91, ctrlKey: true });
});
it('opens page when commits link is clicked', function () {
@ -89,7 +90,7 @@ import 'vendor/jquery.scrollTo';
});
this.class.bindEvents();
document.querySelector('.merge-request-tabs .commits-tab a').click();
$('.merge-request-tabs .commits-tab a').trigger(metakeyEvent);
});
it('opens page when commits badge is clicked', function () {
@ -99,7 +100,7 @@ import 'vendor/jquery.scrollTo';
});
this.class.bindEvents();
document.querySelector('.merge-request-tabs .commits-tab a .badge').click();
$('.merge-request-tabs .commits-tab a .badge').trigger(metakeyEvent);
});
});

View File

@ -26,9 +26,10 @@ describe('Todos', () => {
describe('meta click', () => {
let visitUrlSpy;
let metakeyEvent;
beforeEach(() => {
spyOn(gl.utils, 'isMetaClick').and.returnValue(true);
metakeyEvent = $.Event('click', { keyCode: 91, ctrlKey: true });
visitUrlSpy = spyOn(gl.utils, 'visitUrl').and.callFake(() => {});
});
@ -41,7 +42,7 @@ describe('Todos', () => {
done();
});
todoItem.click();
$('.todos-list .todo').trigger(metakeyEvent);
expect(visitUrlSpy).not.toHaveBeenCalled();
});

View File

@ -232,29 +232,42 @@ describe('mrWidgetOptions', () => {
describe('handleMounted', () => {
it('should call required methods to do the initial kick-off', () => {
spyOn(vm, 'initDeploymentsPolling');
spyOn(vm, 'setFavicon');
spyOn(vm, 'setFaviconHelper');
vm.handleMounted();
expect(vm.setFavicon).toHaveBeenCalled();
expect(vm.setFaviconHelper).toHaveBeenCalled();
expect(vm.initDeploymentsPolling).toHaveBeenCalled();
});
});
describe('setFavicon', () => {
let faviconElement;
beforeEach(() => {
const favicon = document.createElement('link');
favicon.setAttribute('id', 'favicon');
favicon.setAttribute('href', 'default/favicon');
document.body.appendChild(favicon);
faviconElement = document.body.getElementById('favicon');
});
afterEach(() => {
document.body.removeChild(document.getElementById('favicon'));
});
it('should call setFavicon method', () => {
spyOn(gl.utils, 'setFavicon');
vm.setFavicon();
expect(gl.utils.setFavicon).toHaveBeenCalledWith(vm.mr.ciStatusFaviconPath);
expect(faviconElement.getAttribute('href')).toEqual(vm.mr.ciStatusFaviconPath);
});
it('should not call setFavicon when there is no ciStatusFaviconPath', () => {
spyOn(gl.utils, 'setFavicon');
vm.mr.ciStatusFaviconPath = null;
vm.setFavicon();
vm.setFaviconHelper();
expect(gl.utils.setFavicon).not.toHaveBeenCalled();
expect(faviconElement.getAttribute('href')).toEqual(null);
});
});