diff options
Diffstat (limited to 'spec/frontend/lib')
-rw-r--r-- | spec/frontend/lib/apollo/instrumentation_link_spec.js | 54 | ||||
-rw-r--r-- | spec/frontend/lib/dompurify_spec.js | 25 | ||||
-rw-r--r-- | spec/frontend/lib/logger/__snapshots__/hello_spec.js.snap | 16 | ||||
-rw-r--r-- | spec/frontend/lib/logger/hello_deferred_spec.js | 17 | ||||
-rw-r--r-- | spec/frontend/lib/logger/hello_spec.js | 20 | ||||
-rw-r--r-- | spec/frontend/lib/logger/index_spec.js | 23 | ||||
-rw-r--r-- | spec/frontend/lib/utils/accessor_spec.js | 65 | ||||
-rw-r--r-- | spec/frontend/lib/utils/datetime/date_format_utility_spec.js | 120 | ||||
-rw-r--r-- | spec/frontend/lib/utils/dom_utils_spec.js | 15 | ||||
-rw-r--r-- | spec/frontend/lib/utils/text_markdown_spec.js | 19 | ||||
-rw-r--r-- | spec/frontend/lib/utils/url_utility_spec.js | 23 |
11 files changed, 316 insertions, 81 deletions
diff --git a/spec/frontend/lib/apollo/instrumentation_link_spec.js b/spec/frontend/lib/apollo/instrumentation_link_spec.js new file mode 100644 index 00000000000..ef686129257 --- /dev/null +++ b/spec/frontend/lib/apollo/instrumentation_link_spec.js @@ -0,0 +1,54 @@ +import { testApolloLink } from 'helpers/test_apollo_link'; +import { getInstrumentationLink, FEATURE_CATEGORY_HEADER } from '~/lib/apollo/instrumentation_link'; + +const TEST_FEATURE_CATEGORY = 'foo_feature'; + +describe('~/lib/apollo/instrumentation_link', () => { + const setFeatureCategory = (val) => { + window.gon.feature_category = val; + }; + + afterEach(() => { + getInstrumentationLink.cache.clear(); + }); + + describe('getInstrumentationLink', () => { + describe('with no gon.feature_category', () => { + beforeEach(() => { + setFeatureCategory(null); + }); + + it('returns null', () => { + expect(getInstrumentationLink()).toBe(null); + }); + }); + + describe('with gon.feature_category', () => { + beforeEach(() => { + setFeatureCategory(TEST_FEATURE_CATEGORY); + }); + + it('returns memoized apollo link', () => { + const result = getInstrumentationLink(); + + // expect.any(ApolloLink) doesn't work for some reason... + expect(result).toHaveProp('request'); + expect(result).toBe(getInstrumentationLink()); + }); + + it('adds a feature category header from the returned apollo link', async () => { + const defaultHeaders = { Authorization: 'foo' }; + const operation = await testApolloLink(getInstrumentationLink(), { + context: { headers: defaultHeaders }, + }); + + const { headers } = operation.getContext(); + + expect(headers).toEqual({ + ...defaultHeaders, + [FEATURE_CATEGORY_HEADER]: TEST_FEATURE_CATEGORY, + }); + }); + }); + }); +}); diff --git a/spec/frontend/lib/dompurify_spec.js b/spec/frontend/lib/dompurify_spec.js index fa8dbb12a08..324441fa2c9 100644 --- a/spec/frontend/lib/dompurify_spec.js +++ b/spec/frontend/lib/dompurify_spec.js @@ -44,6 +44,31 @@ describe('~/lib/dompurify', () => { expect(sanitize('<strong></strong>', { ALLOWED_TAGS: [] })).toBe(''); }); + describe('includes default configuration', () => { + it('with empty config', () => { + const svgIcon = '<svg width="100"><use></use></svg>'; + expect(sanitize(svgIcon, {})).toBe(svgIcon); + }); + + it('with valid config', () => { + expect(sanitize('<a href="#" data-remote="true"></a>', { ALLOWED_TAGS: ['a'] })).toBe( + '<a href="#"></a>', + ); + }); + }); + + it("doesn't sanitize local references", () => { + const htmlHref = `<svg><use href="#some-element"></use></svg>`; + const htmlXlink = `<svg><use xlink:href="#some-element"></use></svg>`; + + expect(sanitize(htmlHref)).toBe(htmlHref); + expect(sanitize(htmlXlink)).toBe(htmlXlink); + }); + + it("doesn't sanitize gl-emoji", () => { + expect(sanitize('<p><gl-emoji>💯</gl-emoji></p>')).toBe('<p><gl-emoji>💯</gl-emoji></p>'); + }); + describe.each` type | gon ${'root'} | ${rootGon} diff --git a/spec/frontend/lib/logger/__snapshots__/hello_spec.js.snap b/spec/frontend/lib/logger/__snapshots__/hello_spec.js.snap new file mode 100644 index 00000000000..791ec05befd --- /dev/null +++ b/spec/frontend/lib/logger/__snapshots__/hello_spec.js.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`~/lib/logger/hello logHello console logs a friendly hello message 1`] = ` +Array [ + Array [ + "%cWelcome to GitLab!%c + +Does this page need fixes or improvements? Open an issue or contribute a merge request to help make GitLab more lovable. At GitLab, everyone can contribute! + +🤝 Contribute to GitLab: https://about.gitlab.com/community/contribute/ +🔎 Create a new GitLab issue: https://gitlab.com/gitlab-org/gitlab/-/issues/new", + "padding-top: 0.5em; font-size: 2em;", + "padding-bottom: 0.5em;", + ], +] +`; diff --git a/spec/frontend/lib/logger/hello_deferred_spec.js b/spec/frontend/lib/logger/hello_deferred_spec.js new file mode 100644 index 00000000000..3233cbff0dc --- /dev/null +++ b/spec/frontend/lib/logger/hello_deferred_spec.js @@ -0,0 +1,17 @@ +import waitForPromises from 'helpers/wait_for_promises'; +import { logHello } from '~/lib/logger/hello'; +import { logHelloDeferred } from '~/lib/logger/hello_deferred'; + +jest.mock('~/lib/logger/hello'); + +describe('~/lib/logger/hello_deferred', () => { + it('dynamically imports and calls logHello', async () => { + logHelloDeferred(); + + expect(logHello).not.toHaveBeenCalled(); + + await waitForPromises(); + + expect(logHello).toHaveBeenCalled(); + }); +}); diff --git a/spec/frontend/lib/logger/hello_spec.js b/spec/frontend/lib/logger/hello_spec.js new file mode 100644 index 00000000000..39abe0e0dd0 --- /dev/null +++ b/spec/frontend/lib/logger/hello_spec.js @@ -0,0 +1,20 @@ +import { logHello } from '~/lib/logger/hello'; + +describe('~/lib/logger/hello', () => { + let consoleLogSpy; + + beforeEach(() => { + // We don't `mockImplementation` so we can validate there's no errors thrown + consoleLogSpy = jest.spyOn(console, 'log'); + }); + + describe('logHello', () => { + it('console logs a friendly hello message', () => { + expect(consoleLogSpy).not.toHaveBeenCalled(); + + logHello(); + + expect(consoleLogSpy.mock.calls).toMatchSnapshot(); + }); + }); +}); diff --git a/spec/frontend/lib/logger/index_spec.js b/spec/frontend/lib/logger/index_spec.js new file mode 100644 index 00000000000..9382fafe4de --- /dev/null +++ b/spec/frontend/lib/logger/index_spec.js @@ -0,0 +1,23 @@ +import { logError, LOG_PREFIX } from '~/lib/logger'; + +describe('~/lib/logger', () => { + let consoleErrorSpy; + + beforeEach(() => { + consoleErrorSpy = jest.spyOn(console, 'error'); + consoleErrorSpy.mockImplementation(); + }); + + describe('logError', () => { + it('sends given message to console.error', () => { + const message = 'Lorem ipsum dolar sit amit'; + const error = new Error('lorem ipsum'); + + expect(consoleErrorSpy).not.toHaveBeenCalled(); + + logError(message, error); + + expect(consoleErrorSpy).toHaveBeenCalledWith(LOG_PREFIX, `${message}\n`, error); + }); + }); +}); diff --git a/spec/frontend/lib/utils/accessor_spec.js b/spec/frontend/lib/utils/accessor_spec.js index 752a88296e6..63497d795ce 100644 --- a/spec/frontend/lib/utils/accessor_spec.js +++ b/spec/frontend/lib/utils/accessor_spec.js @@ -6,60 +6,9 @@ describe('AccessorUtilities', () => { const testError = new Error('test error'); - describe('isPropertyAccessSafe', () => { - let base; - - it('should return `true` if access is safe', () => { - base = { - testProp: 'testProp', - }; - expect(AccessorUtilities.isPropertyAccessSafe(base, 'testProp')).toBe(true); - }); - - it('should return `false` if access throws an error', () => { - base = { - get testProp() { - throw testError; - }, - }; - - expect(AccessorUtilities.isPropertyAccessSafe(base, 'testProp')).toBe(false); - }); - - it('should return `false` if property is undefined', () => { - base = {}; - - expect(AccessorUtilities.isPropertyAccessSafe(base, 'testProp')).toBe(false); - }); - }); - - describe('isFunctionCallSafe', () => { - const base = {}; - - it('should return `true` if calling is safe', () => { - base.func = () => {}; - - expect(AccessorUtilities.isFunctionCallSafe(base, 'func')).toBe(true); - }); - - it('should return `false` if calling throws an error', () => { - base.func = () => { - throw new Error('test error'); - }; - - expect(AccessorUtilities.isFunctionCallSafe(base, 'func')).toBe(false); - }); - - it('should return `false` if function is undefined', () => { - base.func = undefined; - - expect(AccessorUtilities.isFunctionCallSafe(base, 'func')).toBe(false); - }); - }); - - describe('isLocalStorageAccessSafe', () => { + describe('canUseLocalStorage', () => { it('should return `true` if access is safe', () => { - expect(AccessorUtilities.isLocalStorageAccessSafe()).toBe(true); + expect(AccessorUtilities.canUseLocalStorage()).toBe(true); }); it('should return `false` if access to .setItem isnt safe', () => { @@ -67,19 +16,19 @@ describe('AccessorUtilities', () => { throw testError; }); - expect(AccessorUtilities.isLocalStorageAccessSafe()).toBe(false); + expect(AccessorUtilities.canUseLocalStorage()).toBe(false); }); it('should set a test item if access is safe', () => { - AccessorUtilities.isLocalStorageAccessSafe(); + AccessorUtilities.canUseLocalStorage(); - expect(window.localStorage.setItem).toHaveBeenCalledWith('isLocalStorageAccessSafe', 'true'); + expect(window.localStorage.setItem).toHaveBeenCalledWith('canUseLocalStorage', 'true'); }); it('should remove the test item if access is safe', () => { - AccessorUtilities.isLocalStorageAccessSafe(); + AccessorUtilities.canUseLocalStorage(); - expect(window.localStorage.removeItem).toHaveBeenCalledWith('isLocalStorageAccessSafe'); + expect(window.localStorage.removeItem).toHaveBeenCalledWith('canUseLocalStorage'); }); }); }); diff --git a/spec/frontend/lib/utils/datetime/date_format_utility_spec.js b/spec/frontend/lib/utils/datetime/date_format_utility_spec.js new file mode 100644 index 00000000000..942ba56196e --- /dev/null +++ b/spec/frontend/lib/utils/datetime/date_format_utility_spec.js @@ -0,0 +1,120 @@ +import * as utils from '~/lib/utils/datetime/date_format_utility'; + +describe('date_format_utility.js', () => { + describe('padWithZeros', () => { + it.each` + input | output + ${0} | ${'00'} + ${'1'} | ${'01'} + ${'10'} | ${'10'} + ${'100'} | ${'100'} + ${100} | ${'100'} + ${'a'} | ${'0a'} + ${'foo'} | ${'foo'} + `('properly pads $input to match $output', ({ input, output }) => { + expect(utils.padWithZeros(input)).toEqual([output]); + }); + + it('accepts multiple arguments', () => { + expect(utils.padWithZeros(1, '2', 3)).toEqual(['01', '02', '03']); + }); + + it('returns an empty array provided no argument', () => { + expect(utils.padWithZeros()).toEqual([]); + }); + }); + + describe('stripTimezoneFromISODate', () => { + it.each` + input | expectedOutput + ${'2021-08-16T00:00:00Z'} | ${'2021-08-16T00:00:00'} + ${'2021-08-16T10:30:00+02:00'} | ${'2021-08-16T10:30:00'} + ${'2021-08-16T10:30:00-05:30'} | ${'2021-08-16T10:30:00'} + `('returns $expectedOutput when given $input', ({ input, expectedOutput }) => { + expect(utils.stripTimezoneFromISODate(input)).toBe(expectedOutput); + }); + + it('returns null if date is invalid', () => { + expect(utils.stripTimezoneFromISODate('Invalid date')).toBe(null); + }); + }); + + describe('dateToYearMonthDate', () => { + it.each` + date | expectedOutput + ${new Date('2021-08-05')} | ${{ year: '2021', month: '08', day: '05' }} + ${new Date('2021-12-24')} | ${{ year: '2021', month: '12', day: '24' }} + `('returns $expectedOutput provided $date', ({ date, expectedOutput }) => { + expect(utils.dateToYearMonthDate(date)).toEqual(expectedOutput); + }); + + it('throws provided an invalid date', () => { + expect(() => utils.dateToYearMonthDate('Invalid date')).toThrow( + 'Argument should be a Date instance', + ); + }); + }); + + describe('timeToHoursMinutes', () => { + it.each` + time | expectedOutput + ${'23:12'} | ${{ hours: '23', minutes: '12' }} + ${'23:12'} | ${{ hours: '23', minutes: '12' }} + `('returns $expectedOutput provided $time', ({ time, expectedOutput }) => { + expect(utils.timeToHoursMinutes(time)).toEqual(expectedOutput); + }); + + it('throws provided an invalid time', () => { + expect(() => utils.timeToHoursMinutes('Invalid time')).toThrow('Invalid time provided'); + }); + }); + + describe('dateAndTimeToISOString', () => { + it('computes the date properly', () => { + expect(utils.dateAndTimeToISOString(new Date('2021-08-16'), '10:00')).toBe( + '2021-08-16T10:00:00.000Z', + ); + }); + + it('computes the date properly with an offset', () => { + expect(utils.dateAndTimeToISOString(new Date('2021-08-16'), '10:00', '-04:00')).toBe( + '2021-08-16T10:00:00.000-04:00', + ); + }); + + it('throws if date in invalid', () => { + expect(() => utils.dateAndTimeToISOString('Invalid date', '10:00')).toThrow( + 'Argument should be a Date instance', + ); + }); + + it('throws if time in invalid', () => { + expect(() => utils.dateAndTimeToISOString(new Date('2021-08-16'), '')).toThrow( + 'Invalid time provided', + ); + }); + + it('throws if offset is invalid', () => { + expect(() => + utils.dateAndTimeToISOString(new Date('2021-08-16'), '10:00', 'not an offset'), + ).toThrow('Could not initialize date'); + }); + }); + + describe('dateToTimeInputValue', () => { + it.each` + input | expectedOutput + ${new Date('2021-08-16T10:00:00.000Z')} | ${'10:00'} + ${new Date('2021-08-16T22:30:00.000Z')} | ${'22:30'} + ${new Date('2021-08-16T22:30:00.000-03:00')} | ${'01:30'} + `('extracts $expectedOutput out of $input', ({ input, expectedOutput }) => { + expect(utils.dateToTimeInputValue(input)).toBe(expectedOutput); + }); + + it('throws if date is invalid', () => { + expect(() => utils.dateToTimeInputValue('Invalid date')).toThrow( + 'Argument should be a Date instance', + ); + }); + }); +}); diff --git a/spec/frontend/lib/utils/dom_utils_spec.js b/spec/frontend/lib/utils/dom_utils_spec.js index 7c4c20e651f..cb8b1c7ca9a 100644 --- a/spec/frontend/lib/utils/dom_utils_spec.js +++ b/spec/frontend/lib/utils/dom_utils_spec.js @@ -5,6 +5,7 @@ import { parseBooleanDataAttributes, isElementVisible, isElementHidden, + getParents, } from '~/lib/utils/dom_utils'; const TEST_MARGIN = 5; @@ -193,4 +194,18 @@ describe('DOM Utils', () => { }); }, ); + + describe('getParents', () => { + it('gets all parents of an element', () => { + const el = document.createElement('div'); + el.innerHTML = '<p><span><strong><mark>hello world'; + + expect(getParents(el.querySelector('mark'))).toEqual([ + el.querySelector('strong'), + el.querySelector('span'), + el.querySelector('p'), + el, + ]); + }); + }); }); diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js index beedb9b2eba..acbf1a975b8 100644 --- a/spec/frontend/lib/utils/text_markdown_spec.js +++ b/spec/frontend/lib/utils/text_markdown_spec.js @@ -88,6 +88,25 @@ describe('init markdown', () => { expect(textArea.value).toEqual(`${initialValue}\n- `); }); + it('unescapes new line characters', () => { + const initialValue = ''; + + textArea.value = initialValue; + textArea.selectionStart = 0; + textArea.selectionEnd = 0; + + insertMarkdownText({ + textArea, + text: textArea.value, + tag: '```suggestion:-0+0\n{text}\n```', + blockTag: true, + selected: '# Does not parse the %br currently.', + wrap: false, + }); + + expect(textArea.value).toContain('# Does not parse the \\n currently.'); + }); + it('inserts the tag on the same line if the current line only contains spaces', () => { const initialValue = ' '; diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js index c8ac7ffc9d9..6f186ba3227 100644 --- a/spec/frontend/lib/utils/url_utility_spec.js +++ b/spec/frontend/lib/utils/url_utility_spec.js @@ -645,29 +645,6 @@ describe('URL utility', () => { }); }); - describe('urlParamsToObject', () => { - it('parses path for label with trailing +', () => { - // eslint-disable-next-line import/no-deprecated - expect(urlUtils.urlParamsToObject('label_name[]=label%2B', {})).toEqual({ - label_name: ['label+'], - }); - }); - - it('parses path for milestone with trailing +', () => { - // eslint-disable-next-line import/no-deprecated - expect(urlUtils.urlParamsToObject('milestone_title=A%2B', {})).toEqual({ - milestone_title: 'A+', - }); - }); - - it('parses path for search terms with spaces', () => { - // eslint-disable-next-line import/no-deprecated - expect(urlUtils.urlParamsToObject('search=two+words', {})).toEqual({ - search: 'two words', - }); - }); - }); - describe('queryToObject', () => { it.each` case | query | options | result |