Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/lib')
-rw-r--r--spec/frontend/lib/dompurify_spec.js16
-rw-r--r--spec/frontend/lib/graphql_spec.js54
-rw-r--r--spec/frontend/lib/utils/common_utils_spec.js146
-rw-r--r--spec/frontend/lib/utils/datetime/timeago_utility_spec.js103
-rw-r--r--spec/frontend/lib/utils/datetime_utility_spec.js40
-rw-r--r--spec/frontend/lib/utils/finite_state_machine_spec.js293
-rw-r--r--spec/frontend/lib/utils/text_markdown_spec.js2
-rw-r--r--spec/frontend/lib/utils/url_utility_spec.js115
8 files changed, 626 insertions, 143 deletions
diff --git a/spec/frontend/lib/dompurify_spec.js b/spec/frontend/lib/dompurify_spec.js
index a01f86678e9..fa8dbb12a08 100644
--- a/spec/frontend/lib/dompurify_spec.js
+++ b/spec/frontend/lib/dompurify_spec.js
@@ -30,6 +30,9 @@ const unsafeUrls = [
`https://evil.url/${absoluteGon.sprite_file_icons}`,
];
+const forbiddenDataAttrs = ['data-remote', 'data-url', 'data-type', 'data-method'];
+const acceptedDataAttrs = ['data-random', 'data-custom'];
+
describe('~/lib/dompurify', () => {
let originalGon;
@@ -95,4 +98,17 @@ describe('~/lib/dompurify', () => {
expect(sanitize(htmlXlink)).toBe(expectedSanitized);
});
});
+
+ describe('handles data attributes correctly', () => {
+ it.each(forbiddenDataAttrs)('removes %s attributes', (attr) => {
+ const htmlHref = `<a ${attr}="true">hello</a>`;
+ expect(sanitize(htmlHref)).toBe('<a>hello</a>');
+ });
+
+ it.each(acceptedDataAttrs)('does not remove %s attributes', (attr) => {
+ const attrWithValue = `${attr}="true"`;
+ const htmlHref = `<a ${attrWithValue}>hello</a>`;
+ expect(sanitize(htmlHref)).toBe(`<a ${attrWithValue}>hello</a>`);
+ });
+ });
});
diff --git a/spec/frontend/lib/graphql_spec.js b/spec/frontend/lib/graphql_spec.js
new file mode 100644
index 00000000000..a39ce2ffd99
--- /dev/null
+++ b/spec/frontend/lib/graphql_spec.js
@@ -0,0 +1,54 @@
+import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
+import { stripWhitespaceFromQuery } from '~/lib/graphql';
+import { queryToObject } from '~/lib/utils/url_utility';
+
+describe('stripWhitespaceFromQuery', () => {
+ const operationName = 'getPipelineDetails';
+ const variables = `{
+ projectPath: 'root/abcd-dag',
+ iid: '44'
+ }`;
+
+ const testQuery = getPipelineDetails.loc.source.body;
+ const defaultPath = '/api/graphql';
+ const encodedVariables = encodeURIComponent(variables);
+
+ it('shortens the query argument by replacing multiple spaces and newlines with a single space', () => {
+ const testString = `${defaultPath}?query=${encodeURIComponent(testQuery)}`;
+ expect(testString.length > stripWhitespaceFromQuery(testString, defaultPath).length).toBe(true);
+ });
+
+ it('does not contract a single space', () => {
+ const simpleSingleString = `${defaultPath}?query=${encodeURIComponent('fragment Nonsense')}`;
+ expect(stripWhitespaceFromQuery(simpleSingleString, defaultPath)).toEqual(simpleSingleString);
+ });
+
+ it('works with a non-default path', () => {
+ const newPath = 'another/graphql/path';
+ const newPathSingleString = `${newPath}?query=${encodeURIComponent('fragment Nonsense')}`;
+ expect(stripWhitespaceFromQuery(newPathSingleString, newPath)).toEqual(newPathSingleString);
+ });
+
+ it('does not alter other arguments', () => {
+ const bareParams = `?query=${encodeURIComponent(
+ testQuery,
+ )}&operationName=${operationName}&variables=${encodedVariables}`;
+ const testLongString = `${defaultPath}${bareParams}`;
+
+ const processed = stripWhitespaceFromQuery(testLongString, defaultPath);
+ const decoded = decodeURIComponent(processed);
+ const params = queryToObject(decoded);
+
+ expect(params.operationName).toBe(operationName);
+ expect(params.variables).toBe(variables);
+ });
+
+ it('works when there are no query params', () => {
+ expect(stripWhitespaceFromQuery(defaultPath, defaultPath)).toEqual(defaultPath);
+ });
+
+ it('works when the params do not include a query', () => {
+ const paramsWithoutQuery = `${defaultPath}&variables=${encodedVariables}`;
+ expect(stripWhitespaceFromQuery(paramsWithoutQuery, defaultPath)).toEqual(paramsWithoutQuery);
+ });
+});
diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js
index e03d1ef7295..f5a74ee7f09 100644
--- a/spec/frontend/lib/utils/common_utils_spec.js
+++ b/spec/frontend/lib/utils/common_utils_spec.js
@@ -1,6 +1,56 @@
import * as commonUtils from '~/lib/utils/common_utils';
describe('common_utils', () => {
+ describe('getPagePath', () => {
+ const { getPagePath } = commonUtils;
+
+ let originalBody;
+
+ beforeEach(() => {
+ originalBody = document.body;
+ document.body = document.createElement('body');
+ });
+
+ afterEach(() => {
+ document.body = originalBody;
+ });
+
+ it('returns an empty path if none is defined', () => {
+ expect(getPagePath()).toBe('');
+ expect(getPagePath(0)).toBe('');
+ });
+
+ describe('returns a path', () => {
+ const mockSection = 'my_section';
+ const mockSubSection = 'my_sub_section';
+ const mockPage = 'my_page';
+
+ it('returns a page', () => {
+ document.body.dataset.page = mockPage;
+
+ expect(getPagePath()).toBe(mockPage);
+ expect(getPagePath(0)).toBe(mockPage);
+ });
+
+ it('returns a section and page', () => {
+ document.body.dataset.page = `${mockSection}:${mockPage}`;
+
+ expect(getPagePath()).toBe(mockSection);
+ expect(getPagePath(0)).toBe(mockSection);
+ expect(getPagePath(1)).toBe(mockPage);
+ });
+
+ it('returns a section and subsection', () => {
+ document.body.dataset.page = `${mockSection}:${mockSubSection}:${mockPage}`;
+
+ expect(getPagePath()).toBe(mockSection);
+ expect(getPagePath(0)).toBe(mockSection);
+ expect(getPagePath(1)).toBe(mockSubSection);
+ expect(getPagePath(2)).toBe(mockPage);
+ });
+ });
+ });
+
describe('parseUrl', () => {
it('returns an anchor tag with url', () => {
expect(commonUtils.parseUrl('/some/absolute/url').pathname).toContain('some/absolute/url');
@@ -26,42 +76,6 @@ describe('common_utils', () => {
});
});
- describe('urlParamsToArray', () => {
- it('returns empty array for empty querystring', () => {
- expect(commonUtils.urlParamsToArray('')).toEqual([]);
- });
-
- it('should decode params', () => {
- expect(commonUtils.urlParamsToArray('?label_name%5B%5D=test')[0]).toBe('label_name[]=test');
- });
-
- it('should remove the question mark from the search params', () => {
- const paramsArray = commonUtils.urlParamsToArray('?test=thing');
-
- expect(paramsArray[0][0]).not.toBe('?');
- });
- });
-
- describe('urlParamsToObject', () => {
- it('parses path for label with trailing +', () => {
- expect(commonUtils.urlParamsToObject('label_name[]=label%2B', {})).toEqual({
- label_name: ['label+'],
- });
- });
-
- it('parses path for milestone with trailing +', () => {
- expect(commonUtils.urlParamsToObject('milestone_title=A%2B', {})).toEqual({
- milestone_title: 'A+',
- });
- });
-
- it('parses path for search terms with spaces', () => {
- expect(commonUtils.urlParamsToObject('search=two+words', {})).toEqual({
- search: 'two words',
- });
- });
- });
-
describe('handleLocationHash', () => {
beforeEach(() => {
jest.spyOn(window.document, 'getElementById');
@@ -175,33 +189,6 @@ describe('common_utils', () => {
});
});
- describe('parseQueryStringIntoObject', () => {
- it('should return object with query parameters', () => {
- expect(commonUtils.parseQueryStringIntoObject('scope=all&page=2')).toEqual({
- scope: 'all',
- page: '2',
- });
-
- expect(commonUtils.parseQueryStringIntoObject('scope=all')).toEqual({ scope: 'all' });
- expect(commonUtils.parseQueryStringIntoObject()).toEqual({});
- });
- });
-
- describe('objectToQueryString', () => {
- it('returns empty string when `param` is undefined, null or empty string', () => {
- expect(commonUtils.objectToQueryString()).toBe('');
- expect(commonUtils.objectToQueryString('')).toBe('');
- });
-
- it('returns query string with values of `params`', () => {
- const singleQueryParams = { foo: true };
- const multipleQueryParams = { foo: true, bar: true };
-
- expect(commonUtils.objectToQueryString(singleQueryParams)).toBe('foo=true');
- expect(commonUtils.objectToQueryString(multipleQueryParams)).toBe('foo=true&bar=true');
- });
- });
-
describe('buildUrlWithCurrentLocation', () => {
it('should build an url with current location and given parameters', () => {
expect(commonUtils.buildUrlWithCurrentLocation()).toEqual(window.location.pathname);
@@ -310,39 +297,6 @@ describe('common_utils', () => {
});
});
- describe('getParameterByName', () => {
- beforeEach(() => {
- window.history.pushState({}, null, '?scope=all&p=2');
- });
-
- afterEach(() => {
- window.history.replaceState({}, null, null);
- });
-
- it('should return valid parameter', () => {
- const value = commonUtils.getParameterByName('scope');
-
- expect(commonUtils.getParameterByName('p')).toEqual('2');
- expect(value).toBe('all');
- });
-
- it('should return invalid parameter', () => {
- const value = commonUtils.getParameterByName('fakeParameter');
-
- expect(value).toBe(null);
- });
-
- it('should return valid paramentes if URL is provided', () => {
- let value = commonUtils.getParameterByName('foo', 'http://cocteau.twins/?foo=bar');
-
- expect(value).toBe('bar');
-
- value = commonUtils.getParameterByName('manan', 'http://cocteau.twins/?foo=bar&manan=canchu');
-
- expect(value).toBe('canchu');
- });
- });
-
describe('normalizedHeaders', () => {
it('should upperCase all the header keys to keep them consistent', () => {
const apiHeaders = {
diff --git a/spec/frontend/lib/utils/datetime/timeago_utility_spec.js b/spec/frontend/lib/utils/datetime/timeago_utility_spec.js
new file mode 100644
index 00000000000..2314ec678d3
--- /dev/null
+++ b/spec/frontend/lib/utils/datetime/timeago_utility_spec.js
@@ -0,0 +1,103 @@
+import { getTimeago, localTimeAgo, timeFor } from '~/lib/utils/datetime/timeago_utility';
+import { s__ } from '~/locale';
+import '~/commons/bootstrap';
+
+describe('TimeAgo utils', () => {
+ let oldGon;
+
+ afterEach(() => {
+ window.gon = oldGon;
+ });
+
+ beforeEach(() => {
+ oldGon = window.gon;
+ });
+
+ describe('getTimeago', () => {
+ describe('with User Setting timeDisplayRelative: true', () => {
+ beforeEach(() => {
+ window.gon = { time_display_relative: true };
+ });
+
+ it.each([
+ [new Date().toISOString(), 'just now'],
+ [new Date().getTime(), 'just now'],
+ [new Date(), 'just now'],
+ [null, 'just now'],
+ ])('formats date `%p` as `%p`', (date, result) => {
+ expect(getTimeago().format(date)).toEqual(result);
+ });
+ });
+
+ describe('with User Setting timeDisplayRelative: false', () => {
+ beforeEach(() => {
+ window.gon = { time_display_relative: false };
+ });
+
+ it.each([
+ [new Date().toISOString(), 'Jul 6, 2020, 12:00 AM'],
+ [new Date(), 'Jul 6, 2020, 12:00 AM'],
+ [new Date().getTime(), 'Jul 6, 2020, 12:00 AM'],
+ // Slightly different behaviour when `null` is passed :see_no_evil`
+ [null, 'Jan 1, 1970, 12:00 AM'],
+ ])('formats date `%p` as `%p`', (date, result) => {
+ expect(getTimeago().format(date)).toEqual(result);
+ });
+ });
+ });
+
+ describe('timeFor', () => {
+ it('returns localize `past due` when in past', () => {
+ const date = new Date();
+ date.setFullYear(date.getFullYear() - 1);
+
+ expect(timeFor(date)).toBe(s__('Timeago|Past due'));
+ });
+
+ it('returns localized remaining time when in the future', () => {
+ const date = new Date();
+ date.setFullYear(date.getFullYear() + 1);
+
+ // Add a day to prevent a transient error. If date is even 1 second
+ // short of a full year, timeFor will return '11 months remaining'
+ date.setDate(date.getDate() + 1);
+
+ expect(timeFor(date)).toBe(s__('Timeago|1 year remaining'));
+ });
+ });
+
+ describe('localTimeAgo', () => {
+ beforeEach(() => {
+ document.body.innerHTML =
+ '<time title="some time" datetime="2020-02-18T22:22:32Z">1 hour ago</time>';
+ });
+
+ describe.each`
+ timeDisplayRelative | text
+ ${true} | ${'4 months ago'}
+ ${false} | ${'Feb 18, 2020, 10:22 PM'}
+ `(
+ `With User Setting timeDisplayRelative: $timeDisplayRelative`,
+ ({ timeDisplayRelative, text }) => {
+ it.each`
+ updateTooltip | title
+ ${false} | ${'some time'}
+ ${true} | ${'Feb 18, 2020 10:22pm UTC'}
+ `(
+ `has content: '${text}' and tooltip: '$title' with updateTooltip = $updateTooltip`,
+ ({ updateTooltip, title }) => {
+ window.gon = { time_display_relative: timeDisplayRelative };
+
+ const element = document.querySelector('time');
+ localTimeAgo([element], updateTooltip);
+
+ jest.runAllTimers();
+
+ expect(element.getAttribute('title')).toBe(title);
+ expect(element.innerText).toBe(text);
+ },
+ );
+ },
+ );
+ });
+});
diff --git a/spec/frontend/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js
index df0ccb19cb7..f6ad41d5478 100644
--- a/spec/frontend/lib/utils/datetime_utility_spec.js
+++ b/spec/frontend/lib/utils/datetime_utility_spec.js
@@ -1,30 +1,9 @@
-import $ from 'jquery';
import timezoneMock from 'timezone-mock';
import * as datetimeUtility from '~/lib/utils/datetime_utility';
import { __, s__ } from '~/locale';
import '~/commons/bootstrap';
describe('Date time utils', () => {
- describe('timeFor', () => {
- it('returns localize `past due` when in past', () => {
- const date = new Date();
- date.setFullYear(date.getFullYear() - 1);
-
- expect(datetimeUtility.timeFor(date)).toBe(s__('Timeago|Past due'));
- });
-
- it('returns localized remaining time when in the future', () => {
- const date = new Date();
- date.setFullYear(date.getFullYear() + 1);
-
- // Add a day to prevent a transient error. If date is even 1 second
- // short of a full year, timeFor will return '11 months remaining'
- date.setDate(date.getDate() + 1);
-
- expect(datetimeUtility.timeFor(date)).toBe(s__('Timeago|1 year remaining'));
- });
- });
-
describe('get localized day name', () => {
it('should return Sunday', () => {
const day = datetimeUtility.getDayName(new Date('07/17/2016'));
@@ -870,25 +849,6 @@ describe('approximateDuration', () => {
});
});
-describe('localTimeAgo', () => {
- beforeEach(() => {
- document.body.innerHTML = `<time title="some time" datetime="2020-02-18T22:22:32Z">1 hour ago</time>`;
- });
-
- it.each`
- timeagoArg | title
- ${false} | ${'some time'}
- ${true} | ${'Feb 18, 2020 10:22pm UTC'}
- `('converts $seconds seconds to $approximation', ({ timeagoArg, title }) => {
- const element = document.querySelector('time');
- datetimeUtility.localTimeAgo($(element), timeagoArg);
-
- jest.runAllTimers();
-
- expect(element.getAttribute('title')).toBe(title);
- });
-});
-
describe('differenceInSeconds', () => {
const startDateTime = new Date('2019-07-17T00:00:00.000Z');
diff --git a/spec/frontend/lib/utils/finite_state_machine_spec.js b/spec/frontend/lib/utils/finite_state_machine_spec.js
new file mode 100644
index 00000000000..441dd24c758
--- /dev/null
+++ b/spec/frontend/lib/utils/finite_state_machine_spec.js
@@ -0,0 +1,293 @@
+import { machine, transition } from '~/lib/utils/finite_state_machine';
+
+describe('Finite State Machine', () => {
+ const STATE_IDLE = 'idle';
+ const STATE_LOADING = 'loading';
+ const STATE_ERRORED = 'errored';
+
+ const TRANSITION_START_LOAD = 'START_LOAD';
+ const TRANSITION_LOAD_ERROR = 'LOAD_ERROR';
+ const TRANSITION_LOAD_SUCCESS = 'LOAD_SUCCESS';
+ const TRANSITION_ACKNOWLEDGE_ERROR = 'ACKNOWLEDGE_ERROR';
+
+ const definition = {
+ initial: STATE_IDLE,
+ states: {
+ [STATE_IDLE]: {
+ on: {
+ [TRANSITION_START_LOAD]: STATE_LOADING,
+ },
+ },
+ [STATE_LOADING]: {
+ on: {
+ [TRANSITION_LOAD_ERROR]: STATE_ERRORED,
+ [TRANSITION_LOAD_SUCCESS]: STATE_IDLE,
+ },
+ },
+ [STATE_ERRORED]: {
+ on: {
+ [TRANSITION_ACKNOWLEDGE_ERROR]: STATE_IDLE,
+ [TRANSITION_START_LOAD]: STATE_LOADING,
+ },
+ },
+ },
+ };
+
+ describe('machine', () => {
+ const STATE_IMPOSSIBLE = 'impossible';
+ const badDefinition = {
+ init: definition.initial,
+ badKeyShouldBeStates: definition.states,
+ };
+ const unstartableDefinition = {
+ initial: STATE_IMPOSSIBLE,
+ states: definition.states,
+ };
+ let liveMachine;
+
+ beforeEach(() => {
+ liveMachine = machine(definition);
+ });
+
+ it('throws an error if the machine definition is invalid', () => {
+ expect(() => machine(badDefinition)).toThrowError(
+ 'A state machine must have an initial state (`.initial`) and a dictionary of possible states (`.states`)',
+ );
+ });
+
+ it('throws an error if the initial state is invalid', () => {
+ expect(() => machine(unstartableDefinition)).toThrowError(
+ `Cannot initialize the state machine to state '${STATE_IMPOSSIBLE}'. Is that one of the machine's defined states?`,
+ );
+ });
+
+ it.each`
+ partOfMachine | equals | description | eqDescription
+ ${'keys'} | ${['is', 'send', 'value', 'states']} | ${'keys'} | ${'the correct array'}
+ ${'is'} | ${expect.any(Function)} | ${'`is` property'} | ${'a function'}
+ ${'send'} | ${expect.any(Function)} | ${'`send` property'} | ${'a function'}
+ ${'value'} | ${definition.initial} | ${'`value` property'} | ${'the same as the `initial` value of the machine definition'}
+ ${'states'} | ${definition.states} | ${'`states` property'} | ${'the same as the `states` value of the machine definition'}
+ `("The machine's $description should be $eqDescription", ({ partOfMachine, equals }) => {
+ const test = partOfMachine === 'keys' ? Object.keys(liveMachine) : liveMachine[partOfMachine];
+
+ expect(test).toEqual(equals);
+ });
+
+ it.each`
+ initialState | transitionEvent | expectedState
+ ${definition.initial} | ${TRANSITION_START_LOAD} | ${STATE_LOADING}
+ ${STATE_LOADING} | ${TRANSITION_LOAD_ERROR} | ${STATE_ERRORED}
+ ${STATE_ERRORED} | ${TRANSITION_ACKNOWLEDGE_ERROR} | ${STATE_IDLE}
+ ${STATE_IDLE} | ${TRANSITION_START_LOAD} | ${STATE_LOADING}
+ ${STATE_LOADING} | ${TRANSITION_LOAD_SUCCESS} | ${STATE_IDLE}
+ `(
+ 'properly steps from $initialState to $expectedState when the event "$transitionEvent" is sent',
+ ({ initialState, transitionEvent, expectedState }) => {
+ liveMachine.value = initialState;
+
+ liveMachine.send(transitionEvent);
+
+ expect(liveMachine.is(expectedState)).toBe(true);
+ expect(liveMachine.value).toBe(expectedState);
+ },
+ );
+
+ it.each`
+ initialState | transitionEvent
+ ${STATE_IDLE} | ${TRANSITION_ACKNOWLEDGE_ERROR}
+ ${STATE_IDLE} | ${TRANSITION_LOAD_SUCCESS}
+ ${STATE_IDLE} | ${TRANSITION_LOAD_ERROR}
+ ${STATE_IDLE} | ${'RANDOM_FOO'}
+ ${STATE_LOADING} | ${TRANSITION_START_LOAD}
+ ${STATE_LOADING} | ${TRANSITION_ACKNOWLEDGE_ERROR}
+ ${STATE_LOADING} | ${'RANDOM_FOO'}
+ ${STATE_ERRORED} | ${TRANSITION_LOAD_ERROR}
+ ${STATE_ERRORED} | ${TRANSITION_LOAD_SUCCESS}
+ ${STATE_ERRORED} | ${'RANDOM_FOO'}
+ `(
+ `does not perform any transition if the machine can't move from "$initialState" using the "$transitionEvent" event`,
+ ({ initialState, transitionEvent }) => {
+ liveMachine.value = initialState;
+
+ liveMachine.send(transitionEvent);
+
+ expect(liveMachine.is(initialState)).toBe(true);
+ expect(liveMachine.value).toBe(initialState);
+ },
+ );
+
+ describe('send', () => {
+ it.each`
+ startState | transitionEvent | result
+ ${STATE_IDLE} | ${TRANSITION_START_LOAD} | ${STATE_LOADING}
+ ${STATE_LOADING} | ${TRANSITION_LOAD_SUCCESS} | ${STATE_IDLE}
+ ${STATE_LOADING} | ${TRANSITION_LOAD_ERROR} | ${STATE_ERRORED}
+ ${STATE_ERRORED} | ${TRANSITION_ACKNOWLEDGE_ERROR} | ${STATE_IDLE}
+ ${STATE_ERRORED} | ${TRANSITION_START_LOAD} | ${STATE_LOADING}
+ `(
+ 'successfully transitions to $result from $startState when the transition $transitionEvent is received',
+ ({ startState, transitionEvent, result }) => {
+ liveMachine.value = startState;
+
+ expect(liveMachine.send(transitionEvent)).toEqual(result);
+ },
+ );
+
+ it.each`
+ startState | transitionEvent
+ ${STATE_IDLE} | ${TRANSITION_ACKNOWLEDGE_ERROR}
+ ${STATE_IDLE} | ${TRANSITION_LOAD_SUCCESS}
+ ${STATE_IDLE} | ${TRANSITION_LOAD_ERROR}
+ ${STATE_IDLE} | ${'RANDOM_FOO'}
+ ${STATE_LOADING} | ${TRANSITION_START_LOAD}
+ ${STATE_LOADING} | ${TRANSITION_ACKNOWLEDGE_ERROR}
+ ${STATE_LOADING} | ${'RANDOM_FOO'}
+ ${STATE_ERRORED} | ${TRANSITION_LOAD_ERROR}
+ ${STATE_ERRORED} | ${TRANSITION_LOAD_SUCCESS}
+ ${STATE_ERRORED} | ${'RANDOM_FOO'}
+ `(
+ 'remains as $startState if an undefined transition ($transitionEvent) is received',
+ ({ startState, transitionEvent }) => {
+ liveMachine.value = startState;
+
+ expect(liveMachine.send(transitionEvent)).toEqual(startState);
+ },
+ );
+
+ describe('detached', () => {
+ it.each`
+ startState | transitionEvent | result
+ ${STATE_IDLE} | ${TRANSITION_START_LOAD} | ${STATE_LOADING}
+ ${STATE_LOADING} | ${TRANSITION_LOAD_SUCCESS} | ${STATE_IDLE}
+ ${STATE_LOADING} | ${TRANSITION_LOAD_ERROR} | ${STATE_ERRORED}
+ ${STATE_ERRORED} | ${TRANSITION_ACKNOWLEDGE_ERROR} | ${STATE_IDLE}
+ ${STATE_ERRORED} | ${TRANSITION_START_LOAD} | ${STATE_LOADING}
+ `(
+ 'successfully transitions to $result from $startState when the transition $transitionEvent is received outside the context of the machine',
+ ({ startState, transitionEvent, result }) => {
+ const liveSend = machine({
+ ...definition,
+ initial: startState,
+ }).send;
+
+ expect(liveSend(transitionEvent)).toEqual(result);
+ },
+ );
+
+ it.each`
+ startState | transitionEvent
+ ${STATE_IDLE} | ${TRANSITION_ACKNOWLEDGE_ERROR}
+ ${STATE_IDLE} | ${TRANSITION_LOAD_SUCCESS}
+ ${STATE_IDLE} | ${TRANSITION_LOAD_ERROR}
+ ${STATE_IDLE} | ${'RANDOM_FOO'}
+ ${STATE_LOADING} | ${TRANSITION_START_LOAD}
+ ${STATE_LOADING} | ${TRANSITION_ACKNOWLEDGE_ERROR}
+ ${STATE_LOADING} | ${'RANDOM_FOO'}
+ ${STATE_ERRORED} | ${TRANSITION_LOAD_ERROR}
+ ${STATE_ERRORED} | ${TRANSITION_LOAD_SUCCESS}
+ ${STATE_ERRORED} | ${'RANDOM_FOO'}
+ `(
+ 'remains as $startState if an undefined transition ($transitionEvent) is received',
+ ({ startState, transitionEvent }) => {
+ const liveSend = machine({
+ ...definition,
+ initial: startState,
+ }).send;
+
+ expect(liveSend(transitionEvent)).toEqual(startState);
+ },
+ );
+ });
+ });
+
+ describe('is', () => {
+ it.each`
+ bool | test | actual
+ ${true} | ${STATE_IDLE} | ${STATE_IDLE}
+ ${false} | ${STATE_LOADING} | ${STATE_IDLE}
+ ${false} | ${STATE_ERRORED} | ${STATE_IDLE}
+ ${true} | ${STATE_LOADING} | ${STATE_LOADING}
+ ${false} | ${STATE_IDLE} | ${STATE_LOADING}
+ ${false} | ${STATE_ERRORED} | ${STATE_LOADING}
+ ${true} | ${STATE_ERRORED} | ${STATE_ERRORED}
+ ${false} | ${STATE_IDLE} | ${STATE_ERRORED}
+ ${false} | ${STATE_LOADING} | ${STATE_ERRORED}
+ `(
+ 'returns "$bool" for "$test" when the current state is "$actual"',
+ ({ bool, test, actual }) => {
+ liveMachine = machine({
+ ...definition,
+ initial: actual,
+ });
+
+ expect(liveMachine.is(test)).toEqual(bool);
+ },
+ );
+
+ describe('detached', () => {
+ it.each`
+ bool | test | actual
+ ${true} | ${STATE_IDLE} | ${STATE_IDLE}
+ ${false} | ${STATE_LOADING} | ${STATE_IDLE}
+ ${false} | ${STATE_ERRORED} | ${STATE_IDLE}
+ ${true} | ${STATE_LOADING} | ${STATE_LOADING}
+ ${false} | ${STATE_IDLE} | ${STATE_LOADING}
+ ${false} | ${STATE_ERRORED} | ${STATE_LOADING}
+ ${true} | ${STATE_ERRORED} | ${STATE_ERRORED}
+ ${false} | ${STATE_IDLE} | ${STATE_ERRORED}
+ ${false} | ${STATE_LOADING} | ${STATE_ERRORED}
+ `(
+ 'returns "$bool" for "$test" when the current state is "$actual"',
+ ({ bool, test, actual }) => {
+ const liveIs = machine({
+ ...definition,
+ initial: actual,
+ }).is;
+
+ expect(liveIs(test)).toEqual(bool);
+ },
+ );
+ });
+ });
+ });
+
+ describe('transition', () => {
+ it.each`
+ startState | transitionEvent | result
+ ${STATE_IDLE} | ${TRANSITION_START_LOAD} | ${STATE_LOADING}
+ ${STATE_LOADING} | ${TRANSITION_LOAD_SUCCESS} | ${STATE_IDLE}
+ ${STATE_LOADING} | ${TRANSITION_LOAD_ERROR} | ${STATE_ERRORED}
+ ${STATE_ERRORED} | ${TRANSITION_ACKNOWLEDGE_ERROR} | ${STATE_IDLE}
+ ${STATE_ERRORED} | ${TRANSITION_START_LOAD} | ${STATE_LOADING}
+ `(
+ 'successfully transitions to $result from $startState when the transition $transitionEvent is received',
+ ({ startState, transitionEvent, result }) => {
+ expect(transition(definition, startState, transitionEvent)).toEqual(result);
+ },
+ );
+
+ it.each`
+ startState | transitionEvent
+ ${STATE_IDLE} | ${TRANSITION_ACKNOWLEDGE_ERROR}
+ ${STATE_IDLE} | ${TRANSITION_LOAD_SUCCESS}
+ ${STATE_IDLE} | ${TRANSITION_LOAD_ERROR}
+ ${STATE_IDLE} | ${'RANDOM_FOO'}
+ ${STATE_LOADING} | ${TRANSITION_START_LOAD}
+ ${STATE_LOADING} | ${TRANSITION_ACKNOWLEDGE_ERROR}
+ ${STATE_LOADING} | ${'RANDOM_FOO'}
+ ${STATE_ERRORED} | ${TRANSITION_LOAD_ERROR}
+ ${STATE_ERRORED} | ${TRANSITION_LOAD_SUCCESS}
+ ${STATE_ERRORED} | ${'RANDOM_FOO'}
+ `(
+ 'remains as $startState if an undefined transition ($transitionEvent) is received',
+ ({ startState, transitionEvent }) => {
+ expect(transition(definition, startState, transitionEvent)).toEqual(startState);
+ },
+ );
+
+ it('remains as the provided starting state if it is an unrecognized state', () => {
+ expect(transition(definition, 'RANDOM_FOO', TRANSITION_START_LOAD)).toEqual('RANDOM_FOO');
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js
index cad500039c0..beedb9b2eba 100644
--- a/spec/frontend/lib/utils/text_markdown_spec.js
+++ b/spec/frontend/lib/utils/text_markdown_spec.js
@@ -300,7 +300,7 @@ describe('init markdown', () => {
});
});
- describe('Editor Lite', () => {
+ describe('Source Editor', () => {
let editor;
beforeEach(() => {
diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
index 31c78681994..66d0faa95e7 100644
--- a/spec/frontend/lib/utils/url_utility_spec.js
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -24,6 +24,16 @@ const setWindowLocation = (value) => {
};
describe('URL utility', () => {
+ let originalLocation;
+
+ beforeAll(() => {
+ originalLocation = window.location;
+ });
+
+ afterAll(() => {
+ window.location = originalLocation;
+ });
+
describe('webIDEUrl', () => {
afterEach(() => {
gon.relative_url_root = '';
@@ -319,19 +329,17 @@ describe('URL utility', () => {
});
describe('doesHashExistInUrl', () => {
- it('should return true when the given string exists in the URL hash', () => {
+ beforeEach(() => {
setWindowLocation({
- href: 'https://gitlab.com/gitlab-org/gitlab-test/issues/1#note_1',
+ hash: 'https://gitlab.com/gitlab-org/gitlab-test/issues/1#note_1',
});
+ });
+ it('should return true when the given string exists in the URL hash', () => {
expect(urlUtils.doesHashExistInUrl('note_')).toBe(true);
});
it('should return false when the given string does not exist in the URL hash', () => {
- setWindowLocation({
- href: 'https://gitlab.com/gitlab-org/gitlab-test/issues/1#note_1',
- });
-
expect(urlUtils.doesHashExistInUrl('doesnotexist')).toBe(false);
});
});
@@ -651,6 +659,45 @@ describe('URL utility', () => {
});
});
+ describe('urlParamsToArray', () => {
+ it('returns empty array for empty querystring', () => {
+ expect(urlUtils.urlParamsToArray('')).toEqual([]);
+ });
+
+ it('should decode params', () => {
+ expect(urlUtils.urlParamsToArray('?label_name%5B%5D=test')[0]).toBe('label_name[]=test');
+ });
+
+ it('should remove the question mark from the search params', () => {
+ const paramsArray = urlUtils.urlParamsToArray('?test=thing');
+
+ expect(paramsArray[0][0]).not.toBe('?');
+ });
+ });
+
+ 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
@@ -673,12 +720,68 @@ describe('URL utility', () => {
});
});
+ describe('getParameterByName', () => {
+ const { getParameterByName } = urlUtils;
+
+ it('should return valid parameter', () => {
+ setWindowLocation({ search: '?scope=all&p=2' });
+
+ expect(getParameterByName('p')).toEqual('2');
+ expect(getParameterByName('scope')).toBe('all');
+ });
+
+ it('should return invalid parameter', () => {
+ setWindowLocation({ search: '?scope=all&p=2' });
+
+ expect(getParameterByName('fakeParameter')).toBe(null);
+ });
+
+ it('should return a parameter with spaces', () => {
+ setWindowLocation({ search: '?search=my terms' });
+
+ expect(getParameterByName('search')).toBe('my terms');
+ });
+
+ it('should return a parameter with encoded spaces', () => {
+ setWindowLocation({ search: '?search=my%20terms' });
+
+ expect(getParameterByName('search')).toBe('my terms');
+ });
+
+ it('should return a parameter with plus signs as spaces', () => {
+ setWindowLocation({ search: '?search=my+terms' });
+
+ expect(getParameterByName('search')).toBe('my terms');
+ });
+
+ it('should return valid parameters if search is provided', () => {
+ expect(getParameterByName('foo', 'foo=bar')).toBe('bar');
+ expect(getParameterByName('foo', '?foo=bar')).toBe('bar');
+
+ expect(getParameterByName('manan', 'foo=bar&manan=canchu')).toBe('canchu');
+ expect(getParameterByName('manan', '?foo=bar&manan=canchu')).toBe('canchu');
+ });
+ });
+
describe('objectToQuery', () => {
it('converts search query object back into a search query', () => {
const searchQueryObject = { one: '1', two: '2' };
expect(urlUtils.objectToQuery(searchQueryObject)).toEqual('one=1&two=2');
});
+
+ it('returns empty string when `params` is undefined, null or empty string', () => {
+ expect(urlUtils.objectToQuery()).toBe('');
+ expect(urlUtils.objectToQuery('')).toBe('');
+ });
+
+ it('returns query string with values of `params`', () => {
+ const singleQueryParams = { foo: true };
+ const multipleQueryParams = { foo: true, bar: true };
+
+ expect(urlUtils.objectToQuery(singleQueryParams)).toBe('foo=true');
+ expect(urlUtils.objectToQuery(multipleQueryParams)).toBe('foo=true&bar=true');
+ });
});
describe('cleanLeadingSeparator', () => {