diff options
Diffstat (limited to 'spec/frontend/lib/utils')
6 files changed, 253 insertions, 40 deletions
diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js index a0140d1d8a8..947c38c8ae8 100644 --- a/spec/frontend/lib/utils/common_utils_spec.js +++ b/spec/frontend/lib/utils/common_utils_spec.js @@ -1016,45 +1016,6 @@ describe('common_utils', () => { }); }); - describe('searchBy', () => { - const searchSpace = { - iid: 1, - reference: '&1', - title: 'Error omnis quos consequatur ullam a vitae sed omnis libero cupiditate.', - url: '/groups/gitlab-org/-/epics/1', - }; - - it('returns null when `query` or `searchSpace` params are empty/undefined', () => { - expect(commonUtils.searchBy('omnis', null)).toBeNull(); - expect(commonUtils.searchBy('', searchSpace)).toBeNull(); - expect(commonUtils.searchBy()).toBeNull(); - }); - - it('returns object with matching props based on `query` & `searchSpace` params', () => { - // String `omnis` is found only in `title` prop so return just that - expect(commonUtils.searchBy('omnis', searchSpace)).toEqual( - expect.objectContaining({ - title: searchSpace.title, - }), - ); - - // String `1` is found in both `iid` and `reference` props so return both - expect(commonUtils.searchBy('1', searchSpace)).toEqual( - expect.objectContaining({ - iid: searchSpace.iid, - reference: searchSpace.reference, - }), - ); - - // String `/epics/1` is found in `url` prop so return just that - expect(commonUtils.searchBy('/epics/1', searchSpace)).toEqual( - expect.objectContaining({ - url: searchSpace.url, - }), - ); - }); - }); - describe('isScopedLabel', () => { it('returns true when `::` is present in title', () => { expect(commonUtils.isScopedLabel({ title: 'foo::bar' })).toBe(true); diff --git a/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_action_spec.js b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_action_spec.js new file mode 100644 index 00000000000..142c76f7bc0 --- /dev/null +++ b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_action_spec.js @@ -0,0 +1,103 @@ +import Vue, { nextTick } from 'vue'; +import { createWrapper } from '@vue/test-utils'; +import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; +import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_action'; +import ConfirmModal from '~/lib/utils/confirm_via_gl_modal/confirm_modal.vue'; + +const originalMount = Vue.prototype.$mount; + +describe('confirmAction', () => { + let modalWrapper; + let confirActionPromise; + let modal; + + const findConfirmModal = () => modalWrapper.findComponent(ConfirmModal); + const renderRootComponent = async (message, opts) => { + confirActionPromise = confirmAction(message, opts); + // We have to wait for two ticks here. + // The first one is to wait for rendering of the root component + // The second one to wait for rendering of the dynamically + // loaded confirm-modal component + await nextTick(); + await nextTick(); + modal = findConfirmModal(); + }; + const mockMount = (vm, el) => { + originalMount.call(vm, el); + modalWrapper = createWrapper(vm); + return vm; + }; + + beforeEach(() => { + setHTMLFixture('<div id="component"></div>'); + const el = document.getElementById('component'); + // We mock the implementation only once to make sure that we mock + // it only for the root component in confirm_action. + // Mounting other components (like confirm-modal) should not be affected with + // this mock + jest.spyOn(Vue.prototype, '$mount').mockImplementationOnce(function mock() { + return mockMount(this, el); + }); + }); + + afterEach(() => { + resetHTMLFixture(); + Vue.prototype.$mount.mockRestore(); + modalWrapper?.destroy(); + modalWrapper = null; + modal?.destroy(); + modal = null; + }); + + it('creats a ConfirmModal with message as slot', async () => { + const message = 'Bonjour le monde!'; + await renderRootComponent(message); + + expect(modal.vm.$slots.default[0].text).toBe(message); + }); + + it('creats a ConfirmModal with props', async () => { + const options = { + primaryBtnText: 'primaryBtnText', + primaryBtnVariant: 'info', + secondaryBtnText: 'secondaryBtnText', + secondaryBtnVariant: 'success', + cancelBtnText: 'cancelBtnText', + cancelBtnVariant: 'danger', + modalHtmlMessage: '<strong>Hello</strong>', + title: 'title', + hideCancel: true, + }; + await renderRootComponent('', options); + expect(modal.props()).toEqual( + expect.objectContaining({ + primaryText: options.primaryBtnText, + primaryVariant: options.primaryBtnVariant, + secondaryText: options.secondaryBtnText, + secondaryVariant: options.secondaryBtnVariant, + cancelText: options.cancelBtnText, + cancelVariant: options.cancelBtnVariant, + modalHtmlMessage: options.modalHtmlMessage, + title: options.title, + hideCancel: options.hideCancel, + }), + ); + }); + + it('resolves promise when modal emit `closed`', async () => { + await renderRootComponent(''); + + modal.vm.$emit('closed'); + + await expect(confirActionPromise).resolves.toBe(false); + }); + + it('confirms when modal emit `confirmed` before `closed`', async () => { + await renderRootComponent(''); + + modal.vm.$emit('confirmed'); + modal.vm.$emit('closed'); + + await expect(confirActionPromise).resolves.toBe(true); + }); +}); diff --git a/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal_spec.js b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal_spec.js new file mode 100644 index 00000000000..6966c79b232 --- /dev/null +++ b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal_spec.js @@ -0,0 +1,80 @@ +import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; +import { confirmViaGlModal } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; +import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_action'; + +jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_action'); + +describe('confirmViaGlModal', () => { + let el; + + afterEach(() => { + el = undefined; + resetHTMLFixture(); + jest.resetAllMocks(); + }); + + const createElement = (html) => { + setHTMLFixture(html); + return document.body.firstChild; + }; + + it('returns confirmAction result', async () => { + confirmAction.mockReturnValue(Promise.resolve(true)); + el = createElement(`<div/>`); + + await expect(confirmViaGlModal('', el)).resolves.toBe(true); + }); + + it('calls confirmAction with message', () => { + el = createElement(`<div/>`); + + confirmViaGlModal('message', el); + + expect(confirmAction).toHaveBeenCalledWith('message', {}); + }); + + it.each(['gl-sr-only', 'sr-only'])( + `uses slot.%s contentText as primaryBtnText`, + (srOnlyClass) => { + el = createElement( + `<a href="#"><span class="${srOnlyClass}">Delete merge request</span></a>`, + ); + + confirmViaGlModal('', el); + + expect(confirmAction).toHaveBeenCalledWith('', { + primaryBtnText: 'Delete merge request', + }); + }, + ); + + it('uses `aria-label` value as `primaryBtnText`', () => { + el = createElement(`<a aria-label="Delete merge request" href="#"></a>`); + + confirmViaGlModal('', el); + + expect(confirmAction).toHaveBeenCalledWith('', { + primaryBtnText: 'Delete merge request', + }); + }); + + it.each([ + ['title', 'title', 'Delete?'], + ['confirm-btn-variant', `primaryBtnVariant`, 'danger'], + ])('uses data-%s value as confirmAction config', (dataKey, configKey, value) => { + el = createElement(`<a data-${dataKey}="${value}" href="#"></a>`); + + confirmViaGlModal('message', el); + + expect(confirmAction).toHaveBeenCalledWith('message', { [configKey]: value }); + }); + + it('uses message as modalHtmlMessage value when data-is-html-message is true', () => { + el = createElement(`<a data-is-html-message="true" href="#"></a>`); + const message = 'Hola mundo!'; + + confirmViaGlModal(message, el); + + expect(confirmAction).toHaveBeenCalledWith(message, { modalHtmlMessage: message }); + }); +}); diff --git a/spec/frontend/lib/utils/datetime/date_calculation_utility_spec.js b/spec/frontend/lib/utils/datetime/date_calculation_utility_spec.js index 59b3b4c02df..055d57d6ada 100644 --- a/spec/frontend/lib/utils/datetime/date_calculation_utility_spec.js +++ b/spec/frontend/lib/utils/datetime/date_calculation_utility_spec.js @@ -1,4 +1,9 @@ -import { getDateWithUTC, newDateAsLocaleTime } from '~/lib/utils/datetime/date_calculation_utility'; +import { + getDateWithUTC, + newDateAsLocaleTime, + nSecondsAfter, + nSecondsBefore, +} from '~/lib/utils/datetime/date_calculation_utility'; describe('newDateAsLocaleTime', () => { it.each` @@ -31,3 +36,33 @@ describe('getDateWithUTC', () => { expect(getDateWithUTC(date)).toEqual(expected); }); }); + +describe('nSecondsAfter', () => { + const start = new Date('2022-03-22T01:23:45.678Z'); + it.each` + date | seconds | expected + ${start} | ${0} | ${start} + ${start} | ${1} | ${new Date('2022-03-22T01:23:46.678Z')} + ${start} | ${5} | ${new Date('2022-03-22T01:23:50.678Z')} + ${start} | ${60} | ${new Date('2022-03-22T01:24:45.678Z')} + ${start} | ${3600} | ${new Date('2022-03-22T02:23:45.678Z')} + ${start} | ${86400} | ${new Date('2022-03-23T01:23:45.678Z')} + `('returns $expected given $string', ({ date, seconds, expected }) => { + expect(nSecondsAfter(date, seconds)).toEqual(expected); + }); +}); + +describe('nSecondsBefore', () => { + const start = new Date('2022-03-22T01:23:45.678Z'); + it.each` + date | seconds | expected + ${start} | ${0} | ${start} + ${start} | ${1} | ${new Date('2022-03-22T01:23:44.678Z')} + ${start} | ${5} | ${new Date('2022-03-22T01:23:40.678Z')} + ${start} | ${60} | ${new Date('2022-03-22T01:22:45.678Z')} + ${start} | ${3600} | ${new Date('2022-03-22T00:23:45.678Z')} + ${start} | ${86400} | ${new Date('2022-03-21T01:23:45.678Z')} + `('returns $expected given $string', ({ date, seconds, expected }) => { + expect(nSecondsBefore(date, seconds)).toEqual(expected); + }); +}); diff --git a/spec/frontend/lib/utils/dom_utils_spec.js b/spec/frontend/lib/utils/dom_utils_spec.js index b537e6b2bf8..d6bac935970 100644 --- a/spec/frontend/lib/utils/dom_utils_spec.js +++ b/spec/frontend/lib/utils/dom_utils_spec.js @@ -1,8 +1,10 @@ import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; + import { addClassIfElementExists, canScrollUp, canScrollDown, + getContentWrapperHeight, parseBooleanDataAttributes, isElementVisible, getParents, @@ -235,4 +237,30 @@ describe('DOM Utils', () => { expect(div.getAttribute('title')).toBe('another test'); }); }); + + describe('getContentWrapperHeight', () => { + const fixture = ` + <div> + <div class="content-wrapper"> + <div class="content"></div> + </div> + </div> + `; + + beforeEach(() => { + setHTMLFixture(fixture); + }); + + afterEach(() => { + resetHTMLFixture(); + }); + + it('returns the height of an element that exists', () => { + expect(getContentWrapperHeight('.content-wrapper')).toBe('0px'); + }); + + it('returns an empty string for a class that does not exist', () => { + expect(getContentWrapperHeight('.does-not-exist')).toBe(''); + }); + }); }); diff --git a/spec/frontend/lib/utils/unit_format/index_spec.js b/spec/frontend/lib/utils/unit_format/index_spec.js index dc9d6ece48e..057d7aded02 100644 --- a/spec/frontend/lib/utils/unit_format/index_spec.js +++ b/spec/frontend/lib/utils/unit_format/index_spec.js @@ -2,6 +2,7 @@ import { number, percent, percentHundred, + days, seconds, milliseconds, decimalBytes, @@ -72,6 +73,11 @@ describe('unit_format', () => { expect(percentHundred(1000)).toBe('1,000%'); }); + it('days', () => { + expect(days(1)).toBe('1d'); + expect(days(1, undefined, { unitSeparator: '/' })).toBe('1/d'); + }); + it('seconds', () => { expect(seconds(1)).toBe('1s'); expect(seconds(1, undefined, { unitSeparator: ' ' })).toBe('1 s'); |