Commit a3541a8d authored by Scott Escue's avatar Scott Escue Committed by Mike Greiling

Removing the URL manipulation functions added to 'common_utils.js' in favor of...

Removing the URL manipulation functions added to 'common_utils.js' in favor of the functions that already existed in 'url_utility.js'. Refactoring 'removeParams' function in 'url_utility.js' to allow url to be passed and to preserve the original host and/or path provided in the url.
parent 6b067fe4
......@@ -4,6 +4,14 @@ import { getLocationHash } from './url_utility';
import { convertToCamelCase } from './text_utility';
import { isObject } from './type_utility';
/**
* Simply returns `window.location`. This function exists to provide a means to spy
* `window.location` in unit tests.
*
* @returns {Location | string | any} The browser's `window.location`
*/
export const windowLocation = () => window.location;
export const getPagePath = (index = 0) => {
const page = $('body').attr('data-page') || '';
......@@ -180,87 +188,6 @@ export const urlParamsToObject = (path = '') =>
return data;
}, {});
/**
* Apply the query param and value to the given url by returning a new url string that includes
* the param/value pair. If the given url already includes the query param, the query param value
* will be updated in the new url string. Otherwise, the query param and value will by added in
* the new url string.
*
* @param url {string} - url to which the query param will be applied
* @param param {string} - name of the query param to set
* @param value {string|number} - value to give the query param
* @returns {string} A copy of the original url with the new or updated query param
*/
export const setUrlParam = (url, param, value) => {
const [rootAndQuery, fragment] = url.split('#');
const [root, query] = rootAndQuery.split('?');
const encodedParam = encodeURIComponent(param);
const encodedPair = `${encodedParam}=${encodeURIComponent(value)}`;
let paramExists = false;
const paramArray =
(query ? query.split('&') : [])
.map(paramPair => {
const [foundParam] = paramPair.split('=');
if (foundParam === encodedParam) {
paramExists = true;
return encodedPair;
}
return paramPair;
});
if (paramExists === false) {
paramArray.push(encodedPair);
}
const writableFragment = fragment ? `#${fragment}` : '';
return `${root}?${paramArray.join('&')}${writableFragment}`;
};
/**
* Remove the query param from the given url by returning a new url string that no longer includes
* the param/value pair.
*
* @param url {string} - url from which the query param will be removed
* @param param {string} - the name of the query param to remove
* @returns {string} A copy of the original url but without the query param
*/
export const removeUrlParam = (url, param) => {
const [rootAndQuery, fragment] = url.split('#');
const [root, query] = rootAndQuery.split('?');
if (query === undefined) {
return url;
}
const encodedParam = encodeURIComponent(param);
const updatedQuery = query
.split('&')
.filter(paramPair => {
const [foundParam] = paramPair.split('=');
return foundParam !== encodedParam;
})
.join('&');
const writableQuery = updatedQuery.length > 0 ? `?${updatedQuery}` : '';
const writableFragment = fragment ? `#${fragment}` : '';
return `${root}${writableQuery}${writableFragment}`;
};
/**
* Apply the fragment to the given url by returning a new url string that includes
* the fragment. If the given url already contains a fragment, the original fragment
* will be removed.
*
* @param url {string} - url to which the fragment will be applied
* @param fragment {string} - fragment to append
*/
export const setUrlFragment = (url, fragment) => {
const [rootUrl] = url.split('#');
const encodedFragment = encodeURIComponent(fragment.replace(/^#/, ''));
return `${rootUrl}#${encodedFragment}`;
};
export const isMetaKey = e => e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
// Identify following special clicks
......
import { windowLocation } from './common_utils';
// Returns an array containing the value(s) of the
// of the key passed as an argument
export function getParameterValues(sParam) {
......@@ -42,22 +44,35 @@ export function mergeUrlParams(params, url) {
return `${urlparts[1]}?${query}${urlparts[3]}`;
}
export function removeParamQueryString(url, param) {
const decodedUrl = decodeURIComponent(url);
const urlVariables = decodedUrl.split('&');
return urlVariables.filter(variable => variable.indexOf(param) === -1).join('&');
}
export function removeParams(params, source = window.location.href) {
const url = document.createElement('a');
url.href = source;
/**
* Removes specified query params from the url by returning a new url string that no longer
* includes the param/value pair. If no url is provided, `window.location.href` is used as
* the default value.
*
* @param {string[]} params - the query param names to remove
* @param {string} [url=windowLocation().href] - url from which the query param will be removed
* @returns {string} A copy of the original url but without the query param
*/
export function removeParams(params, url = windowLocation().href) {
const [rootAndQuery, fragment] = url.split('#');
const [root, query] = rootAndQuery.split('?');
if (query === undefined) {
return url;
}
params.forEach(param => {
url.search = removeParamQueryString(url.search, param);
});
const encodedParams = params.map(param => encodeURIComponent(param));
const updatedQuery = query
.split('&')
.filter(paramPair => {
const [foundParam] = paramPair.split('=');
return encodedParams.indexOf(foundParam) < 0;
})
.join('&');
return url.href;
const writableQuery = updatedQuery.length > 0 ? `?${updatedQuery}` : '';
const writableFragment = fragment ? `#${fragment}` : '';
return `${root}${writableQuery}${writableFragment}`;
}
export function getLocationHash(url = window.location.href) {
......@@ -66,6 +81,20 @@ export function getLocationHash(url = window.location.href) {
return hashIndex === -1 ? null : url.substring(hashIndex + 1);
}
/**
* Apply the fragment to the given url by returning a new url string that includes
* the fragment. If the given url already contains a fragment, the original fragment
* will be removed.
*
* @param {string} url - url to which the fragment will be applied
* @param {string} fragment - fragment to append
*/
export const setUrlFragment = (url, fragment) => {
const [rootUrl] = url.split('#');
const encodedFragment = encodeURIComponent(fragment.replace(/^#/, ''));
return `${rootUrl}#${encodedFragment}`;
};
export function visitUrl(url, external = false) {
if (external) {
// Simulate `target="blank" rel="noopener noreferrer"`
......
import $ from 'jquery';
import { setUrlParam, removeUrlParam } from '~/lib/utils/common_utils';
import { mergeUrlParams, removeParams } from '~/lib/utils/url_utility';
/**
* OAuth-based login buttons have a separate "remember me" checkbox.
......@@ -25,9 +25,9 @@ export default class OAuthRememberMe {
const href = $(element).attr('href');
if (rememberMe) {
$(element).attr('href', setUrlParam(href, 'remember_me', 1));
$(element).attr('href', mergeUrlParams({ remember_me: 1 }, href));
} else {
$(element).attr('href', removeUrlParam(href, 'remember_me'));
$(element).attr('href', removeParams(['remember_me'], href));
}
});
}
......
import { setUrlFragment, setUrlParam } from '../../../lib/utils/common_utils';
import { mergeUrlParams, setUrlFragment } from '~/lib/utils/url_utility';
/**
* Ensure the given URL fragment is preserved by appending it to sign-in/sign-up form actions and
......@@ -22,7 +22,7 @@ export default function preserveUrlFragment(fragment) {
// query param will be available in the omniauth callback upon successful authentication
const anchors = document.querySelectorAll('#signin-container a.oauth-login');
Array.prototype.forEach.call(anchors, (anchor) => {
const newHref = setUrlParam(anchor.getAttribute('href'), 'redirect_fragment', normalFragment);
const newHref = mergeUrlParams({ redirect_fragment: normalFragment }, anchor.getAttribute('href'));
anchor.setAttribute('href', newHref);
});
}
......
......@@ -65,67 +65,6 @@ describe('common_utils', () => {
});
});
describe('setUrlParam', () => {
it('should append param when url has no other params', () => {
const url = commonUtils.setUrlParam('/feature/home', 'newParam', 'yes');
expect(url).toBe('/feature/home?newParam=yes');
});
it('should append param when url has other params', () => {
const url = commonUtils.setUrlParam('/feature/home?showAll=true', 'newParam', 'yes');
expect(url).toBe('/feature/home?showAll=true&newParam=yes');
});
it('should replace param when url contains the param', () => {
const url = commonUtils.setUrlParam('/feature/home?showAll=true&limit=5', 'limit', '100');
expect(url).toBe('/feature/home?showAll=true&limit=100');
});
it('should update param and preserve fragment', () => {
const url = commonUtils.setUrlParam('/home?q=no&limit=5&showAll=true#H1', 'limit', '100');
expect(url).toBe('/home?q=no&limit=100&showAll=true#H1');
});
});
describe('removeUrlParam', () => {
it('should remove param when url has no other params', () => {
const url = commonUtils.removeUrlParam('/feature/home?size=5', 'size');
expect(url).toBe('/feature/home');
});
it('should remove param when url has other params', () => {
const url = commonUtils.removeUrlParam('/feature/home?q=1&size=5&f=html', 'size');
expect(url).toBe('/feature/home?q=1&f=html');
});
it('should remove param and preserve fragment', () => {
const url = commonUtils.removeUrlParam('/feature/home?size=5#H2', 'size');
expect(url).toBe('/feature/home#H2');
});
it('should not modify url if param does not exist', () => {
const url = commonUtils.removeUrlParam('/feature/home?q=1&size=5&f=html', 'locale');
expect(url).toBe('/feature/home?q=1&size=5&f=html');
});
});
describe('setUrlFragment', () => {
it('should set fragment when url has no fragment', () => {
const url = commonUtils.setUrlFragment('/home/feature', 'usage');
expect(url).toBe('/home/feature#usage');
});
it('should set fragment when url has existing fragment', () => {
const url = commonUtils.setUrlFragment('/home/feature#overview', 'usage');
expect(url).toBe('/home/feature#usage');
});
it('should set fragment when given fragment includes #', () => {
const url = commonUtils.setUrlFragment('/home/feature#overview', '#install');
expect(url).toBe('/home/feature#install');
});
});
describe('handleLocationHash', () => {
beforeEach(() => {
spyOn(window.document, 'getElementById').and.callThrough();
......
import { webIDEUrl, mergeUrlParams } from '~/lib/utils/url_utility';
import UrlUtility, * as urlUtils from '~/lib/utils/url_utility';
describe('URL utility', () => {
describe('webIDEUrl', () => {
......@@ -8,7 +8,7 @@ describe('URL utility', () => {
describe('without relative_url_root', () => {
it('returns IDE path with route', () => {
expect(webIDEUrl('/gitlab-org/gitlab-ce/merge_requests/1')).toBe(
expect(urlUtils.webIDEUrl('/gitlab-org/gitlab-ce/merge_requests/1')).toBe(
'/-/ide/project/gitlab-org/gitlab-ce/merge_requests/1',
);
});
......@@ -20,7 +20,7 @@ describe('URL utility', () => {
});
it('returns IDE path with route', () => {
expect(webIDEUrl('/gitlab/gitlab-org/gitlab-ce/merge_requests/1')).toBe(
expect(urlUtils.webIDEUrl('/gitlab/gitlab-org/gitlab-ce/merge_requests/1')).toBe(
'/gitlab/-/ide/project/gitlab-org/gitlab-ce/merge_requests/1',
);
});
......@@ -29,23 +29,83 @@ describe('URL utility', () => {
describe('mergeUrlParams', () => {
it('adds w', () => {
expect(mergeUrlParams({ w: 1 }, '#frag')).toBe('?w=1#frag');
expect(mergeUrlParams({ w: 1 }, '/path#frag')).toBe('/path?w=1#frag');
expect(mergeUrlParams({ w: 1 }, 'https://host/path')).toBe('https://host/path?w=1');
expect(mergeUrlParams({ w: 1 }, 'https://host/path#frag')).toBe('https://host/path?w=1#frag');
expect(mergeUrlParams({ w: 1 }, 'https://h/p?k1=v1#frag')).toBe('https://h/p?k1=v1&w=1#frag');
expect(urlUtils.mergeUrlParams({ w: 1 }, '#frag')).toBe('?w=1#frag');
expect(urlUtils.mergeUrlParams({ w: 1 }, '/path#frag')).toBe('/path?w=1#frag');
expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://host/path')).toBe('https://host/path?w=1');
expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://host/path#frag')).toBe('https://host/path?w=1#frag');
expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://h/p?k1=v1#frag')).toBe('https://h/p?k1=v1&w=1#frag');
});
it('updates w', () => {
expect(mergeUrlParams({ w: 1 }, '?k1=v1&w=0#frag')).toBe('?k1=v1&w=1#frag');
expect(urlUtils.mergeUrlParams({ w: 1 }, '?k1=v1&w=0#frag')).toBe('?k1=v1&w=1#frag');
});
it('adds multiple params', () => {
expect(mergeUrlParams({ a: 1, b: 2, c: 3 }, '#frag')).toBe('?a=1&b=2&c=3#frag');
expect(urlUtils.mergeUrlParams({ a: 1, b: 2, c: 3 }, '#frag')).toBe('?a=1&b=2&c=3#frag');
});
it('adds and updates encoded params', () => {
expect(mergeUrlParams({ a: '&', q: '?' }, '?a=%23#frag')).toBe('?a=%26&q=%3F#frag');
expect(urlUtils.mergeUrlParams({ a: '&', q: '?' }, '?a=%23#frag')).toBe('?a=%26&q=%3F#frag');
});
});
describe('removeParams', () => {
describe('when url is passed', () => {
it('removes query param with encoded ampersand', () => {
const url = urlUtils.removeParams(['filter'], '/mail?filter=n%3Djoe%26l%3Dhome');
expect(url).toBe('/mail');
});
it('should remove param when url has no other params', () => {
const url = urlUtils.removeParams(['size'], '/feature/home?size=5');
expect(url).toBe('/feature/home');
});
it('should remove param when url has other params', () => {
const url = urlUtils.removeParams(['size'], '/feature/home?q=1&size=5&f=html');
expect(url).toBe('/feature/home?q=1&f=html');
});
it('should remove param and preserve fragment', () => {
const url = urlUtils.removeParams(['size'], '/feature/home?size=5#H2');
expect(url).toBe('/feature/home#H2');
});
it('should remove multiple params', () => {
const url = urlUtils.removeParams(['z', 'a'], '/home?z=11111&l=en_US&a=true#H2');
expect(url).toBe('/home?l=en_US#H2');
});
});
describe('when no url is passed', () => {
it('should remove params from window.location.href', () => {
spyOnDependency(UrlUtility, 'windowLocation').and.callFake(() => {
const anchor = document.createElement('a');
anchor.href = 'https://mysite.com/?zip=11111&locale=en_US&ads=false#privacy';
return anchor;
});
const url = urlUtils.removeParams(['locale']);
expect(url).toBe('https://mysite.com/?zip=11111&ads=false#privacy');
});
});
});
describe('setUrlFragment', () => {
it('should set fragment when url has no fragment', () => {
const url = urlUtils.setUrlFragment('/home/feature', 'usage');
expect(url).toBe('/home/feature#usage');
});
it('should set fragment when url has existing fragment', () => {
const url = urlUtils.setUrlFragment('/home/feature#overview', 'usage');
expect(url).toBe('/home/feature#usage');
});
it('should set fragment when given fragment includes #', () => {
const url = urlUtils.setUrlFragment('/home/feature#overview', '#install');
expect(url).toBe('/home/feature#install');
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment