diff options
Diffstat (limited to 'spec/frontend/lib')
9 files changed, 255 insertions, 12 deletions
diff --git a/spec/frontend/lib/apollo/suppress_network_errors_during_navigation_link_spec.js b/spec/frontend/lib/apollo/suppress_network_errors_during_navigation_link_spec.js index 7b604724977..971ba8b583c 100644 --- a/spec/frontend/lib/apollo/suppress_network_errors_during_navigation_link_spec.js +++ b/spec/frontend/lib/apollo/suppress_network_errors_during_navigation_link_spec.js @@ -1,4 +1,4 @@ -import { ApolloLink, Observable } from 'apollo-link'; +import { ApolloLink, Observable } from '@apollo/client/core'; import waitForPromises from 'helpers/wait_for_promises'; import { getSuppressNetworkErrorsDuringNavigationLink } from '~/lib/apollo/suppress_network_errors_during_navigation_link'; import { isNavigatingAway } from '~/lib/utils/is_navigating_away'; diff --git a/spec/frontend/lib/utils/apollo_startup_js_link_spec.js b/spec/frontend/lib/utils/apollo_startup_js_link_spec.js index c0e5b06651f..e58bc063004 100644 --- a/spec/frontend/lib/utils/apollo_startup_js_link_spec.js +++ b/spec/frontend/lib/utils/apollo_startup_js_link_spec.js @@ -1,4 +1,4 @@ -import { ApolloLink, Observable } from 'apollo-link'; +import { ApolloLink, Observable } from '@apollo/client/core'; import { StartupJSLink } from '~/lib/utils/apollo_startup_js_link'; describe('StartupJSLink', () => { diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js index 3e2ba918d9b..3fea08d5512 100644 --- a/spec/frontend/lib/utils/common_utils_spec.js +++ b/spec/frontend/lib/utils/common_utils_spec.js @@ -394,8 +394,7 @@ describe('common_utils', () => { describe('backOff', () => { beforeEach(() => { - // shortcut our timeouts otherwise these tests will take a long time to finish - jest.spyOn(window, 'setTimeout').mockImplementation((cb) => setImmediate(cb, 0)); + jest.spyOn(window, 'setTimeout'); }); it('solves the promise from the callback', (done) => { @@ -446,6 +445,7 @@ describe('common_utils', () => { if (numberOfCalls < 3) { numberOfCalls += 1; next(); + jest.runOnlyPendingTimers(); } else { stop(resp); } @@ -464,7 +464,10 @@ describe('common_utils', () => { it('rejects the backOff promise after timing out', (done) => { commonUtils - .backOff((next) => next(), 64000) + .backOff((next) => { + next(); + jest.runOnlyPendingTimers(); + }, 64000) .catch((errBackoffResp) => { const timeouts = window.setTimeout.mock.calls.map(([, timeout]) => timeout); diff --git a/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js index d19f9352bbc..e06d1384610 100644 --- a/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js +++ b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js @@ -6,11 +6,13 @@ describe('Confirm Modal', () => { let wrapper; let modal; - const createComponent = ({ primaryText, primaryVariant } = {}) => { + const createComponent = ({ primaryText, primaryVariant, title, hideCancel = false } = {}) => { wrapper = mount(ConfirmModal, { propsData: { primaryText, primaryVariant, + hideCancel, + title, }, }); }; @@ -55,5 +57,19 @@ describe('Confirm Modal', () => { expect(customProps.text).toBe('OK'); expect(customProps.attributes.variant).toBe('confirm'); }); + + it('should hide the cancel button if `hideCancel` is set', () => { + createComponent({ hideCancel: true }); + const props = findGlModal().props(); + + expect(props.actionCancel).toBeNull(); + }); + + it('should set the modal title when the `title` prop is set', () => { + const title = 'Modal title'; + createComponent({ title }); + + expect(findGlModal().props().title).toBe(title); + }); }); }); diff --git a/spec/frontend/lib/utils/number_utility_spec.js b/spec/frontend/lib/utils/number_utility_spec.js index e743678ea90..dc4aa0ea5ed 100644 --- a/spec/frontend/lib/utils/number_utility_spec.js +++ b/spec/frontend/lib/utils/number_utility_spec.js @@ -4,6 +4,7 @@ import { bytesToMiB, bytesToGiB, numberToHumanSize, + numberToMetricPrefix, sum, isOdd, median, @@ -99,6 +100,21 @@ describe('Number Utils', () => { }); }); + describe('numberToMetricPrefix', () => { + it.each` + number | expected + ${123} | ${'123'} + ${1234} | ${'1.2k'} + ${12345} | ${'12.3k'} + ${123456} | ${'123.5k'} + ${1234567} | ${'1.2m'} + ${12345678} | ${'12.3m'} + ${123456789} | ${'123.5m'} + `('returns $expected given $number', ({ number, expected }) => { + expect(numberToMetricPrefix(number)).toBe(expected); + }); + }); + describe('sum', () => { it('should add up two values', () => { expect(sum(1, 2)).toEqual(3); diff --git a/spec/frontend/lib/utils/table_utility_spec.js b/spec/frontend/lib/utils/table_utility_spec.js index 75b9252aa40..0ceccbe4c74 100644 --- a/spec/frontend/lib/utils/table_utility_spec.js +++ b/spec/frontend/lib/utils/table_utility_spec.js @@ -8,4 +8,35 @@ describe('table_utility', () => { expect(tableUtils.thWidthClass(width)).toBe(`gl-w-${width}p ${DEFAULT_TH_CLASSES}`); }); }); + + describe('sortObjectToString', () => { + it('returns the expected sorting string ending in "DESC" when sortDesc is true', () => { + expect(tableUtils.sortObjectToString({ sortBy: 'mergedAt', sortDesc: true })).toBe( + 'MERGED_AT_DESC', + ); + }); + + it('returns the expected sorting string ending in "ASC" when sortDesc is false', () => { + expect(tableUtils.sortObjectToString({ sortBy: 'mergedAt', sortDesc: false })).toBe( + 'MERGED_AT_ASC', + ); + }); + }); + + describe('sortStringToObject', () => { + it.each` + sortBy | sortDesc | sortString + ${'mergedAt'} | ${true} | ${'MERGED_AT_DESC'} + ${'mergedAt'} | ${false} | ${'MERGED_AT_ASC'} + ${'severity'} | ${true} | ${'SEVERITY_DESC'} + ${'severity'} | ${false} | ${'SEVERITY_ASC'} + ${null} | ${null} | ${'SEVERITY'} + ${null} | ${null} | ${''} + `( + 'returns the expected sort object when the sort string is "$sortString"', + ({ sortBy, sortDesc, sortString }) => { + expect(tableUtils.sortStringToObject(sortString)).toStrictEqual({ sortBy, sortDesc }); + }, + ); + }); }); diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js index ab81ec47b64..dded32cc890 100644 --- a/spec/frontend/lib/utils/text_markdown_spec.js +++ b/spec/frontend/lib/utils/text_markdown_spec.js @@ -165,6 +165,80 @@ describe('init markdown', () => { // cursor placement should be between tags expect(textArea.selectionStart).toBe(start.length + tag.length); }); + + describe('Continuing markdown lists', () => { + const enterEvent = new KeyboardEvent('keydown', { key: 'Enter' }); + + beforeEach(() => { + gon.features = { markdownContinueLists: true }; + }); + + it.each` + text | expected + ${'- item'} | ${'- item\n- '} + ${'- [ ] item'} | ${'- [ ] item\n- [ ] '} + ${'- [x] item'} | ${'- [x] item\n- [x] '} + ${'- item\n - second'} | ${'- item\n - second\n - '} + ${'1. item'} | ${'1. item\n1. '} + ${'1. [ ] item'} | ${'1. [ ] item\n1. [ ] '} + ${'1. [x] item'} | ${'1. [x] item\n1. [x] '} + ${'108. item'} | ${'108. item\n108. '} + ${'108. item\n - second'} | ${'108. item\n - second\n - '} + ${'108. item\n 1. second'} | ${'108. item\n 1. second\n 1. '} + `('adds correct list continuation characters', ({ text, expected }) => { + textArea.value = text; + textArea.setSelectionRange(text.length, text.length); + + textArea.addEventListener('keydown', keypressNoteText); + textArea.dispatchEvent(enterEvent); + + expect(textArea.value).toEqual(expected); + expect(textArea.selectionStart).toBe(expected.length); + }); + + // test that when pressing Enter on an empty list item, the empty + // list item text is selected, so that when the Enter propagates, + // it's removed + it.each` + text | expected + ${'- item\n- '} | ${'- item\n'} + ${'- [ ] item\n- [ ] '} | ${'- [ ] item\n'} + ${'- [x] item\n- [x] '} | ${'- [x] item\n'} + ${'- item\n - second\n - '} | ${'- item\n - second\n'} + ${'1. item\n1. '} | ${'1. item\n'} + ${'1. [ ] item\n1. [ ] '} | ${'1. [ ] item\n'} + ${'1. [x] item\n1. [x] '} | ${'1. [x] item\n'} + ${'108. item\n108. '} | ${'108. item\n'} + ${'108. item\n - second\n - '} | ${'108. item\n - second\n'} + ${'108. item\n 1. second\n 1. '} | ${'108. item\n 1. second\n'} + `('adds correct list continuation characters', ({ text, expected }) => { + textArea.value = text; + textArea.setSelectionRange(text.length, text.length); + + textArea.addEventListener('keydown', keypressNoteText); + textArea.dispatchEvent(enterEvent); + + expect(textArea.value.substr(0, textArea.selectionStart)).toEqual(expected); + expect(textArea.selectionStart).toBe(expected.length); + expect(textArea.selectionEnd).toBe(text.length); + }); + + it('does nothing if feature flag disabled', () => { + gon.features = { markdownContinueLists: false }; + + const text = '- item'; + const expected = '- item'; + + textArea.value = text; + textArea.setSelectionRange(text.length, text.length); + + textArea.addEventListener('keydown', keypressNoteText); + textArea.dispatchEvent(enterEvent); + + expect(textArea.value).toEqual(expected); + expect(textArea.selectionStart).toBe(expected.length); + }); + }); }); describe('with selection', () => { diff --git a/spec/frontend/lib/utils/vuex_module_mappers_spec.js b/spec/frontend/lib/utils/vuex_module_mappers_spec.js index d7e51e4daca..1821a15f677 100644 --- a/spec/frontend/lib/utils/vuex_module_mappers_spec.js +++ b/spec/frontend/lib/utils/vuex_module_mappers_spec.js @@ -1,4 +1,4 @@ -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import Vue from 'vue'; import Vuex from 'vuex'; import { @@ -10,13 +10,12 @@ import { const TEST_MODULE_NAME = 'testModuleName'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); // setup test component and store ---------------------------------------------- // // These are used to indirectly test `vuex_module_mappers`. -const TestComponent = Vue.extend({ +const TestComponent = { props: { vuexModule: { type: String, @@ -47,7 +46,7 @@ const TestComponent = Vue.extend({ <pre data-testid="state">{{ stateJson }}</pre> <pre data-testid="getters">{{ gettersJson }}</pre> </div>`, -}); +}; const createTestStore = () => { return new Vuex.Store({ @@ -94,7 +93,6 @@ describe('~/lib/utils/vuex_module_mappers', () => { vuexModule: TEST_MODULE_NAME, }, store, - localVue, }); }); diff --git a/spec/frontend/lib/utils/yaml_spec.js b/spec/frontend/lib/utils/yaml_spec.js new file mode 100644 index 00000000000..d1ce00130e2 --- /dev/null +++ b/spec/frontend/lib/utils/yaml_spec.js @@ -0,0 +1,105 @@ +import { Document, parseDocument } from 'yaml'; +import { merge } from '~/lib/utils/yaml'; + +// Mock data for Comments on pairs +const COMMENTS_ON_PAIRS_SOURCE = `foo: + # barbaz + bar: baz + + # bazboo + baz: boo +`; + +const COMMENTS_ON_PAIRS_TARGET = `foo: + # abcdef + abc: def + # boobaz + boo: baz +`; + +const COMMENTS_ON_PAIRS_EXPECTED = `foo: + # abcdef + abc: def + # boobaz + boo: baz + # barbaz + bar: baz + + # bazboo + baz: boo +`; + +// Mock data for Comments on seqs +const COMMENTS_ON_SEQS_SOURCE = `foo: + # barbaz + - barbaz + # bazboo + - baz: boo +`; + +const COMMENTS_ON_SEQS_TARGET = `foo: + # abcdef + - abcdef + + # boobaz + - boobaz +`; + +const COMMENTS_ON_SEQS_EXPECTED = `foo: + # abcdef + - abcdef + + # boobaz + - boobaz + # barbaz + - barbaz + # bazboo + - baz: boo +`; + +describe('Yaml utility functions', () => { + describe('merge', () => { + const getAsNode = (yamlStr) => { + return parseDocument(yamlStr).contents; + }; + + describe('Merge two Nodes', () => { + it.each` + scenario | source | target | options | expected + ${'merge a map'} | ${getAsNode('foo:\n bar: baz\n')} | ${'foo:\n abc: def\n'} | ${undefined} | ${'foo:\n abc: def\n bar: baz\n'} + ${'merge a seq'} | ${getAsNode('foo:\n - bar\n')} | ${'foo:\n - abc\n'} | ${undefined} | ${'foo:\n - bar\n'} + ${'merge-append seqs'} | ${getAsNode('foo:\n - bar\n')} | ${'foo:\n - abc\n'} | ${{ onSequence: 'append' }} | ${'foo:\n - abc\n - bar\n'} + ${'merge-replace a seq'} | ${getAsNode('foo:\n - bar\n')} | ${'foo:\n - abc\n'} | ${{ onSequence: 'replace' }} | ${'foo:\n - bar\n'} + ${'override existing paths'} | ${getAsNode('foo:\n bar: baz\n')} | ${'foo:\n bar: boo\n'} | ${undefined} | ${'foo:\n bar: baz\n'} + ${'deep maps'} | ${getAsNode('foo:\n bar:\n abc: def\n')} | ${'foo:\n bar:\n baz: boo\n jkl: mno\n'} | ${undefined} | ${'foo:\n bar:\n baz: boo\n abc: def\n jkl: mno\n'} + ${'append maps inside seqs'} | ${getAsNode('foo:\n - abc: def\n')} | ${'foo:\n - bar: baz\n'} | ${{ onSequence: 'append' }} | ${'foo:\n - bar: baz\n - abc: def\n'} + ${'inexistent paths create new nodes'} | ${getAsNode('foo:\n bar: baz\n')} | ${'abc: def\n'} | ${undefined} | ${'abc: def\nfoo:\n bar: baz\n'} + ${'document as source'} | ${parseDocument('foo:\n bar: baz\n')} | ${'foo:\n abc: def\n'} | ${undefined} | ${'foo:\n abc: def\n bar: baz\n'} + ${'object as source'} | ${{ foo: { bar: 'baz' } }} | ${'foo:\n abc: def\n'} | ${undefined} | ${'foo:\n abc: def\n bar: baz\n'} + ${'comments on pairs'} | ${parseDocument(COMMENTS_ON_PAIRS_SOURCE)} | ${COMMENTS_ON_PAIRS_TARGET} | ${undefined} | ${COMMENTS_ON_PAIRS_EXPECTED} + ${'comments on seqs'} | ${parseDocument(COMMENTS_ON_SEQS_SOURCE)} | ${COMMENTS_ON_SEQS_TARGET} | ${{ onSequence: 'append' }} | ${COMMENTS_ON_SEQS_EXPECTED} + `('$scenario', ({ source, target, expected, options }) => { + const targetDoc = parseDocument(target); + merge(targetDoc, source, options); + const expectedDoc = parseDocument(expected); + expect(targetDoc.toString()).toEqual(expectedDoc.toString()); + }); + + it('type conflict will throw an Error', () => { + const sourceDoc = parseDocument('foo:\n bar:\n - baz\n'); + const targetDoc = parseDocument('foo:\n bar: def\n'); + expect(() => merge(targetDoc, sourceDoc)).toThrow( + 'Type conflict at "foo.bar": Destination node is of type Scalar, the node' + + ' to be merged is of type YAMLSeq', + ); + }); + + it('merging a collection into an empty doc', () => { + const targetDoc = new Document(); + merge(targetDoc, { foo: { bar: 'baz' } }); + const expected = parseDocument('foo:\n bar: baz\n'); + expect(targetDoc.toString()).toEqual(expected.toString()); + }); + }); + }); +}); |