summaryrefslogtreecommitdiff
path: root/spec/frontend/lib/utils
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/lib/utils')
-rw-r--r--spec/frontend/lib/utils/axios_startup_calls_spec.js131
-rw-r--r--spec/frontend/lib/utils/datetime_utility_spec.js14
-rw-r--r--spec/frontend/lib/utils/forms_spec.js44
-rw-r--r--spec/frontend/lib/utils/text_markdown_spec.js34
-rw-r--r--spec/frontend/lib/utils/text_utility_spec.js21
-rw-r--r--spec/frontend/lib/utils/url_utility_spec.js38
6 files changed, 276 insertions, 6 deletions
diff --git a/spec/frontend/lib/utils/axios_startup_calls_spec.js b/spec/frontend/lib/utils/axios_startup_calls_spec.js
new file mode 100644
index 00000000000..e804cae7914
--- /dev/null
+++ b/spec/frontend/lib/utils/axios_startup_calls_spec.js
@@ -0,0 +1,131 @@
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import setupAxiosStartupCalls from '~/lib/utils/axios_startup_calls';
+
+describe('setupAxiosStartupCalls', () => {
+ const AXIOS_RESPONSE = { text: 'AXIOS_RESPONSE' };
+ const STARTUP_JS_RESPONSE = { text: 'STARTUP_JS_RESPONSE' };
+ let mock;
+
+ function mockFetchCall(status) {
+ const p = {
+ ok: status >= 200 && status < 300,
+ status,
+ headers: new Headers({ 'Content-Type': 'application/json' }),
+ statusText: `MOCK-FETCH ${status}`,
+ clone: () => p,
+ json: () => Promise.resolve(STARTUP_JS_RESPONSE),
+ };
+ return Promise.resolve(p);
+ }
+
+ function mockConsoleWarn() {
+ jest.spyOn(console, 'warn').mockImplementation();
+ }
+
+ function expectConsoleWarn(path) {
+ // eslint-disable-next-line no-console
+ expect(console.warn).toHaveBeenCalledWith(expect.stringMatching(path), expect.any(Error));
+ }
+
+ beforeEach(() => {
+ window.gl = {};
+ mock = new MockAdapter(axios);
+ mock.onGet('/non-startup').reply(200, AXIOS_RESPONSE);
+ mock.onGet('/startup').reply(200, AXIOS_RESPONSE);
+ mock.onGet('/startup-failing').reply(200, AXIOS_RESPONSE);
+ });
+
+ afterEach(() => {
+ delete window.gl;
+ axios.interceptors.request.handlers = [];
+ mock.restore();
+ });
+
+ it('if no startupCalls are registered: does not register a request interceptor', () => {
+ setupAxiosStartupCalls(axios);
+
+ expect(axios.interceptors.request.handlers.length).toBe(0);
+ });
+
+ describe('if startupCalls are registered', () => {
+ beforeEach(() => {
+ window.gl.startup_calls = {
+ '/startup': {
+ fetchCall: mockFetchCall(200),
+ },
+ '/startup-failing': {
+ fetchCall: mockFetchCall(400),
+ },
+ };
+ setupAxiosStartupCalls(axios);
+ });
+
+ it('registers a request interceptor', () => {
+ expect(axios.interceptors.request.handlers.length).toBe(1);
+ });
+
+ it('detaches the request interceptor if every startup call has been made', async () => {
+ expect(axios.interceptors.request.handlers[0]).not.toBeNull();
+
+ await axios.get('/startup');
+ mockConsoleWarn();
+ await axios.get('/startup-failing');
+
+ // Axios sets the interceptor to null
+ expect(axios.interceptors.request.handlers[0]).toBeNull();
+ });
+
+ it('delegates to startup calls if URL is registered and call is successful', async () => {
+ const { headers, data, status, statusText } = await axios.get('/startup');
+
+ expect(headers).toEqual({ 'content-type': 'application/json' });
+ expect(status).toBe(200);
+ expect(statusText).toBe('MOCK-FETCH 200');
+ expect(data).toEqual(STARTUP_JS_RESPONSE);
+ expect(data).not.toEqual(AXIOS_RESPONSE);
+ });
+
+ it('delegates to startup calls exactly once', async () => {
+ await axios.get('/startup');
+ const { data } = await axios.get('/startup');
+
+ expect(data).not.toEqual(STARTUP_JS_RESPONSE);
+ expect(data).toEqual(AXIOS_RESPONSE);
+ });
+
+ it('does not delegate to startup calls if the call is failing', async () => {
+ mockConsoleWarn();
+ const { data } = await axios.get('/startup-failing');
+
+ expect(data).not.toEqual(STARTUP_JS_RESPONSE);
+ expect(data).toEqual(AXIOS_RESPONSE);
+ expectConsoleWarn('/startup-failing');
+ });
+
+ it('does not delegate to startup call if URL is not registered', async () => {
+ const { data } = await axios.get('/non-startup');
+
+ expect(data).toEqual(AXIOS_RESPONSE);
+ expect(data).not.toEqual(STARTUP_JS_RESPONSE);
+ });
+ });
+
+ it('removes GitLab Base URL from startup call', async () => {
+ const oldGon = window.gon;
+ window.gon = { gitlab_url: 'https://example.org/gitlab' };
+
+ window.gl.startup_calls = {
+ '/startup': {
+ fetchCall: mockFetchCall(200),
+ },
+ };
+ setupAxiosStartupCalls(axios);
+
+ const { data } = await axios.get('https://example.org/gitlab/startup');
+
+ expect(data).toEqual(STARTUP_JS_RESPONSE);
+
+ window.gon = oldGon;
+ });
+});
diff --git a/spec/frontend/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js
index 9eb5587e83c..5b1fdea058b 100644
--- a/spec/frontend/lib/utils/datetime_utility_spec.js
+++ b/spec/frontend/lib/utils/datetime_utility_spec.js
@@ -653,3 +653,17 @@ describe('differenceInSeconds', () => {
expect(datetimeUtility.differenceInSeconds(startDate, endDate)).toBe(expected);
});
});
+
+describe('differenceInMilliseconds', () => {
+ const startDateTime = new Date('2019-07-17T00:00:00.000Z');
+
+ it.each`
+ startDate | endDate | expected
+ ${startDateTime.getTime()} | ${new Date('2019-07-17T00:00:00.000Z')} | ${0}
+ ${startDateTime} | ${new Date('2019-07-17T12:00:00.000Z').getTime()} | ${43200000}
+ ${startDateTime} | ${new Date('2019-07-18T00:00:00.000Z').getTime()} | ${86400000}
+ ${new Date('2019-07-18T00:00:00.000Z')} | ${startDateTime.getTime()} | ${-86400000}
+ `('returns $expected for $endDate - $startDate', ({ startDate, endDate, expected }) => {
+ expect(datetimeUtility.differenceInMilliseconds(startDate, endDate)).toBe(expected);
+ });
+});
diff --git a/spec/frontend/lib/utils/forms_spec.js b/spec/frontend/lib/utils/forms_spec.js
index 07ba7c29dfc..a69be99ab98 100644
--- a/spec/frontend/lib/utils/forms_spec.js
+++ b/spec/frontend/lib/utils/forms_spec.js
@@ -1,4 +1,4 @@
-import { serializeForm } from '~/lib/utils/forms';
+import { serializeForm, serializeFormObject, isEmptyValue } from '~/lib/utils/forms';
describe('lib/utils/forms', () => {
const createDummyForm = inputs => {
@@ -93,4 +93,46 @@ describe('lib/utils/forms', () => {
});
});
});
+
+ describe('isEmptyValue', () => {
+ it.each`
+ input | returnValue
+ ${''} | ${true}
+ ${[]} | ${true}
+ ${null} | ${true}
+ ${undefined} | ${true}
+ ${'hello'} | ${false}
+ ${' '} | ${false}
+ ${0} | ${false}
+ `('returns $returnValue for value $input', ({ input, returnValue }) => {
+ expect(isEmptyValue(input)).toBe(returnValue);
+ });
+ });
+
+ describe('serializeFormObject', () => {
+ it('returns an serialized object', () => {
+ const form = {
+ profileName: { value: 'hello', state: null, feedback: null },
+ spiderTimeout: { value: 2, state: true, feedback: null },
+ targetTimeout: { value: 12, state: true, feedback: null },
+ };
+ expect(serializeFormObject(form)).toEqual({
+ profileName: 'hello',
+ spiderTimeout: 2,
+ targetTimeout: 12,
+ });
+ });
+
+ it('returns only the entries with value', () => {
+ const form = {
+ profileName: { value: '', state: null, feedback: null },
+ spiderTimeout: { value: 0, state: null, feedback: null },
+ targetTimeout: { value: null, state: null, feedback: null },
+ name: { value: undefined, state: null, feedback: null },
+ };
+ expect(serializeFormObject(form)).toEqual({
+ spiderTimeout: 0,
+ });
+ });
+ });
});
diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js
index 2e52958a828..1aaae80dcdf 100644
--- a/spec/frontend/lib/utils/text_markdown_spec.js
+++ b/spec/frontend/lib/utils/text_markdown_spec.js
@@ -1,4 +1,4 @@
-import { insertMarkdownText } from '~/lib/utils/text_markdown';
+import { insertMarkdownText, keypressNoteText } from '~/lib/utils/text_markdown';
describe('init markdown', () => {
let textArea;
@@ -115,14 +115,15 @@ describe('init markdown', () => {
describe('with selection', () => {
const text = 'initial selected value';
const selected = 'selected';
+ let selectedIndex;
+
beforeEach(() => {
textArea.value = text;
- const selectedIndex = text.indexOf(selected);
+ selectedIndex = text.indexOf(selected);
textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length);
});
it('applies the tag to the selected value', () => {
- const selectedIndex = text.indexOf(selected);
const tag = '*';
insertMarkdownText({
@@ -153,6 +154,29 @@ describe('init markdown', () => {
expect(textArea.value).toEqual(text.replace(selected, `[${selected}](url)`));
});
+ it.each`
+ key | expected
+ ${'['} | ${`[${selected}]`}
+ ${'*'} | ${`**${selected}**`}
+ ${"'"} | ${`'${selected}'`}
+ ${'_'} | ${`_${selected}_`}
+ ${'`'} | ${`\`${selected}\``}
+ ${'"'} | ${`"${selected}"`}
+ ${'{'} | ${`{${selected}}`}
+ ${'('} | ${`(${selected})`}
+ ${'<'} | ${`<${selected}>`}
+ `('generates $expected when $key is pressed', ({ key, expected }) => {
+ const event = new KeyboardEvent('keydown', { key });
+
+ textArea.addEventListener('keydown', keypressNoteText);
+ textArea.dispatchEvent(event);
+
+ expect(textArea.value).toEqual(text.replace(selected, expected));
+
+ // cursor placement should be after selection + 2 tag lengths
+ expect(textArea.selectionStart).toBe(selectedIndex + expected.length);
+ });
+
describe('and text to be selected', () => {
const tag = '[{text}](url)';
const select = 'url';
@@ -178,7 +202,7 @@ describe('init markdown', () => {
it('selects the right text when multiple tags are present', () => {
const initialValue = `${tag} ${tag} ${selected}`;
textArea.value = initialValue;
- const selectedIndex = initialValue.indexOf(selected);
+ selectedIndex = initialValue.indexOf(selected);
textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length);
insertMarkdownText({
textArea,
@@ -204,7 +228,7 @@ describe('init markdown', () => {
const initialValue = `text ${expectedUrl} text`;
textArea.value = initialValue;
- const selectedIndex = initialValue.indexOf(expectedUrl);
+ selectedIndex = initialValue.indexOf(expectedUrl);
textArea.setSelectionRange(selectedIndex, selectedIndex + expectedUrl.length);
insertMarkdownText({
diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js
index 285f7d04c3b..6fef5f6b63c 100644
--- a/spec/frontend/lib/utils/text_utility_spec.js
+++ b/spec/frontend/lib/utils/text_utility_spec.js
@@ -205,6 +205,27 @@ describe('text_utility', () => {
});
});
+ describe('convertUnicodeToAscii', () => {
+ it('does nothing on an empty string', () => {
+ expect(textUtils.convertUnicodeToAscii('')).toBe('');
+ });
+
+ it('does nothing on an already ascii string', () => {
+ expect(textUtils.convertUnicodeToAscii('The quick brown fox jumps over the lazy dog.')).toBe(
+ 'The quick brown fox jumps over the lazy dog.',
+ );
+ });
+
+ it('replaces Unicode characters', () => {
+ expect(textUtils.convertUnicodeToAscii('Dĭd söméònê äšk fœŕ Ůnĭċődę?')).toBe(
+ 'Did soemeone aesk foer Unicode?',
+ );
+
+ expect(textUtils.convertUnicodeToAscii("Jürgen's Projekt")).toBe("Juergen's Projekt");
+ expect(textUtils.convertUnicodeToAscii('öäüÖÄÜ')).toBe('oeaeueOeAeUe');
+ });
+ });
+
describe('splitCamelCase', () => {
it('separates a PascalCase word to two', () => {
expect(textUtils.splitCamelCase('HelloWorld')).toBe('Hello World');
diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
index a13ac3778cf..869ae274a3f 100644
--- a/spec/frontend/lib/utils/url_utility_spec.js
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -161,6 +161,15 @@ describe('URL utility', () => {
);
});
+ it('sorts params in alphabetical order with sort option', () => {
+ expect(mergeUrlParams({ c: 'c', b: 'b', a: 'a' }, 'https://host/path', { sort: true })).toBe(
+ 'https://host/path?a=a&b=b&c=c',
+ );
+ expect(
+ mergeUrlParams({ alpha: 'alpha' }, 'https://host/path?op=/&foo=bar', { sort: true }),
+ ).toBe('https://host/path?alpha=alpha&foo=bar&op=%2F');
+ });
+
describe('with spread array option', () => {
const spreadArrayOptions = { spreadArrays: true };
@@ -616,6 +625,35 @@ describe('URL utility', () => {
expect(urlUtils.queryToObject(searchQuery)).toEqual({ one: '1', two: '2' });
});
+
+ describe('with gatherArrays=false', () => {
+ it('overwrites values with the same array-key and does not change the key', () => {
+ const searchQuery = '?one[]=1&one[]=2&two=2&two=3';
+
+ expect(urlUtils.queryToObject(searchQuery)).toEqual({ 'one[]': '2', two: '3' });
+ });
+ });
+
+ describe('with gatherArrays=true', () => {
+ const options = { gatherArrays: true };
+ it('gathers only values with the same array-key and strips `[]` from the key', () => {
+ const searchQuery = '?one[]=1&one[]=2&two=2&two=3';
+
+ expect(urlUtils.queryToObject(searchQuery, options)).toEqual({ one: ['1', '2'], two: '3' });
+ });
+
+ it('overwrites values with the same array-key name', () => {
+ const searchQuery = '?one=1&one[]=2&two=2&two=3';
+
+ expect(urlUtils.queryToObject(searchQuery, options)).toEqual({ one: ['2'], two: '3' });
+ });
+
+ it('overwrites values with the same key name', () => {
+ const searchQuery = '?one[]=1&one=2&two=2&two=3';
+
+ expect(urlUtils.queryToObject(searchQuery, options)).toEqual({ one: '2', two: '3' });
+ });
+ });
});
describe('objectToQuery', () => {