diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-20 13:00:54 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-20 13:00:54 +0300 |
commit | 3cccd102ba543e02725d247893729e5c73b38295 (patch) | |
tree | f36a04ec38517f5deaaacb5acc7d949688d1e187 /spec/frontend/lib | |
parent | 205943281328046ef7b4528031b90fbda70c75ac (diff) |
Add latest changes from gitlab-org/gitlab@14-10-stable-eev14.10.0-rc42
Diffstat (limited to 'spec/frontend/lib')
13 files changed, 354 insertions, 206 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 971ba8b583c..5ac7a7985a8 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 @@ -82,34 +82,39 @@ describe('getSuppressNetworkErrorsDuringNavigationLink', () => { isNavigatingAway.mockReturnValue(false); }); - it('forwards successful requests', (done) => { + it('forwards successful requests', () => { createSubscription(makeMockSuccessLink(), { next({ data }) { expect(data).toEqual({ foo: { id: 1 } }); }, - error: () => done.fail('Should not happen'), - complete: () => done(), + error: () => { + throw new Error('Should not happen'); + }, }); }); - it('forwards GraphQL errors', (done) => { + it('forwards GraphQL errors', () => { createSubscription(makeMockGraphQLErrorLink(), { next({ errors }) { expect(errors).toEqual([{ message: 'foo' }]); }, - error: () => done.fail('Should not happen'), - complete: () => done(), + error: () => { + throw new Error('Should not happen'); + }, }); }); - it('forwards network errors', (done) => { + it('forwards network errors', () => { createSubscription(makeMockNetworkErrorLink(), { - next: () => done.fail('Should not happen'), + next: () => { + throw new Error('Should not happen'); + }, error: (error) => { expect(error.message).toBe('NetworkError'); - done(); }, - complete: () => done.fail('Should not happen'), + complete: () => { + throw new Error('Should not happen'); + }, }); }); }); @@ -119,23 +124,25 @@ describe('getSuppressNetworkErrorsDuringNavigationLink', () => { isNavigatingAway.mockReturnValue(true); }); - it('forwards successful requests', (done) => { + it('forwards successful requests', () => { createSubscription(makeMockSuccessLink(), { next({ data }) { expect(data).toEqual({ foo: { id: 1 } }); }, - error: () => done.fail('Should not happen'), - complete: () => done(), + error: () => { + throw new Error('Should not happen'); + }, }); }); - it('forwards GraphQL errors', (done) => { + it('forwards GraphQL errors', () => { createSubscription(makeMockGraphQLErrorLink(), { next({ errors }) { expect(errors).toEqual([{ message: 'foo' }]); }, - error: () => done.fail('Should not happen'), - complete: () => done(), + error: () => { + throw new Error('Should not happen'); + }, }); }); }); diff --git a/spec/frontend/lib/gfm/index_spec.js b/spec/frontend/lib/gfm/index_spec.js new file mode 100644 index 00000000000..5c72b5a51a7 --- /dev/null +++ b/spec/frontend/lib/gfm/index_spec.js @@ -0,0 +1,46 @@ +import { render } from '~/lib/gfm'; + +describe('gfm', () => { + describe('render', () => { + it('processes Commonmark and provides an ast to the renderer function', async () => { + let result; + + await render({ + markdown: 'This is text', + renderer: (tree) => { + result = tree; + }, + }); + + expect(result.type).toBe('root'); + }); + + it('transforms raw HTML into individual nodes in the AST', async () => { + let result; + + await render({ + markdown: '<strong>This is bold text</strong>', + renderer: (tree) => { + result = tree; + }, + }); + + expect(result.children[0].children[0]).toMatchObject({ + type: 'element', + tagName: 'strong', + properties: {}, + }); + }); + + it('returns the result of executing the renderer function', async () => { + const result = await render({ + markdown: '<strong>This is bold text</strong>', + renderer: () => { + return 'rendered tree'; + }, + }); + + expect(result).toBe('rendered tree'); + }); + }); +}); 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 e58bc063004..06573f346e0 100644 --- a/spec/frontend/lib/utils/apollo_startup_js_link_spec.js +++ b/spec/frontend/lib/utils/apollo_startup_js_link_spec.js @@ -58,17 +58,16 @@ describe('StartupJSLink', () => { link = ApolloLink.from([startupLink, new ApolloLink(() => Observable.of(FORWARDED_RESPONSE))]); }; - it('forwards requests if no calls are set up', (done) => { + it('forwards requests if no calls are set up', () => { setupLink(); link.request(mockOperation()).subscribe((result) => { expect(result).toEqual(FORWARDED_RESPONSE); expect(startupLink.startupCalls).toBe(null); expect(startupLink.request).toEqual(StartupJSLink.noopRequest); - done(); }); }); - it('forwards requests if the operation is not pre-loaded', (done) => { + it('forwards requests if the operation is not pre-loaded', () => { window.gl = { startup_graphql_calls: [ { @@ -82,12 +81,11 @@ describe('StartupJSLink', () => { link.request(mockOperation({ operationName: 'notLoaded' })).subscribe((result) => { expect(result).toEqual(FORWARDED_RESPONSE); expect(startupLink.startupCalls.size).toBe(1); - done(); }); }); describe('variable match errors: ', () => { - it('forwards requests if the variables are not matching', (done) => { + it('forwards requests if the variables are not matching', () => { window.gl = { startup_graphql_calls: [ { @@ -101,11 +99,10 @@ describe('StartupJSLink', () => { link.request(mockOperation()).subscribe((result) => { expect(result).toEqual(FORWARDED_RESPONSE); expect(startupLink.startupCalls.size).toBe(0); - done(); }); }); - it('forwards requests if more variables are set in the operation', (done) => { + it('forwards requests if more variables are set in the operation', () => { window.gl = { startup_graphql_calls: [ { @@ -118,11 +115,10 @@ describe('StartupJSLink', () => { link.request(mockOperation()).subscribe((result) => { expect(result).toEqual(FORWARDED_RESPONSE); expect(startupLink.startupCalls.size).toBe(0); - done(); }); }); - it('forwards requests if less variables are set in the operation', (done) => { + it('forwards requests if less variables are set in the operation', () => { window.gl = { startup_graphql_calls: [ { @@ -136,11 +132,10 @@ describe('StartupJSLink', () => { link.request(mockOperation({ variables: { id: 3 } })).subscribe((result) => { expect(result).toEqual(FORWARDED_RESPONSE); expect(startupLink.startupCalls.size).toBe(0); - done(); }); }); - it('forwards requests if different variables are set', (done) => { + it('forwards requests if different variables are set', () => { window.gl = { startup_graphql_calls: [ { @@ -154,11 +149,10 @@ describe('StartupJSLink', () => { link.request(mockOperation({ variables: { id: 3 } })).subscribe((result) => { expect(result).toEqual(FORWARDED_RESPONSE); expect(startupLink.startupCalls.size).toBe(0); - done(); }); }); - it('forwards requests if array variables have a different order', (done) => { + it('forwards requests if array variables have a different order', () => { window.gl = { startup_graphql_calls: [ { @@ -172,13 +166,12 @@ describe('StartupJSLink', () => { link.request(mockOperation({ variables: { id: [4, 3] } })).subscribe((result) => { expect(result).toEqual(FORWARDED_RESPONSE); expect(startupLink.startupCalls.size).toBe(0); - done(); }); }); }); describe('error handling', () => { - it('forwards the call if the fetchCall is failing with a HTTP Error', (done) => { + it('forwards the call if the fetchCall is failing with a HTTP Error', () => { window.gl = { startup_graphql_calls: [ { @@ -192,11 +185,10 @@ describe('StartupJSLink', () => { link.request(mockOperation()).subscribe((result) => { expect(result).toEqual(FORWARDED_RESPONSE); expect(startupLink.startupCalls.size).toBe(0); - done(); }); }); - it('forwards the call if it errors (e.g. failing JSON)', (done) => { + it('forwards the call if it errors (e.g. failing JSON)', () => { window.gl = { startup_graphql_calls: [ { @@ -210,11 +202,10 @@ describe('StartupJSLink', () => { link.request(mockOperation()).subscribe((result) => { expect(result).toEqual(FORWARDED_RESPONSE); expect(startupLink.startupCalls.size).toBe(0); - done(); }); }); - it('forwards the call if the response contains an error', (done) => { + it('forwards the call if the response contains an error', () => { window.gl = { startup_graphql_calls: [ { @@ -228,11 +219,10 @@ describe('StartupJSLink', () => { link.request(mockOperation()).subscribe((result) => { expect(result).toEqual(FORWARDED_RESPONSE); expect(startupLink.startupCalls.size).toBe(0); - done(); }); }); - it("forwards the call if the response doesn't contain a data object", (done) => { + it("forwards the call if the response doesn't contain a data object", () => { window.gl = { startup_graphql_calls: [ { @@ -246,12 +236,11 @@ describe('StartupJSLink', () => { link.request(mockOperation()).subscribe((result) => { expect(result).toEqual(FORWARDED_RESPONSE); expect(startupLink.startupCalls.size).toBe(0); - done(); }); }); }); - it('resolves the request if the operation is matching', (done) => { + it('resolves the request if the operation is matching', () => { window.gl = { startup_graphql_calls: [ { @@ -265,11 +254,10 @@ describe('StartupJSLink', () => { link.request(mockOperation()).subscribe((result) => { expect(result).toEqual(STARTUP_JS_RESPONSE); expect(startupLink.startupCalls.size).toBe(0); - done(); }); }); - it('resolves the request exactly once', (done) => { + it('resolves the request exactly once', () => { window.gl = { startup_graphql_calls: [ { @@ -285,12 +273,11 @@ describe('StartupJSLink', () => { expect(startupLink.startupCalls.size).toBe(0); link.request(mockOperation()).subscribe((result2) => { expect(result2).toEqual(FORWARDED_RESPONSE); - done(); }); }); }); - it('resolves the request if the variables have a different order', (done) => { + it('resolves the request if the variables have a different order', () => { window.gl = { startup_graphql_calls: [ { @@ -304,11 +291,10 @@ describe('StartupJSLink', () => { link.request(mockOperation({ variables: { name: 'foo', id: 3 } })).subscribe((result) => { expect(result).toEqual(STARTUP_JS_RESPONSE); expect(startupLink.startupCalls.size).toBe(0); - done(); }); }); - it('resolves the request if the variables have undefined values', (done) => { + it('resolves the request if the variables have undefined values', () => { window.gl = { startup_graphql_calls: [ { @@ -324,11 +310,10 @@ describe('StartupJSLink', () => { .subscribe((result) => { expect(result).toEqual(STARTUP_JS_RESPONSE); expect(startupLink.startupCalls.size).toBe(0); - done(); }); }); - it('resolves the request if the variables are of an array format', (done) => { + it('resolves the request if the variables are of an array format', () => { window.gl = { startup_graphql_calls: [ { @@ -342,11 +327,10 @@ describe('StartupJSLink', () => { link.request(mockOperation({ variables: { id: [3, 4] } })).subscribe((result) => { expect(result).toEqual(STARTUP_JS_RESPONSE); expect(startupLink.startupCalls.size).toBe(0); - done(); }); }); - it('resolves multiple requests correctly', (done) => { + it('resolves multiple requests correctly', () => { window.gl = { startup_graphql_calls: [ { @@ -368,7 +352,6 @@ describe('StartupJSLink', () => { link.request(mockOperation({ operationName: OPERATION_NAME })).subscribe((result2) => { expect(result2).toEqual(STARTUP_JS_RESPONSE); expect(startupLink.startupCalls.size).toBe(0); - done(); }); }); }); diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js index 0be0bf89210..763a9bd30fe 100644 --- a/spec/frontend/lib/utils/common_utils_spec.js +++ b/spec/frontend/lib/utils/common_utils_spec.js @@ -266,15 +266,18 @@ describe('common_utils', () => { }); describe('debounceByAnimationFrame', () => { - it('debounces a function to allow a maximum of one call per animation frame', (done) => { + it('debounces a function to allow a maximum of one call per animation frame', () => { const spy = jest.fn(); const debouncedSpy = commonUtils.debounceByAnimationFrame(spy); - window.requestAnimationFrame(() => { - debouncedSpy(); - debouncedSpy(); + + return new Promise((resolve) => { window.requestAnimationFrame(() => { - expect(spy).toHaveBeenCalledTimes(1); - done(); + debouncedSpy(); + debouncedSpy(); + window.requestAnimationFrame(() => { + expect(spy).toHaveBeenCalledTimes(1); + resolve(); + }); }); }); }); @@ -372,28 +375,24 @@ describe('common_utils', () => { jest.spyOn(window, 'setTimeout'); }); - it('solves the promise from the callback', (done) => { + it('solves the promise from the callback', () => { const expectedResponseValue = 'Success!'; - commonUtils + return commonUtils .backOff((next, stop) => new Promise((resolve) => { resolve(expectedResponseValue); - }) - .then((resp) => { - stop(resp); - }) - .catch(done.fail), + }).then((resp) => { + stop(resp); + }), ) .then((respBackoff) => { expect(respBackoff).toBe(expectedResponseValue); - done(); - }) - .catch(done.fail); + }); }); - it('catches the rejected promise from the callback ', (done) => { + it('catches the rejected promise from the callback ', () => { const errorMessage = 'Mistakes were made!'; - commonUtils + return commonUtils .backOff((next, stop) => { new Promise((resolve, reject) => { reject(new Error(errorMessage)); @@ -406,39 +405,34 @@ describe('common_utils', () => { .catch((errBackoffResp) => { expect(errBackoffResp instanceof Error).toBe(true); expect(errBackoffResp.message).toBe(errorMessage); - done(); }); }); - it('solves the promise correctly after retrying a third time', (done) => { + it('solves the promise correctly after retrying a third time', () => { let numberOfCalls = 1; const expectedResponseValue = 'Success!'; - commonUtils + return commonUtils .backOff((next, stop) => - Promise.resolve(expectedResponseValue) - .then((resp) => { - if (numberOfCalls < 3) { - numberOfCalls += 1; - next(); - jest.runOnlyPendingTimers(); - } else { - stop(resp); - } - }) - .catch(done.fail), + Promise.resolve(expectedResponseValue).then((resp) => { + if (numberOfCalls < 3) { + numberOfCalls += 1; + next(); + jest.runOnlyPendingTimers(); + } else { + stop(resp); + } + }), ) .then((respBackoff) => { const timeouts = window.setTimeout.mock.calls.map(([, timeout]) => timeout); expect(timeouts).toEqual([2000, 4000]); expect(respBackoff).toBe(expectedResponseValue); - done(); - }) - .catch(done.fail); + }); }); - it('rejects the backOff promise after timing out', (done) => { - commonUtils + it('rejects the backOff promise after timing out', () => { + return commonUtils .backOff((next) => { next(); jest.runOnlyPendingTimers(); @@ -449,7 +443,6 @@ describe('common_utils', () => { expect(timeouts).toEqual([2000, 4000, 8000, 16000, 32000, 32000]); expect(errBackoffResp instanceof Error).toBe(true); expect(errBackoffResp.message).toBe('BACKOFF_TIMEOUT'); - done(); }); }); }); 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 e06d1384610..d6131b1a1d7 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 @@ -5,12 +5,23 @@ import ConfirmModal from '~/lib/utils/confirm_via_gl_modal/confirm_modal.vue'; describe('Confirm Modal', () => { let wrapper; let modal; + const SECONDARY_TEXT = 'secondaryText'; + const SECONDARY_VARIANT = 'danger'; - const createComponent = ({ primaryText, primaryVariant, title, hideCancel = false } = {}) => { + const createComponent = ({ + primaryText, + primaryVariant, + secondaryText, + secondaryVariant, + title, + hideCancel = false, + } = {}) => { wrapper = mount(ConfirmModal, { propsData: { primaryText, primaryVariant, + secondaryText, + secondaryVariant, hideCancel, title, }, @@ -65,6 +76,19 @@ describe('Confirm Modal', () => { expect(props.actionCancel).toBeNull(); }); + it('should not show secondary Button when secondary Text is not set', () => { + createComponent(); + const props = findGlModal().props(); + expect(props.actionSecondary).toBeNull(); + }); + + it('should show secondary Button when secondaryText is set', () => { + createComponent({ secondaryText: SECONDARY_TEXT, secondaryVariant: SECONDARY_VARIANT }); + const actionSecondary = findGlModal().props('actionSecondary'); + expect(actionSecondary.text).toEqual(SECONDARY_TEXT); + expect(actionSecondary.attributes.variant).toEqual(SECONDARY_VARIANT); + }); + it('should set the modal title when the `title` prop is set', () => { const title = 'Modal title'; createComponent({ title }); diff --git a/spec/frontend/lib/utils/datetime/date_calculation_utility_spec.js b/spec/frontend/lib/utils/datetime/date_calculation_utility_spec.js new file mode 100644 index 00000000000..47bb512cbb5 --- /dev/null +++ b/spec/frontend/lib/utils/datetime/date_calculation_utility_spec.js @@ -0,0 +1,17 @@ +import { newDateAsLocaleTime } from '~/lib/utils/datetime/date_calculation_utility'; + +describe('newDateAsLocaleTime', () => { + it.each` + string | expected + ${'2022-03-22'} | ${new Date('2022-03-22T00:00:00.000Z')} + ${'2022-03-22T00:00:00.000Z'} | ${new Date('2022-03-22T00:00:00.000Z')} + ${2022} | ${null} + ${[]} | ${null} + ${{}} | ${null} + ${true} | ${null} + ${null} | ${null} + ${undefined} | ${null} + `('returns $expected given $string', ({ string, expected }) => { + expect(newDateAsLocaleTime(string)).toEqual(expected); + }); +}); diff --git a/spec/frontend/lib/utils/datetime/date_format_utility_spec.js b/spec/frontend/lib/utils/datetime/date_format_utility_spec.js index 1adc70450e8..018ae12c908 100644 --- a/spec/frontend/lib/utils/datetime/date_format_utility_spec.js +++ b/spec/frontend/lib/utils/datetime/date_format_utility_spec.js @@ -133,3 +133,15 @@ describe('formatTimeAsSummary', () => { expect(utils.formatTimeAsSummary({ [unit]: value })).toBe(result); }); }); + +describe('durationTimeFormatted', () => { + it.each` + duration | expectedOutput + ${87} | ${'00:01:27'} + ${141} | ${'00:02:21'} + ${12} | ${'00:00:12'} + ${60} | ${'00:01:00'} + `('returns $expectedOutput when provided $duration', ({ duration, expectedOutput }) => { + expect(utils.durationTimeFormatted(duration)).toBe(expectedOutput); + }); +}); diff --git a/spec/frontend/lib/utils/datetime/timeago_utility_spec.js b/spec/frontend/lib/utils/datetime/timeago_utility_spec.js index 2314ec678d3..1ef7047d959 100644 --- a/spec/frontend/lib/utils/datetime/timeago_utility_spec.js +++ b/spec/frontend/lib/utils/datetime/timeago_utility_spec.js @@ -1,4 +1,4 @@ -import { getTimeago, localTimeAgo, timeFor } from '~/lib/utils/datetime/timeago_utility'; +import { getTimeago, localTimeAgo, timeFor, duration } from '~/lib/utils/datetime/timeago_utility'; import { s__ } from '~/locale'; import '~/commons/bootstrap'; @@ -66,6 +66,54 @@ describe('TimeAgo utils', () => { }); }); + describe('duration', () => { + const ONE_DAY = 24 * 60 * 60; + + it.each` + secs | formatted + ${0} | ${'0 seconds'} + ${30} | ${'30 seconds'} + ${59} | ${'59 seconds'} + ${60} | ${'1 minute'} + ${-60} | ${'1 minute'} + ${2 * 60} | ${'2 minutes'} + ${60 * 60} | ${'1 hour'} + ${2 * 60 * 60} | ${'2 hours'} + ${ONE_DAY} | ${'1 day'} + ${2 * ONE_DAY} | ${'2 days'} + ${7 * ONE_DAY} | ${'1 week'} + ${14 * ONE_DAY} | ${'2 weeks'} + ${31 * ONE_DAY} | ${'1 month'} + ${61 * ONE_DAY} | ${'2 months'} + ${365 * ONE_DAY} | ${'1 year'} + ${365 * 2 * ONE_DAY} | ${'2 years'} + `('formats $secs as "$formatted"', ({ secs, formatted }) => { + const ms = secs * 1000; + + expect(duration(ms)).toBe(formatted); + }); + + // `duration` can be used to format Rails month durations. + // Ensure formatting for quantities such as `2.months.to_i` + // based on ActiveSupport::Duration::SECONDS_PER_MONTH. + // See: https://api.rubyonrails.org/classes/ActiveSupport/Duration.html + const SECONDS_PER_MONTH = 2629746; // 1.month.to_i + + it.each` + duration | secs | formatted + ${'1.month'} | ${SECONDS_PER_MONTH} | ${'1 month'} + ${'2.months'} | ${SECONDS_PER_MONTH * 2} | ${'2 months'} + ${'3.months'} | ${SECONDS_PER_MONTH * 3} | ${'3 months'} + `( + 'formats ActiveSupport::Duration of `$duration` ($secs) as "$formatted"', + ({ secs, formatted }) => { + const ms = secs * 1000; + + expect(duration(ms)).toBe(formatted); + }, + ); + }); + describe('localTimeAgo', () => { beforeEach(() => { document.body.innerHTML = diff --git a/spec/frontend/lib/utils/poll_spec.js b/spec/frontend/lib/utils/poll_spec.js index 861808e3ad8..1f150599983 100644 --- a/spec/frontend/lib/utils/poll_spec.js +++ b/spec/frontend/lib/utils/poll_spec.js @@ -50,58 +50,48 @@ describe('Poll', () => { }; }); - it('calls the success callback when no header for interval is provided', (done) => { + it('calls the success callback when no header for interval is provided', () => { mockServiceCall({ status: 200 }); setup(); - waitForAllCallsToFinish(1, () => { + return waitForAllCallsToFinish(1, () => { expect(callbacks.success).toHaveBeenCalled(); expect(callbacks.error).not.toHaveBeenCalled(); - - done(); }); }); - it('calls the error callback when the http request returns an error', (done) => { + it('calls the error callback when the http request returns an error', () => { mockServiceCall({ status: 500 }, true); setup(); - waitForAllCallsToFinish(1, () => { + return waitForAllCallsToFinish(1, () => { expect(callbacks.success).not.toHaveBeenCalled(); expect(callbacks.error).toHaveBeenCalled(); - - done(); }); }); - it('skips the error callback when request is aborted', (done) => { + it('skips the error callback when request is aborted', () => { mockServiceCall({ status: 0 }, true); setup(); - waitForAllCallsToFinish(1, () => { + return waitForAllCallsToFinish(1, () => { expect(callbacks.success).not.toHaveBeenCalled(); expect(callbacks.error).not.toHaveBeenCalled(); expect(callbacks.notification).toHaveBeenCalled(); - - done(); }); }); - it('should call the success callback when the interval header is -1', (done) => { + it('should call the success callback when the interval header is -1', () => { mockServiceCall({ status: 200, headers: { 'poll-interval': -1 } }); - setup() - .then(() => { - expect(callbacks.success).toHaveBeenCalled(); - expect(callbacks.error).not.toHaveBeenCalled(); - - done(); - }) - .catch(done.fail); + return setup().then(() => { + expect(callbacks.success).toHaveBeenCalled(); + expect(callbacks.error).not.toHaveBeenCalled(); + }); }); describe('for 2xx status code', () => { successCodes.forEach((httpCode) => { - it(`starts polling when http status is ${httpCode} and interval header is provided`, (done) => { + it(`starts polling when http status is ${httpCode} and interval header is provided`, () => { mockServiceCall({ status: httpCode, headers: { 'poll-interval': 1 } }); const Polling = new Poll({ @@ -114,22 +104,20 @@ describe('Poll', () => { Polling.makeRequest(); - waitForAllCallsToFinish(2, () => { + return waitForAllCallsToFinish(2, () => { Polling.stop(); expect(service.fetch.mock.calls).toHaveLength(2); expect(service.fetch).toHaveBeenCalledWith({ page: 1 }); expect(callbacks.success).toHaveBeenCalled(); expect(callbacks.error).not.toHaveBeenCalled(); - - done(); }); }); }); }); describe('with delayed initial request', () => { - it('delays the first request', async (done) => { + it('delays the first request', async () => { mockServiceCall({ status: 200, headers: { 'poll-interval': 1 } }); const Polling = new Poll({ @@ -144,21 +132,19 @@ describe('Poll', () => { expect(Polling.timeoutID).toBeTruthy(); - waitForAllCallsToFinish(2, () => { + return waitForAllCallsToFinish(2, () => { Polling.stop(); expect(service.fetch.mock.calls).toHaveLength(2); expect(service.fetch).toHaveBeenCalledWith({ page: 1 }); expect(callbacks.success).toHaveBeenCalled(); expect(callbacks.error).not.toHaveBeenCalled(); - - done(); }); }); }); describe('stop', () => { - it('stops polling when method is called', (done) => { + it('stops polling when method is called', () => { mockServiceCall({ status: 200, headers: { 'poll-interval': 1 } }); const Polling = new Poll({ @@ -175,18 +161,16 @@ describe('Poll', () => { Polling.makeRequest(); - waitForAllCallsToFinish(1, () => { + return waitForAllCallsToFinish(1, () => { expect(service.fetch.mock.calls).toHaveLength(1); expect(service.fetch).toHaveBeenCalledWith({ page: 1 }); expect(Polling.stop).toHaveBeenCalled(); - - done(); }); }); }); describe('enable', () => { - it('should enable polling upon a response', (done) => { + it('should enable polling upon a response', () => { mockServiceCall({ status: 200 }); const Polling = new Poll({ resource: service, @@ -200,19 +184,18 @@ describe('Poll', () => { response: { status: 200, headers: { 'poll-interval': 1 } }, }); - waitForAllCallsToFinish(1, () => { + return waitForAllCallsToFinish(1, () => { Polling.stop(); expect(service.fetch.mock.calls).toHaveLength(1); expect(service.fetch).toHaveBeenCalledWith({ page: 4 }); expect(Polling.options.data).toEqual({ page: 4 }); - done(); }); }); }); describe('restart', () => { - it('should restart polling when its called', (done) => { + it('should restart polling when its called', () => { mockServiceCall({ status: 200, headers: { 'poll-interval': 1 } }); const Polling = new Poll({ @@ -238,7 +221,7 @@ describe('Poll', () => { Polling.makeRequest(); - waitForAllCallsToFinish(2, () => { + return waitForAllCallsToFinish(2, () => { Polling.stop(); expect(service.fetch.mock.calls).toHaveLength(2); @@ -247,7 +230,6 @@ describe('Poll', () => { expect(Polling.enable).toHaveBeenCalled(); expect(Polling.restart).toHaveBeenCalled(); expect(Polling.options.data).toEqual({ page: 4 }); - done(); }); }); }); diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js index a5877aa6e3e..103305f0797 100644 --- a/spec/frontend/lib/utils/text_markdown_spec.js +++ b/spec/frontend/lib/utils/text_markdown_spec.js @@ -178,12 +178,23 @@ describe('init markdown', () => { it.each` text | expected ${'- item'} | ${'- item\n- '} + ${'* item'} | ${'* item\n* '} + ${'+ item'} | ${'+ item\n+ '} ${'- [ ] item'} | ${'- [ ] item\n- [ ] '} - ${'- [x] item'} | ${'- [x] item\n- [x] '} + ${'- [x] item'} | ${'- [x] item\n- [ ] '} + ${'- [X] item'} | ${'- [X] item\n- [ ] '} + ${'- [ ] nbsp (U+00A0)'} | ${'- [ ] nbsp (U+00A0)\n- [ ] '} ${'- item\n - second'} | ${'- item\n - second\n - '} + ${'- - -'} | ${'- - -'} + ${'- --'} | ${'- --'} + ${'* **'} | ${'* **'} + ${' ** * ** * ** * **'} | ${' ** * ** * ** * **'} + ${'- - -x'} | ${'- - -x\n- '} + ${'+ ++'} | ${'+ ++\n+ '} ${'1. item'} | ${'1. item\n2. '} ${'1. [ ] item'} | ${'1. [ ] item\n2. [ ] '} - ${'1. [x] item'} | ${'1. [x] item\n2. [x] '} + ${'1. [x] item'} | ${'1. [x] item\n2. [ ] '} + ${'1. [X] item'} | ${'1. [X] item\n2. [ ] '} ${'108. item'} | ${'108. item\n109. '} ${'108. item\n - second'} | ${'108. item\n - second\n - '} ${'108. item\n 1. second'} | ${'108. item\n 1. second\n 2. '} @@ -207,10 +218,12 @@ describe('init markdown', () => { ${'- item\n- '} | ${'- item\n'} ${'- [ ] item\n- [ ] '} | ${'- [ ] item\n'} ${'- [x] item\n- [x] '} | ${'- [x] item\n'} + ${'- [X] item\n- [X] '} | ${'- [X] item\n'} ${'- item\n - second\n - '} | ${'- item\n - second\n'} ${'1. item\n2. '} | ${'1. item\n'} ${'1. [ ] item\n2. [ ] '} | ${'1. [ ] item\n'} ${'1. [x] item\n2. [x] '} | ${'1. [x] item\n'} + ${'1. [X] item\n2. [X] '} | ${'1. [X] item\n'} ${'108. item\n109. '} | ${'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'} diff --git a/spec/frontend/lib/utils/unit_format/formatter_factory_spec.js b/spec/frontend/lib/utils/unit_format/formatter_factory_spec.js index 0ca70e0a77e..9632d0f98f4 100644 --- a/spec/frontend/lib/utils/unit_format/formatter_factory_spec.js +++ b/spec/frontend/lib/utils/unit_format/formatter_factory_spec.js @@ -31,12 +31,17 @@ describe('unit_format/formatter_factory', () => { expect(formatNumber(12.345, 4)).toBe('12.3450'); }); - it('formats a large integer with a length limit', () => { + it('formats a large integer with a max length - using legacy positional argument', () => { expect(formatNumber(10 ** 7, undefined)).toBe('10,000,000'); expect(formatNumber(10 ** 7, undefined, 9)).toBe('1.00e+7'); expect(formatNumber(10 ** 7, undefined, 10)).toBe('10,000,000'); }); + it('formats a large integer with a max length', () => { + expect(formatNumber(10 ** 7, undefined, { maxLength: 9 })).toBe('1.00e+7'); + expect(formatNumber(10 ** 7, undefined, { maxLength: 10 })).toBe('10,000,000'); + }); + describe('formats with a different locale', () => { let originalLang; @@ -92,7 +97,7 @@ describe('unit_format/formatter_factory', () => { expect(formatSuffix(-1000000)).toBe('-1,000,000pop.'); }); - it('formats a floating point nugative number', () => { + it('formats a floating point negative number', () => { expect(formatSuffix(-0.1)).toBe('-0.1pop.'); expect(formatSuffix(-0.1, 0)).toBe('-0pop.'); expect(formatSuffix(-0.1, 2)).toBe('-0.10pop.'); @@ -108,10 +113,20 @@ describe('unit_format/formatter_factory', () => { expect(formatSuffix(10 ** 10)).toBe('10,000,000,000pop.'); }); - it('formats a large integer with a length limit', () => { + it('formats using a unit separator', () => { + expect(formatSuffix(10, 0, { unitSeparator: ' ' })).toBe('10 pop.'); + expect(formatSuffix(10, 0, { unitSeparator: ' x ' })).toBe('10 x pop.'); + }); + + it('formats a large integer with a max length - using legacy positional argument', () => { expect(formatSuffix(10 ** 7, undefined, 10)).toBe('1.00e+7pop.'); expect(formatSuffix(10 ** 10, undefined, 10)).toBe('1.00e+10pop.'); }); + + it('formats a large integer with a max length', () => { + expect(formatSuffix(10 ** 7, undefined, { maxLength: 10 })).toBe('1.00e+7pop.'); + expect(formatSuffix(10 ** 10, undefined, { maxLength: 10 })).toBe('1.00e+10pop.'); + }); }); describe('scaledSIFormatter', () => { @@ -143,6 +158,10 @@ describe('unit_format/formatter_factory', () => { expect(formatGibibytes(10 ** 10)).toBe('10GB'); expect(formatGibibytes(10 ** 11)).toBe('100GB'); }); + + it('formats bytes using a unit separator', () => { + expect(formatGibibytes(1, 0, { unitSeparator: ' ' })).toBe('1 B'); + }); }); describe('scaled format with offset', () => { @@ -174,6 +193,19 @@ describe('unit_format/formatter_factory', () => { expect(formatGigaBytes(10 ** 9)).toBe('1EB'); }); + it('formats bytes using a unit separator', () => { + expect(formatGigaBytes(1, undefined, { unitSeparator: ' ' })).toBe('1 GB'); + }); + + it('formats long byte numbers with max length - using legacy positional argument', () => { + expect(formatGigaBytes(1, 8, 7)).toBe('1.00e+0GB'); + }); + + it('formats long byte numbers with max length', () => { + expect(formatGigaBytes(1, 8)).toBe('1.00000000GB'); + expect(formatGigaBytes(1, 8, { maxLength: 7 })).toBe('1.00e+0GB'); + }); + it('formatting of too large numbers is not suported', () => { // formatting YB is out of range expect(() => scaledSIFormatter('B', 9)).toThrow(); @@ -216,6 +248,10 @@ describe('unit_format/formatter_factory', () => { expect(formatMilligrams(-100)).toBe('-100mg'); expect(formatMilligrams(-(10 ** 4))).toBe('-10g'); }); + + it('formats using a unit separator', () => { + expect(formatMilligrams(1, undefined, { unitSeparator: ' ' })).toBe('1 mg'); + }); }); }); @@ -253,6 +289,10 @@ describe('unit_format/formatter_factory', () => { expect(formatScaledBin(10 * 1024 ** 3)).toBe('10GiB'); expect(formatScaledBin(100 * 1024 ** 3)).toBe('100GiB'); }); + + it('formats using a unit separator', () => { + expect(formatScaledBin(1, undefined, { unitSeparator: ' ' })).toBe('1 B'); + }); }); describe('scaled format with offset', () => { @@ -288,6 +328,10 @@ describe('unit_format/formatter_factory', () => { expect(formatGibibytes(100 * 1024 ** 3)).toBe('100EiB'); }); + it('formats using a unit separator', () => { + expect(formatGibibytes(1, undefined, { unitSeparator: ' ' })).toBe('1 GiB'); + }); + it('formatting of too large numbers is not suported', () => { // formatting YB is out of range expect(() => scaledBinaryFormatter('B', 9)).toThrow(); diff --git a/spec/frontend/lib/utils/unit_format/index_spec.js b/spec/frontend/lib/utils/unit_format/index_spec.js index 7fd273f1b58..dc9d6ece48e 100644 --- a/spec/frontend/lib/utils/unit_format/index_spec.js +++ b/spec/frontend/lib/utils/unit_format/index_spec.js @@ -74,10 +74,13 @@ describe('unit_format', () => { it('seconds', () => { expect(seconds(1)).toBe('1s'); + expect(seconds(1, undefined, { unitSeparator: ' ' })).toBe('1 s'); }); it('milliseconds', () => { expect(milliseconds(1)).toBe('1ms'); + expect(milliseconds(1, undefined, { unitSeparator: ' ' })).toBe('1 ms'); + expect(milliseconds(100)).toBe('100ms'); expect(milliseconds(1000)).toBe('1,000ms'); expect(milliseconds(10_000)).toBe('10,000ms'); @@ -87,6 +90,7 @@ describe('unit_format', () => { it('decimalBytes', () => { expect(decimalBytes(1)).toBe('1B'); expect(decimalBytes(1, 1)).toBe('1.0B'); + expect(decimalBytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 B'); expect(decimalBytes(10)).toBe('10B'); expect(decimalBytes(10 ** 2)).toBe('100B'); @@ -104,31 +108,37 @@ describe('unit_format', () => { it('kilobytes', () => { expect(kilobytes(1)).toBe('1kB'); expect(kilobytes(1, 1)).toBe('1.0kB'); + expect(kilobytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 kB'); }); it('megabytes', () => { expect(megabytes(1)).toBe('1MB'); expect(megabytes(1, 1)).toBe('1.0MB'); + expect(megabytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 MB'); }); it('gigabytes', () => { expect(gigabytes(1)).toBe('1GB'); expect(gigabytes(1, 1)).toBe('1.0GB'); + expect(gigabytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 GB'); }); it('terabytes', () => { expect(terabytes(1)).toBe('1TB'); expect(terabytes(1, 1)).toBe('1.0TB'); + expect(terabytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 TB'); }); it('petabytes', () => { expect(petabytes(1)).toBe('1PB'); expect(petabytes(1, 1)).toBe('1.0PB'); + expect(petabytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 PB'); }); it('bytes', () => { expect(bytes(1)).toBe('1B'); expect(bytes(1, 1)).toBe('1.0B'); + expect(bytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 B'); expect(bytes(10)).toBe('10B'); expect(bytes(100)).toBe('100B'); @@ -142,26 +152,31 @@ describe('unit_format', () => { it('kibibytes', () => { expect(kibibytes(1)).toBe('1KiB'); expect(kibibytes(1, 1)).toBe('1.0KiB'); + expect(kibibytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 KiB'); }); it('mebibytes', () => { expect(mebibytes(1)).toBe('1MiB'); expect(mebibytes(1, 1)).toBe('1.0MiB'); + expect(mebibytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 MiB'); }); it('gibibytes', () => { expect(gibibytes(1)).toBe('1GiB'); expect(gibibytes(1, 1)).toBe('1.0GiB'); + expect(gibibytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 GiB'); }); it('tebibytes', () => { expect(tebibytes(1)).toBe('1TiB'); expect(tebibytes(1, 1)).toBe('1.0TiB'); + expect(tebibytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 TiB'); }); it('pebibytes', () => { expect(pebibytes(1)).toBe('1PiB'); expect(pebibytes(1, 1)).toBe('1.0PiB'); + expect(pebibytes(1, 1, { unitSeparator: ' ' })).toBe('1.0 PiB'); }); describe('getFormatter', () => { diff --git a/spec/frontend/lib/utils/users_cache_spec.js b/spec/frontend/lib/utils/users_cache_spec.js index 4034f39ee9c..30bdddd8e73 100644 --- a/spec/frontend/lib/utils/users_cache_spec.js +++ b/spec/frontend/lib/utils/users_cache_spec.js @@ -93,7 +93,7 @@ describe('UsersCache', () => { .mockImplementation((query, options) => apiSpy(query, options)); }); - it('stores and returns data from API call if cache is empty', (done) => { + it('stores and returns data from API call if cache is empty', async () => { apiSpy = (query, options) => { expect(query).toBe(''); expect(options).toEqual({ @@ -105,16 +105,12 @@ describe('UsersCache', () => { }); }; - UsersCache.retrieve(dummyUsername) - .then((user) => { - expect(user).toBe(dummyUser); - expect(UsersCache.internalStorage[dummyUsername]).toBe(dummyUser); - }) - .then(done) - .catch(done.fail); + const user = await UsersCache.retrieve(dummyUsername); + expect(user).toBe(dummyUser); + expect(UsersCache.internalStorage[dummyUsername]).toBe(dummyUser); }); - it('returns undefined if Ajax call fails and cache is empty', (done) => { + it('returns undefined if Ajax call fails and cache is empty', async () => { const dummyError = new Error('server exploded'); apiSpy = (query, options) => { @@ -126,26 +122,18 @@ describe('UsersCache', () => { return Promise.reject(dummyError); }; - UsersCache.retrieve(dummyUsername) - .then((user) => done.fail(`Received unexpected user: ${JSON.stringify(user)}`)) - .catch((error) => { - expect(error).toBe(dummyError); - }) - .then(done) - .catch(done.fail); + await expect(UsersCache.retrieve(dummyUsername)).rejects.toEqual(dummyError); }); - it('makes no Ajax call if matching data exists', (done) => { + it('makes no Ajax call if matching data exists', async () => { UsersCache.internalStorage[dummyUsername] = dummyUser; - apiSpy = () => done.fail(new Error('expected no Ajax call!')); + apiSpy = () => { + throw new Error('expected no Ajax call!'); + }; - UsersCache.retrieve(dummyUsername) - .then((user) => { - expect(user).toBe(dummyUser); - }) - .then(done) - .catch(done.fail); + const user = await UsersCache.retrieve(dummyUsername); + expect(user).toBe(dummyUser); }); }); @@ -156,7 +144,7 @@ describe('UsersCache', () => { jest.spyOn(UserApi, 'getUser').mockImplementation((id) => apiSpy(id)); }); - it('stores and returns data from API call if cache is empty', (done) => { + it('stores and returns data from API call if cache is empty', async () => { apiSpy = (id) => { expect(id).toBe(dummyUserId); @@ -165,16 +153,12 @@ describe('UsersCache', () => { }); }; - UsersCache.retrieveById(dummyUserId) - .then((user) => { - expect(user).toBe(dummyUser); - expect(UsersCache.internalStorage[dummyUserId]).toBe(dummyUser); - }) - .then(done) - .catch(done.fail); + const user = await UsersCache.retrieveById(dummyUserId); + expect(user).toBe(dummyUser); + expect(UsersCache.internalStorage[dummyUserId]).toBe(dummyUser); }); - it('returns undefined if Ajax call fails and cache is empty', (done) => { + it('returns undefined if Ajax call fails and cache is empty', async () => { const dummyError = new Error('server exploded'); apiSpy = (id) => { @@ -183,26 +167,18 @@ describe('UsersCache', () => { return Promise.reject(dummyError); }; - UsersCache.retrieveById(dummyUserId) - .then((user) => done.fail(`Received unexpected user: ${JSON.stringify(user)}`)) - .catch((error) => { - expect(error).toBe(dummyError); - }) - .then(done) - .catch(done.fail); + await expect(UsersCache.retrieveById(dummyUserId)).rejects.toEqual(dummyError); }); - it('makes no Ajax call if matching data exists', (done) => { + it('makes no Ajax call if matching data exists', async () => { UsersCache.internalStorage[dummyUserId] = dummyUser; - apiSpy = () => done.fail(new Error('expected no Ajax call!')); + apiSpy = () => { + throw new Error('expected no Ajax call!'); + }; - UsersCache.retrieveById(dummyUserId) - .then((user) => { - expect(user).toBe(dummyUser); - }) - .then(done) - .catch(done.fail); + const user = await UsersCache.retrieveById(dummyUserId); + expect(user).toBe(dummyUser); }); }); @@ -213,7 +189,7 @@ describe('UsersCache', () => { jest.spyOn(UserApi, 'getUserStatus').mockImplementation((id) => apiSpy(id)); }); - it('stores and returns data from API call if cache is empty', (done) => { + it('stores and returns data from API call if cache is empty', async () => { apiSpy = (id) => { expect(id).toBe(dummyUserId); @@ -222,16 +198,12 @@ describe('UsersCache', () => { }); }; - UsersCache.retrieveStatusById(dummyUserId) - .then((userStatus) => { - expect(userStatus).toBe(dummyUserStatus); - expect(UsersCache.internalStorage[dummyUserId].status).toBe(dummyUserStatus); - }) - .then(done) - .catch(done.fail); + const userStatus = await UsersCache.retrieveStatusById(dummyUserId); + expect(userStatus).toBe(dummyUserStatus); + expect(UsersCache.internalStorage[dummyUserId].status).toBe(dummyUserStatus); }); - it('returns undefined if Ajax call fails and cache is empty', (done) => { + it('returns undefined if Ajax call fails and cache is empty', async () => { const dummyError = new Error('server exploded'); apiSpy = (id) => { @@ -240,28 +212,20 @@ describe('UsersCache', () => { return Promise.reject(dummyError); }; - UsersCache.retrieveStatusById(dummyUserId) - .then((userStatus) => done.fail(`Received unexpected user: ${JSON.stringify(userStatus)}`)) - .catch((error) => { - expect(error).toBe(dummyError); - }) - .then(done) - .catch(done.fail); + await expect(UsersCache.retrieveStatusById(dummyUserId)).rejects.toEqual(dummyError); }); - it('makes no Ajax call if matching data exists', (done) => { + it('makes no Ajax call if matching data exists', async () => { UsersCache.internalStorage[dummyUserId] = { status: dummyUserStatus, }; - apiSpy = () => done.fail(new Error('expected no Ajax call!')); + apiSpy = () => { + throw new Error('expected no Ajax call!'); + }; - UsersCache.retrieveStatusById(dummyUserId) - .then((userStatus) => { - expect(userStatus).toBe(dummyUserStatus); - }) - .then(done) - .catch(done.fail); + const userStatus = await UsersCache.retrieveStatusById(dummyUserId); + expect(userStatus).toBe(dummyUserStatus); }); }); }); |