summaryrefslogtreecommitdiff
path: root/spec/frontend/lib
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/lib')
-rw-r--r--spec/frontend/lib/utils/axios_utils_spec.js1
-rw-r--r--spec/frontend/lib/utils/common_utils_spec.js2
-rw-r--r--spec/frontend/lib/utils/csrf_token_spec.js57
-rw-r--r--spec/frontend/lib/utils/downloader_spec.js40
-rw-r--r--spec/frontend/lib/utils/navigation_utility_spec.js23
-rw-r--r--spec/frontend/lib/utils/poll_spec.js225
-rw-r--r--spec/frontend/lib/utils/sticky_spec.js77
-rw-r--r--spec/frontend/lib/utils/text_markdown_spec.js8
-rw-r--r--spec/frontend/lib/utils/url_utility_spec.js147
9 files changed, 552 insertions, 28 deletions
diff --git a/spec/frontend/lib/utils/axios_utils_spec.js b/spec/frontend/lib/utils/axios_utils_spec.js
index d5c39567f06..1585a38ae86 100644
--- a/spec/frontend/lib/utils/axios_utils_spec.js
+++ b/spec/frontend/lib/utils/axios_utils_spec.js
@@ -11,6 +11,7 @@ describe('axios_utils', () => {
mock = new AxiosMockAdapter(axios);
mock.onAny('/ok').reply(200);
mock.onAny('/err').reply(500);
+ // eslint-disable-next-line jest/no-standalone-expect
expect(axios.countActiveRequests()).toBe(0);
});
diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js
index 1edfda30fec..c8dc90c9ace 100644
--- a/spec/frontend/lib/utils/common_utils_spec.js
+++ b/spec/frontend/lib/utils/common_utils_spec.js
@@ -503,7 +503,7 @@ describe('common_utils', () => {
beforeEach(() => {
window.gon = window.gon || {};
- beforeGon = Object.assign({}, window.gon);
+ beforeGon = { ...window.gon };
window.gon.sprite_icons = 'icons.svg';
});
diff --git a/spec/frontend/lib/utils/csrf_token_spec.js b/spec/frontend/lib/utils/csrf_token_spec.js
new file mode 100644
index 00000000000..1b98ef126e9
--- /dev/null
+++ b/spec/frontend/lib/utils/csrf_token_spec.js
@@ -0,0 +1,57 @@
+import csrf from '~/lib/utils/csrf';
+import { setHTMLFixture } from 'helpers/fixtures';
+
+describe('csrf', () => {
+ let testContext;
+
+ beforeEach(() => {
+ testContext = {};
+ });
+
+ beforeEach(() => {
+ testContext.tokenKey = 'X-CSRF-Token';
+ testContext.token =
+ 'pH1cvjnP9grx2oKlhWEDvUZnJ8x2eXsIs1qzyHkF3DugSG5yTxR76CWeEZRhML2D1IeVB7NEW0t5l/axE4iJpQ==';
+ });
+
+ it('returns the correct headerKey', () => {
+ expect(csrf.headerKey).toBe(testContext.tokenKey);
+ });
+
+ describe('when csrf token is in the DOM', () => {
+ beforeEach(() => {
+ setHTMLFixture(`
+ <meta name="csrf-token" content="${testContext.token}">
+ `);
+
+ csrf.init();
+ });
+
+ it('returns the csrf token', () => {
+ expect(csrf.token).toBe(testContext.token);
+ });
+
+ it('returns the csrf headers object', () => {
+ expect(csrf.headers[testContext.tokenKey]).toBe(testContext.token);
+ });
+ });
+
+ describe('when csrf token is not in the DOM', () => {
+ beforeEach(() => {
+ setHTMLFixture(`
+ <meta name="some-other-token">
+ `);
+
+ csrf.init();
+ });
+
+ it('returns null for token', () => {
+ expect(csrf.token).toBeNull();
+ });
+
+ it('returns empty object for headers', () => {
+ expect(typeof csrf.headers).toBe('object');
+ expect(Object.keys(csrf.headers).length).toBe(0);
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/downloader_spec.js b/spec/frontend/lib/utils/downloader_spec.js
new file mode 100644
index 00000000000..c14cba3a62b
--- /dev/null
+++ b/spec/frontend/lib/utils/downloader_spec.js
@@ -0,0 +1,40 @@
+import downloader from '~/lib/utils/downloader';
+
+describe('Downloader', () => {
+ let a;
+
+ beforeEach(() => {
+ a = { click: jest.fn() };
+ jest.spyOn(document, 'createElement').mockImplementation(() => a);
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('when inline file content is provided', () => {
+ const fileData = 'inline content';
+ const fileName = 'test.csv';
+
+ it('uses the data urls to download the file', () => {
+ downloader({ fileName, fileData });
+ expect(document.createElement).toHaveBeenCalledWith('a');
+ expect(a.download).toBe(fileName);
+ expect(a.href).toBe(`data:text/plain;base64,${fileData}`);
+ expect(a.click).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('when an endpoint is provided', () => {
+ const url = 'https://gitlab.com/test.csv';
+ const fileName = 'test.csv';
+
+ it('uses the endpoint to download the file', () => {
+ downloader({ fileName, url });
+ expect(document.createElement).toHaveBeenCalledWith('a');
+ expect(a.download).toBe(fileName);
+ expect(a.href).toBe(url);
+ expect(a.click).toHaveBeenCalledTimes(1);
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/navigation_utility_spec.js b/spec/frontend/lib/utils/navigation_utility_spec.js
new file mode 100644
index 00000000000..88172f38894
--- /dev/null
+++ b/spec/frontend/lib/utils/navigation_utility_spec.js
@@ -0,0 +1,23 @@
+import findAndFollowLink from '~/lib/utils/navigation_utility';
+import { visitUrl } from '~/lib/utils/url_utility';
+
+jest.mock('~/lib/utils/url_utility');
+
+describe('findAndFollowLink', () => {
+ it('visits a link when the selector exists', () => {
+ const href = '/some/path';
+
+ setFixtures(`<a class="my-shortcut" href="${href}">link</a>`);
+
+ findAndFollowLink('.my-shortcut');
+
+ expect(visitUrl).toHaveBeenCalledWith(href);
+ });
+
+ it('does not throw an exception when the selector does not exist', () => {
+ // this should not throw an exception
+ findAndFollowLink('.this-selector-does-not-exist');
+
+ expect(visitUrl).not.toHaveBeenCalled();
+ });
+});
diff --git a/spec/frontend/lib/utils/poll_spec.js b/spec/frontend/lib/utils/poll_spec.js
new file mode 100644
index 00000000000..5ee9738ebf3
--- /dev/null
+++ b/spec/frontend/lib/utils/poll_spec.js
@@ -0,0 +1,225 @@
+import Poll from '~/lib/utils/poll';
+import { successCodes } from '~/lib/utils/http_status';
+import waitForPromises from 'helpers/wait_for_promises';
+
+describe('Poll', () => {
+ let callbacks;
+ let service;
+
+ function setup() {
+ return new Poll({
+ resource: service,
+ method: 'fetch',
+ successCallback: callbacks.success,
+ errorCallback: callbacks.error,
+ notificationCallback: callbacks.notification,
+ }).makeRequest();
+ }
+
+ const mockServiceCall = (response, shouldFail = false) => {
+ const value = {
+ ...response,
+ header: response.header || {},
+ };
+
+ if (shouldFail) {
+ service.fetch.mockRejectedValue(value);
+ } else {
+ service.fetch.mockResolvedValue(value);
+ }
+ };
+
+ const waitForAllCallsToFinish = (waitForCount, successCallback) => {
+ if (!waitForCount) {
+ return Promise.resolve().then(successCallback());
+ }
+
+ jest.runOnlyPendingTimers();
+
+ return waitForPromises().then(() => waitForAllCallsToFinish(waitForCount - 1, successCallback));
+ };
+
+ beforeEach(() => {
+ service = {
+ fetch: jest.fn(),
+ };
+ callbacks = {
+ success: jest.fn(),
+ error: jest.fn(),
+ notification: jest.fn(),
+ };
+ });
+
+ it('calls the success callback when no header for interval is provided', done => {
+ mockServiceCall({ status: 200 });
+ setup();
+
+ waitForAllCallsToFinish(1, () => {
+ expect(callbacks.success).toHaveBeenCalled();
+ expect(callbacks.error).not.toHaveBeenCalled();
+
+ done();
+ });
+ });
+
+ it('calls the error callback when the http request returns an error', done => {
+ mockServiceCall({ status: 500 }, true);
+ setup();
+
+ waitForAllCallsToFinish(1, () => {
+ expect(callbacks.success).not.toHaveBeenCalled();
+ expect(callbacks.error).toHaveBeenCalled();
+
+ done();
+ });
+ });
+
+ it('skips the error callback when request is aborted', done => {
+ mockServiceCall({ status: 0 }, true);
+ setup();
+
+ waitForAllCallsToFinish(1, () => {
+ expect(callbacks.success).not.toHaveBeenCalled();
+ expect(callbacks.error).not.toHaveBeenCalled();
+ expect(callbacks.notification).toHaveBeenCalled();
+
+ done();
+ });
+ });
+
+ it('should call the success callback when the interval header is -1', done => {
+ mockServiceCall({ status: 200, headers: { 'poll-interval': -1 } });
+ setup()
+ .then(() => {
+ expect(callbacks.success).toHaveBeenCalled();
+ expect(callbacks.error).not.toHaveBeenCalled();
+
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ describe('for 2xx status code', () => {
+ successCodes.forEach(httpCode => {
+ it(`starts polling when http status is ${httpCode} and interval header is provided`, done => {
+ mockServiceCall({ status: httpCode, headers: { 'poll-interval': 1 } });
+
+ const Polling = new Poll({
+ resource: service,
+ method: 'fetch',
+ data: { page: 1 },
+ successCallback: callbacks.success,
+ errorCallback: callbacks.error,
+ });
+
+ Polling.makeRequest();
+
+ waitForAllCallsToFinish(2, () => {
+ Polling.stop();
+
+ expect(service.fetch.mock.calls).toHaveLength(2);
+ expect(service.fetch).toHaveBeenCalledWith({ page: 1 });
+ expect(callbacks.success).toHaveBeenCalled();
+ expect(callbacks.error).not.toHaveBeenCalled();
+
+ done();
+ });
+ });
+ });
+ });
+
+ describe('stop', () => {
+ it('stops polling when method is called', done => {
+ mockServiceCall({ status: 200, headers: { 'poll-interval': 1 } });
+
+ const Polling = new Poll({
+ resource: service,
+ method: 'fetch',
+ data: { page: 1 },
+ successCallback: () => {
+ Polling.stop();
+ },
+ errorCallback: callbacks.error,
+ });
+
+ jest.spyOn(Polling, 'stop');
+
+ Polling.makeRequest();
+
+ waitForAllCallsToFinish(1, () => {
+ expect(service.fetch.mock.calls).toHaveLength(1);
+ expect(service.fetch).toHaveBeenCalledWith({ page: 1 });
+ expect(Polling.stop).toHaveBeenCalled();
+
+ done();
+ });
+ });
+ });
+
+ describe('enable', () => {
+ it('should enable polling upon a response', done => {
+ mockServiceCall({ status: 200 });
+ const Polling = new Poll({
+ resource: service,
+ method: 'fetch',
+ data: { page: 1 },
+ successCallback: () => {},
+ });
+
+ Polling.enable({
+ data: { page: 4 },
+ response: { status: 200, headers: { 'poll-interval': 1 } },
+ });
+
+ waitForAllCallsToFinish(1, () => {
+ Polling.stop();
+
+ expect(service.fetch.mock.calls).toHaveLength(1);
+ expect(service.fetch).toHaveBeenCalledWith({ page: 4 });
+ expect(Polling.options.data).toEqual({ page: 4 });
+ done();
+ });
+ });
+ });
+
+ describe('restart', () => {
+ it('should restart polling when its called', done => {
+ mockServiceCall({ status: 200, headers: { 'poll-interval': 1 } });
+
+ const Polling = new Poll({
+ resource: service,
+ method: 'fetch',
+ data: { page: 1 },
+ successCallback: () => {
+ Polling.stop();
+
+ // Let's pretend that we asynchronously restart this.
+ // setTimeout is mocked but this will actually get triggered
+ // in waitForAllCalssToFinish.
+ setTimeout(() => {
+ Polling.restart({ data: { page: 4 } });
+ }, 1);
+ },
+ errorCallback: callbacks.error,
+ });
+
+ jest.spyOn(Polling, 'stop');
+ jest.spyOn(Polling, 'enable');
+ jest.spyOn(Polling, 'restart');
+
+ Polling.makeRequest();
+
+ waitForAllCallsToFinish(2, () => {
+ Polling.stop();
+
+ expect(service.fetch.mock.calls).toHaveLength(2);
+ expect(service.fetch).toHaveBeenCalledWith({ page: 4 });
+ expect(Polling.stop).toHaveBeenCalled();
+ expect(Polling.enable).toHaveBeenCalled();
+ expect(Polling.restart).toHaveBeenCalled();
+ expect(Polling.options.data).toEqual({ page: 4 });
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/sticky_spec.js b/spec/frontend/lib/utils/sticky_spec.js
new file mode 100644
index 00000000000..4ad68cc9ff6
--- /dev/null
+++ b/spec/frontend/lib/utils/sticky_spec.js
@@ -0,0 +1,77 @@
+import { isSticky } from '~/lib/utils/sticky';
+import { setHTMLFixture } from 'helpers/fixtures';
+
+const TEST_OFFSET_TOP = 500;
+
+describe('sticky', () => {
+ let el;
+ let offsetTop;
+
+ beforeEach(() => {
+ setHTMLFixture(
+ `
+ <div class="parent">
+ <div id="js-sticky"></div>
+ </div>
+ `,
+ );
+
+ offsetTop = TEST_OFFSET_TOP;
+ el = document.getElementById('js-sticky');
+ Object.defineProperty(el, 'offsetTop', {
+ get() {
+ return offsetTop;
+ },
+ });
+ });
+
+ afterEach(() => {
+ el = null;
+ });
+
+ describe('when stuck', () => {
+ it('does not remove is-stuck class', () => {
+ isSticky(el, 0, el.offsetTop);
+ isSticky(el, 0, el.offsetTop);
+
+ expect(el.classList.contains('is-stuck')).toBeTruthy();
+ });
+
+ it('adds is-stuck class', () => {
+ isSticky(el, 0, el.offsetTop);
+
+ expect(el.classList.contains('is-stuck')).toBeTruthy();
+ });
+
+ it('inserts placeholder element', () => {
+ isSticky(el, 0, el.offsetTop, true);
+
+ expect(document.querySelector('.sticky-placeholder')).not.toBeNull();
+ });
+ });
+
+ describe('when not stuck', () => {
+ it('removes is-stuck class', () => {
+ jest.spyOn(el.classList, 'remove');
+
+ isSticky(el, 0, el.offsetTop);
+ isSticky(el, 0, 0);
+
+ expect(el.classList.remove).toHaveBeenCalledWith('is-stuck');
+ expect(el.classList.contains('is-stuck')).toBe(false);
+ });
+
+ it('does not add is-stuck class', () => {
+ isSticky(el, 0, 0);
+
+ expect(el.classList.contains('is-stuck')).toBeFalsy();
+ });
+
+ it('removes placeholder', () => {
+ isSticky(el, 0, el.offsetTop, true);
+ isSticky(el, 0, 0, true);
+
+ expect(document.querySelector('.sticky-placeholder')).toBeNull();
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js
index ba3e4020e66..1d616a7da0b 100644
--- a/spec/frontend/lib/utils/text_markdown_spec.js
+++ b/spec/frontend/lib/utils/text_markdown_spec.js
@@ -25,7 +25,7 @@ describe('init markdown', () => {
insertMarkdownText({
textArea,
text: textArea.value,
- tag: '*',
+ tag: '* ',
blockTag: null,
selected: '',
wrap: false,
@@ -43,7 +43,7 @@ describe('init markdown', () => {
insertMarkdownText({
textArea,
text: textArea.value,
- tag: '*',
+ tag: '* ',
blockTag: null,
selected: '',
wrap: false,
@@ -61,7 +61,7 @@ describe('init markdown', () => {
insertMarkdownText({
textArea,
text: textArea.value,
- tag: '*',
+ tag: '* ',
blockTag: null,
selected: '',
wrap: false,
@@ -79,7 +79,7 @@ describe('init markdown', () => {
insertMarkdownText({
textArea,
text: textArea.value,
- tag: '*',
+ tag: '* ',
blockTag: null,
selected: '',
wrap: false,
diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
index 4960895890f..c494033badd 100644
--- a/spec/frontend/lib/utils/url_utility_spec.js
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -91,36 +91,75 @@ describe('URL utility', () => {
});
describe('mergeUrlParams', () => {
+ const { mergeUrlParams } = urlUtils;
+
it('adds w', () => {
- 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(mergeUrlParams({ w: 1 }, '#frag')).toBe('?w=1#frag');
+ expect(mergeUrlParams({ w: 1 }, '')).toBe('?w=1');
+ 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(mergeUrlParams({ w: 'null' }, '')).toBe('?w=null');
+ });
- expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://h/p?k1=v1#frag')).toBe(
- 'https://h/p?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');
});
it('updates w', () => {
- expect(urlUtils.mergeUrlParams({ w: 1 }, '?k1=v1&w=0#frag')).toBe('?k1=v1&w=1#frag');
+ expect(mergeUrlParams({ w: 2 }, '/path?w=1#frag')).toBe('/path?w=2#frag');
+ expect(mergeUrlParams({ w: 2 }, 'https://host/path?w=1')).toBe('https://host/path?w=2');
});
- it('adds multiple params', () => {
- expect(urlUtils.mergeUrlParams({ a: 1, b: 2, c: 3 }, '#frag')).toBe('?a=1&b=2&c=3#frag');
+ it('removes null w', () => {
+ expect(mergeUrlParams({ w: null }, '?w=1#frag')).toBe('#frag');
+ expect(mergeUrlParams({ w: null }, '/path?w=1#frag')).toBe('/path#frag');
+ expect(mergeUrlParams({ w: null }, 'https://host/path?w=1')).toBe('https://host/path');
+ expect(mergeUrlParams({ w: null }, 'https://host/path?w=1#frag')).toBe(
+ 'https://host/path#frag',
+ );
+ expect(mergeUrlParams({ w: null }, 'https://h/p?k1=v1&w=1#frag')).toBe(
+ 'https://h/p?k1=v1#frag',
+ );
});
- it('adds and updates encoded params', () => {
- expect(urlUtils.mergeUrlParams({ a: '&', q: '?' }, '?a=%23#frag')).toBe('?a=%26&q=%3F#frag');
+ it('adds and updates encoded param values', () => {
+ expect(mergeUrlParams({ foo: '&', q: '?' }, '?foo=%23#frag')).toBe('?foo=%26&q=%3F#frag');
+ expect(mergeUrlParams({ foo: 'a value' }, '')).toBe('?foo=a%20value');
+ expect(mergeUrlParams({ foo: 'a value' }, '?foo=1')).toBe('?foo=a%20value');
+ });
+
+ it('adds and updates encoded param names', () => {
+ expect(mergeUrlParams({ 'a name': 1 }, '')).toBe('?a%20name=1');
+ expect(mergeUrlParams({ 'a name': 2 }, '?a%20name=1')).toBe('?a%20name=2');
+ expect(mergeUrlParams({ 'a name': null }, '?a%20name=1')).toBe('');
});
it('treats "+" as "%20"', () => {
- expect(urlUtils.mergeUrlParams({ ref: 'bogus' }, '?a=lorem+ipsum&ref=charlie')).toBe(
+ expect(mergeUrlParams({ ref: 'bogus' }, '?a=lorem+ipsum&ref=charlie')).toBe(
'?a=lorem%20ipsum&ref=bogus',
);
});
+
+ it('treats question marks and slashes as part of the query', () => {
+ expect(mergeUrlParams({ ending: '!' }, '?ending=?&foo=bar')).toBe('?ending=!&foo=bar');
+ expect(mergeUrlParams({ ending: '!' }, 'https://host/path?ending=?&foo=bar')).toBe(
+ 'https://host/path?ending=!&foo=bar',
+ );
+ expect(mergeUrlParams({ ending: '?' }, '?ending=!&foo=bar')).toBe('?ending=%3F&foo=bar');
+ expect(mergeUrlParams({ ending: '?' }, 'https://host/path?ending=!&foo=bar')).toBe(
+ 'https://host/path?ending=%3F&foo=bar',
+ );
+ expect(mergeUrlParams({ ending: '!', op: '+' }, '?ending=?&op=/')).toBe('?ending=!&op=%2B');
+ expect(mergeUrlParams({ ending: '!', op: '+' }, 'https://host/path?ending=?&op=/')).toBe(
+ 'https://host/path?ending=!&op=%2B',
+ );
+ expect(mergeUrlParams({ op: '+' }, '?op=/&foo=bar')).toBe('?op=%2B&foo=bar');
+ expect(mergeUrlParams({ op: '+' }, 'https://host/path?op=/&foo=bar')).toBe(
+ 'https://host/path?op=%2B&foo=bar',
+ );
+ });
});
describe('removeParams', () => {
@@ -284,20 +323,76 @@ describe('URL utility', () => {
});
});
- describe('isAbsoluteOrRootRelative', () => {
- const validUrls = ['https://gitlab.com/', 'http://gitlab.com/', '/users/sign_in'];
-
- const invalidUrls = [' https://gitlab.com/', './file/path', 'notanurl', '<a></a>'];
+ describe('isAbsolute', () => {
+ it.each`
+ url | valid
+ ${'https://gitlab.com/'} | ${true}
+ ${'http://gitlab.com/'} | ${true}
+ ${'/users/sign_in'} | ${false}
+ ${' https://gitlab.com'} | ${false}
+ ${'somepath.php?url=https://gitlab.com'} | ${false}
+ ${'notaurl'} | ${false}
+ ${'../relative_url'} | ${false}
+ ${'<a></a>'} | ${false}
+ `('returns $valid for $url', ({ url, valid }) => {
+ expect(urlUtils.isAbsolute(url)).toBe(valid);
+ });
+ });
- it.each(validUrls)(`returns true for %s`, url => {
- expect(urlUtils.isAbsoluteOrRootRelative(url)).toBe(true);
+ describe('isRootRelative', () => {
+ it.each`
+ url | valid
+ ${'https://gitlab.com/'} | ${false}
+ ${'http://gitlab.com/'} | ${false}
+ ${'/users/sign_in'} | ${true}
+ ${' https://gitlab.com'} | ${false}
+ ${'/somepath.php?url=https://gitlab.com'} | ${true}
+ ${'notaurl'} | ${false}
+ ${'../relative_url'} | ${false}
+ ${'<a></a>'} | ${false}
+ `('returns $valid for $url', ({ url, valid }) => {
+ expect(urlUtils.isRootRelative(url)).toBe(valid);
});
+ });
- it.each(invalidUrls)(`returns false for %s`, url => {
- expect(urlUtils.isAbsoluteOrRootRelative(url)).toBe(false);
+ describe('isAbsoluteOrRootRelative', () => {
+ it.each`
+ url | valid
+ ${'https://gitlab.com/'} | ${true}
+ ${'http://gitlab.com/'} | ${true}
+ ${'/users/sign_in'} | ${true}
+ ${' https://gitlab.com'} | ${false}
+ ${'/somepath.php?url=https://gitlab.com'} | ${true}
+ ${'notaurl'} | ${false}
+ ${'../relative_url'} | ${false}
+ ${'<a></a>'} | ${false}
+ `('returns $valid for $url', ({ url, valid }) => {
+ expect(urlUtils.isAbsoluteOrRootRelative(url)).toBe(valid);
});
});
+ describe('relativePathToAbsolute', () => {
+ it.each`
+ path | base | result
+ ${'./foo'} | ${'bar/'} | ${'/bar/foo'}
+ ${'../john.md'} | ${'bar/baz/foo.php'} | ${'/bar/john.md'}
+ ${'../images/img.png'} | ${'bar/baz/foo.php'} | ${'/bar/images/img.png'}
+ ${'../images/Image 1.png'} | ${'bar/baz/foo.php'} | ${'/bar/images/Image 1.png'}
+ ${'/images/img.png'} | ${'bar/baz/foo.php'} | ${'/images/img.png'}
+ ${'/images/img.png'} | ${'/bar/baz/foo.php'} | ${'/images/img.png'}
+ ${'../john.md'} | ${'/bar/baz/foo.php'} | ${'/bar/john.md'}
+ ${'../john.md'} | ${'///bar/baz/foo.php'} | ${'/bar/john.md'}
+ ${'/images/img.png'} | ${'https://gitlab.com/user/project/'} | ${'https://gitlab.com/images/img.png'}
+ ${'../images/img.png'} | ${'https://gitlab.com/user/project/'} | ${'https://gitlab.com/user/images/img.png'}
+ ${'../images/Image 1.png'} | ${'https://gitlab.com/user/project/'} | ${'https://gitlab.com/user/images/Image%201.png'}
+ `(
+ 'converts relative path "$path" with base "$base" to absolute path => "expected"',
+ ({ path, base, result }) => {
+ expect(urlUtils.relativePathToAbsolute(path, base)).toBe(result);
+ },
+ );
+ });
+
describe('isSafeUrl', () => {
const absoluteUrls = [
'http://example.org',
@@ -386,6 +481,12 @@ describe('URL utility', () => {
expect(urlUtils.queryToObject(searchQuery)).toEqual({ one: '1', two: '2' });
});
+
+ it('removes undefined values from the search query', () => {
+ const searchQuery = '?one=1&two=2&three';
+
+ expect(urlUtils.queryToObject(searchQuery)).toEqual({ one: '1', two: '2' });
+ });
});
describe('objectToQuery', () => {