diff options
Diffstat (limited to 'spec/frontend/lib')
-rw-r--r-- | spec/frontend/lib/utils/axios_startup_calls_spec.js | 131 | ||||
-rw-r--r-- | spec/frontend/lib/utils/datetime_utility_spec.js | 14 | ||||
-rw-r--r-- | spec/frontend/lib/utils/forms_spec.js | 44 | ||||
-rw-r--r-- | spec/frontend/lib/utils/text_markdown_spec.js | 34 | ||||
-rw-r--r-- | spec/frontend/lib/utils/text_utility_spec.js | 21 | ||||
-rw-r--r-- | spec/frontend/lib/utils/url_utility_spec.js | 38 |
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', () => { |