summaryrefslogtreecommitdiff
path: root/spec/frontend
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-10-13 12:08:41 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-10-13 12:08:41 +0000
commit6e91fbf77476011a7fd86ca3467aad6d7b110ff3 (patch)
treecace6db4e7ebef8b15a6a7fc8fbe8ff0d89bea90 /spec/frontend
parent15ae4a8da83661f2b714d804721001a53b354d28 (diff)
downloadgitlab-ce-6e91fbf77476011a7fd86ca3467aad6d7b110ff3.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/analytics/instance_statistics/components/instance_counts_spec.js6
-rw-r--r--spec/frontend/analytics/instance_statistics/mock_data.js28
-rw-r--r--spec/frontend/analytics/instance_statistics/utils_spec.js41
-rw-r--r--spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap90
-rw-r--r--spec/frontend/ide/components/commit_sidebar/form_spec.js37
-rw-r--r--spec/frontend/ide/lib/errors_spec.js46
-rw-r--r--spec/frontend/ide/stores/getters_spec.js10
-rw-r--r--spec/frontend/ide/stores/modules/commit/actions_spec.js82
-rw-r--r--spec/frontend/ide/utils_spec.js40
-rw-r--r--spec/frontend/incidents/components/incidents_list_spec.js18
-rw-r--r--spec/frontend/issuable_show/components/issuable_description_spec.js41
-rw-r--r--spec/frontend/issuable_show/components/issuable_edit_form_spec.js122
-rw-r--r--spec/frontend/issuable_show/components/issuable_title_spec.js100
-rw-r--r--spec/frontend/monitoring/router_spec.js3
-rw-r--r--spec/frontend/packages/details/components/composer_installation_spec.js7
-rw-r--r--spec/frontend/packages/details/store/getters_spec.js26
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/split_button_spec.js.snap37
-rw-r--r--spec/frontend/vue_shared/components/split_button_spec.js6
18 files changed, 602 insertions, 138 deletions
diff --git a/spec/frontend/analytics/instance_statistics/components/instance_counts_spec.js b/spec/frontend/analytics/instance_statistics/components/instance_counts_spec.js
index 2274f4c3fde..12b5e14b9c4 100644
--- a/spec/frontend/analytics/instance_statistics/components/instance_counts_spec.js
+++ b/spec/frontend/analytics/instance_statistics/components/instance_counts_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import InstanceCounts from '~/analytics/instance_statistics/components/instance_counts.vue';
import MetricCard from '~/analytics/shared/components/metric_card.vue';
-import countsMockData from '../mock_data';
+import { mockInstanceCounts } from '../mock_data';
describe('InstanceCounts', () => {
let wrapper;
@@ -44,11 +44,11 @@ describe('InstanceCounts', () => {
describe('with data', () => {
beforeEach(() => {
- createComponent({ data: { counts: countsMockData } });
+ createComponent({ data: { counts: mockInstanceCounts } });
});
it('passes the counts data to the metric card', () => {
- expect(findMetricCard().props('metrics')).toEqual(countsMockData);
+ expect(findMetricCard().props('metrics')).toEqual(mockInstanceCounts);
});
});
});
diff --git a/spec/frontend/analytics/instance_statistics/mock_data.js b/spec/frontend/analytics/instance_statistics/mock_data.js
index 9fabf3a4c65..c3f5069da28 100644
--- a/spec/frontend/analytics/instance_statistics/mock_data.js
+++ b/spec/frontend/analytics/instance_statistics/mock_data.js
@@ -1,4 +1,30 @@
-export default [
+export const mockInstanceCounts = [
{ key: 'projects', value: 10, label: 'Projects' },
{ key: 'groups', value: 20, label: 'Group' },
];
+
+export const mockCountsData1 = [
+ { recordedAt: '2020-07-23', count: 52 },
+ { recordedAt: '2020-07-22', count: 40 },
+ { recordedAt: '2020-07-21', count: 31 },
+ { recordedAt: '2020-06-14', count: 23 },
+ { recordedAt: '2020-06-12', count: 20 },
+];
+
+export const countsMonthlyChartData1 = [
+ ['2020-07-01', 41], // average of 2020-07-x items
+ ['2020-06-01', 21.5], // average of 2020-06-x items
+];
+
+export const mockCountsData2 = [
+ { recordedAt: '2020-07-28', count: 10 },
+ { recordedAt: '2020-07-27', count: 9 },
+ { recordedAt: '2020-06-26', count: 14 },
+ { recordedAt: '2020-06-25', count: 23 },
+ { recordedAt: '2020-06-24', count: 25 },
+];
+
+export const countsMonthlyChartData2 = [
+ ['2020-07-01', 9.5], // average of 2020-07-x items
+ ['2020-06-01', 20.666666666666668], // average of 2020-06-x items
+];
diff --git a/spec/frontend/analytics/instance_statistics/utils_spec.js b/spec/frontend/analytics/instance_statistics/utils_spec.js
new file mode 100644
index 00000000000..f6ea81eb678
--- /dev/null
+++ b/spec/frontend/analytics/instance_statistics/utils_spec.js
@@ -0,0 +1,41 @@
+import { getAverageByMonth } from '~/analytics/instance_statistics/utils';
+import {
+ mockCountsData1,
+ mockCountsData2,
+ countsMonthlyChartData1,
+ countsMonthlyChartData2,
+} from './mock_data';
+
+describe('getAverageByMonth', () => {
+ it('collects data into average by months', () => {
+ expect(getAverageByMonth(mockCountsData1)).toStrictEqual(countsMonthlyChartData1);
+ expect(getAverageByMonth(mockCountsData2)).toStrictEqual(countsMonthlyChartData2);
+ });
+
+ it('it transforms a data point to the first of the month', () => {
+ const item = mockCountsData1[0];
+ const firstOfTheMonth = item.recordedAt.replace(/-[0-9]{2}$/, '-01');
+ expect(getAverageByMonth([item])).toStrictEqual([[firstOfTheMonth, item.count]]);
+ });
+
+ it('it uses sane defaults', () => {
+ expect(getAverageByMonth()).toStrictEqual([]);
+ });
+
+ it('it errors when passing null', () => {
+ expect(() => {
+ getAverageByMonth(null);
+ }).toThrow();
+ });
+
+ describe('when shouldRound = true', () => {
+ const options = { shouldRound: true };
+
+ it('rounds the averages', () => {
+ const roundedData1 = countsMonthlyChartData1.map(([date, avg]) => [date, Math.round(avg)]);
+ const roundedData2 = countsMonthlyChartData2.map(([date, avg]) => [date, Math.round(avg)]);
+ expect(getAverageByMonth(mockCountsData1, options)).toStrictEqual(roundedData1);
+ expect(getAverageByMonth(mockCountsData2, options)).toStrictEqual(roundedData2);
+ });
+ });
+});
diff --git a/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap b/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap
index 93b757e008a..15eeadcc8b8 100644
--- a/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap
+++ b/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap
@@ -5,14 +5,17 @@ exports[`Remove cluster confirmation modal renders splitbutton with modal includ
class="gl-display-flex gl-justify-content-end"
>
<div
- class="dropdown b-dropdown gl-dropdown btn-group"
+ class="dropdown b-dropdown gl-new-dropdown btn-group"
+ menu-class="dropdown-menu-large"
>
<button
- class="btn btn-danger"
+ class="btn btn-danger btn-md gl-button split-content-button"
type="button"
>
+ <!---->
+
<span
- class="gl-dropdown-toggle-text"
+ class="gl-new-dropdown-button-text"
>
Remove integration and resources
</span>
@@ -22,7 +25,7 @@ exports[`Remove cluster confirmation modal renders splitbutton with modal includ
<button
aria-expanded="false"
aria-haspopup="true"
- class="btn dropdown-toggle btn-danger dropdown-toggle-split"
+ class="btn dropdown-toggle btn-danger btn-md gl-button gl-dropdown-toggle dropdown-toggle-split"
type="button"
>
<span
@@ -32,29 +35,58 @@ exports[`Remove cluster confirmation modal renders splitbutton with modal includ
</span>
</button>
<ul
- class="dropdown-menu dropdown-menu-selectable dropdown-menu-large"
+ class="dropdown-menu dropdown-menu-large"
role="menu"
tabindex="-1"
>
+ <!---->
+
<li
+ class="gl-new-dropdown-item"
role="presentation"
>
<button
- class="dropdown-item is-active"
+ class="dropdown-item"
role="menuitem"
type="button"
>
- <strong>
- Remove integration and resources
- </strong>
+ <svg
+ class="gl-icon s16 gl-new-dropdown-item-check-icon"
+ data-testid="mobile-issue-close-icon"
+ >
+ <use
+ href="#mobile-issue-close"
+ />
+ </svg>
+
+ <!---->
- <div>
- Deletes all GitLab resources attached to this cluster during removal
+ <!---->
+
+ <div
+ class="gl-new-dropdown-item-text-wrapper"
+ >
+ <p
+ class="gl-new-dropdown-item-text-primary"
+ >
+ <strong>
+ Remove integration and resources
+ </strong>
+
+ <div>
+ Deletes all GitLab resources attached to this cluster during removal
+ </div>
+ </p>
+
+ <!---->
</div>
+
+ <!---->
</button>
</li>
<li
+ class="gl-new-dropdown-divider"
role="presentation"
>
<hr
@@ -64,6 +96,7 @@ exports[`Remove cluster confirmation modal renders splitbutton with modal includ
/>
</li>
<li
+ class="gl-new-dropdown-item"
role="presentation"
>
<button
@@ -71,13 +104,38 @@ exports[`Remove cluster confirmation modal renders splitbutton with modal includ
role="menuitem"
type="button"
>
- <strong>
- Remove integration
- </strong>
+ <svg
+ class="gl-icon s16 gl-new-dropdown-item-check-icon gl-visibility-hidden"
+ data-testid="mobile-issue-close-icon"
+ >
+ <use
+ href="#mobile-issue-close"
+ />
+ </svg>
+
+ <!---->
- <div>
- Removes cluster from project but keeps associated resources
+ <!---->
+
+ <div
+ class="gl-new-dropdown-item-text-wrapper"
+ >
+ <p
+ class="gl-new-dropdown-item-text-primary"
+ >
+ <strong>
+ Remove integration
+ </strong>
+
+ <div>
+ Removes cluster from project but keeps associated resources
+ </div>
+ </p>
+
+ <!---->
</div>
+
+ <!---->
</button>
</li>
diff --git a/spec/frontend/ide/components/commit_sidebar/form_spec.js b/spec/frontend/ide/components/commit_sidebar/form_spec.js
index 56667d6b03d..abd7e3bb8fc 100644
--- a/spec/frontend/ide/components/commit_sidebar/form_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/form_spec.js
@@ -7,7 +7,12 @@ import { createStore } from '~/ide/stores';
import consts from '~/ide/stores/modules/commit/constants';
import CommitForm from '~/ide/components/commit_sidebar/form.vue';
import { leftSidebarViews } from '~/ide/constants';
-import { createCodeownersCommitError, createUnexpectedCommitError } from '~/ide/lib/errors';
+import {
+ createCodeownersCommitError,
+ createUnexpectedCommitError,
+ createBranchChangedCommitError,
+ branchAlreadyExistsCommitError,
+} from '~/ide/lib/errors';
describe('IDE commit form', () => {
const Component = Vue.extend(CommitForm);
@@ -290,20 +295,30 @@ describe('IDE commit form', () => {
jest.spyOn(vm.$store, 'dispatch').mockReturnValue(Promise.resolve());
});
- it('updates commit action and commits', async () => {
- store.state.commit.commitError = createCodeownersCommitError('test message');
+ const commitActions = [
+ ['commit/updateCommitAction', consts.COMMIT_TO_NEW_BRANCH],
+ ['commit/commitChanges'],
+ ];
- await vm.$nextTick();
+ it.each`
+ commitError | expectedActions
+ ${createCodeownersCommitError} | ${commitActions}
+ ${createBranchChangedCommitError} | ${commitActions}
+ ${branchAlreadyExistsCommitError} | ${[['commit/addSuffixToBranchName'], ...commitActions]}
+ `(
+ 'updates commit action and commits for error: $commitError',
+ async ({ commitError, expectedActions }) => {
+ store.state.commit.commitError = commitError('test message');
- getByText(document.body, 'Create new branch').click();
+ await vm.$nextTick();
- await waitForPromises();
+ getByText(document.body, 'Create new branch').click();
- expect(vm.$store.dispatch.mock.calls).toEqual([
- ['commit/updateCommitAction', consts.COMMIT_TO_NEW_BRANCH],
- ['commit/commitChanges', undefined],
- ]);
- });
+ await waitForPromises();
+
+ expect(vm.$store.dispatch.mock.calls).toEqual(expectedActions);
+ },
+ );
});
});
diff --git a/spec/frontend/ide/lib/errors_spec.js b/spec/frontend/ide/lib/errors_spec.js
index 8c3fb378302..733d5a5da3c 100644
--- a/spec/frontend/ide/lib/errors_spec.js
+++ b/spec/frontend/ide/lib/errors_spec.js
@@ -2,6 +2,7 @@ import {
createUnexpectedCommitError,
createCodeownersCommitError,
createBranchChangedCommitError,
+ branchAlreadyExistsCommitError,
parseCommitError,
} from '~/ide/lib/errors';
@@ -21,35 +22,22 @@ describe('~/ide/lib/errors', () => {
},
});
- describe('createCodeownersCommitError', () => {
- it('uses given message', () => {
- expect(createCodeownersCommitError(TEST_MESSAGE)).toEqual({
- title: 'CODEOWNERS rule violation',
- messageHTML: TEST_MESSAGE,
- canCreateBranch: true,
- });
- });
+ const NEW_BRANCH_SUFFIX = `<br/><br/>Would you like to create a new branch?`;
+ const AUTOGENERATE_SUFFIX = `<br/><br/>Would you like to try auto-generating a branch name?`;
- it('escapes special chars', () => {
- expect(createCodeownersCommitError(TEST_SPECIAL)).toEqual({
- title: 'CODEOWNERS rule violation',
- messageHTML: TEST_SPECIAL_ESCAPED,
- canCreateBranch: true,
- });
- });
- });
-
- describe('createBranchChangedCommitError', () => {
- it.each`
- message | expectedMessage
- ${TEST_MESSAGE} | ${`${TEST_MESSAGE}<br/><br/>Would you like to create a new branch?`}
- ${TEST_SPECIAL} | ${`${TEST_SPECIAL_ESCAPED}<br/><br/>Would you like to create a new branch?`}
- `('uses given message="$message"', ({ message, expectedMessage }) => {
- expect(createBranchChangedCommitError(message)).toEqual({
- title: 'Branch changed',
- messageHTML: expectedMessage,
- canCreateBranch: true,
- });
+ it.each`
+ fn | title | message | messageHTML
+ ${createCodeownersCommitError} | ${'CODEOWNERS rule violation'} | ${TEST_MESSAGE} | ${TEST_MESSAGE}
+ ${createCodeownersCommitError} | ${'CODEOWNERS rule violation'} | ${TEST_SPECIAL} | ${TEST_SPECIAL_ESCAPED}
+ ${branchAlreadyExistsCommitError} | ${'Branch already exists'} | ${TEST_MESSAGE} | ${`${TEST_MESSAGE}${AUTOGENERATE_SUFFIX}`}
+ ${branchAlreadyExistsCommitError} | ${'Branch already exists'} | ${TEST_SPECIAL} | ${`${TEST_SPECIAL_ESCAPED}${AUTOGENERATE_SUFFIX}`}
+ ${createBranchChangedCommitError} | ${'Branch changed'} | ${TEST_MESSAGE} | ${`${TEST_MESSAGE}${NEW_BRANCH_SUFFIX}`}
+ ${createBranchChangedCommitError} | ${'Branch changed'} | ${TEST_SPECIAL} | ${`${TEST_SPECIAL_ESCAPED}${NEW_BRANCH_SUFFIX}`}
+ `('$fn escapes and uses given message="$message"', ({ fn, title, message, messageHTML }) => {
+ expect(fn(message)).toEqual({
+ title,
+ messageHTML,
+ primaryAction: { text: 'Create new branch', callback: expect.any(Function) },
});
});
@@ -60,7 +48,7 @@ describe('~/ide/lib/errors', () => {
${{}} | ${createUnexpectedCommitError()}
${{ response: {} }} | ${createUnexpectedCommitError()}
${{ response: { data: {} } }} | ${createUnexpectedCommitError()}
- ${createResponseError('test')} | ${createUnexpectedCommitError()}
+ ${createResponseError(TEST_MESSAGE)} | ${createUnexpectedCommitError(TEST_MESSAGE)}
${createResponseError(CODEOWNERS_MESSAGE)} | ${createCodeownersCommitError(CODEOWNERS_MESSAGE)}
${createResponseError(CHANGED_MESSAGE)} | ${createBranchChangedCommitError(CHANGED_MESSAGE)}
`('parses message into error object with "$message"', ({ message, expectation }) => {
diff --git a/spec/frontend/ide/stores/getters_spec.js b/spec/frontend/ide/stores/getters_spec.js
index e24f08fa802..5ae87f5f9cd 100644
--- a/spec/frontend/ide/stores/getters_spec.js
+++ b/spec/frontend/ide/stores/getters_spec.js
@@ -449,16 +449,16 @@ describe('IDE store getters', () => {
describe('getAvailableFileName', () => {
it.each`
path | newPath
- ${'foo'} | ${'foo_1'}
+ ${'foo'} | ${'foo-1'}
${'foo__93.png'} | ${'foo__94.png'}
- ${'foo/bar.png'} | ${'foo/bar_1.png'}
+ ${'foo/bar.png'} | ${'foo/bar-1.png'}
${'foo/bar--34.png'} | ${'foo/bar--35.png'}
${'foo/bar 2.png'} | ${'foo/bar 3.png'}
${'foo/bar-621.png'} | ${'foo/bar-622.png'}
- ${'jquery.min.js'} | ${'jquery_1.min.js'}
+ ${'jquery.min.js'} | ${'jquery-1.min.js'}
${'my_spec_22.js.snap'} | ${'my_spec_23.js.snap'}
- ${'subtitles5.mp4.srt'} | ${'subtitles_6.mp4.srt'}
- ${'sample_file.mp3'} | ${'sample_file_1.mp3'}
+ ${'subtitles5.mp4.srt'} | ${'subtitles-6.mp4.srt'}
+ ${'sample-file.mp3'} | ${'sample-file-1.mp3'}
${'Screenshot 2020-05-26 at 10.53.08 PM.png'} | ${'Screenshot 2020-05-26 at 11.53.08 PM.png'}
`('suffixes the path with a number if the path already exists', ({ path, newPath }) => {
localState.entries[path] = file();
diff --git a/spec/frontend/ide/stores/modules/commit/actions_spec.js b/spec/frontend/ide/stores/modules/commit/actions_spec.js
index babc50e54f1..cfe2bddf76c 100644
--- a/spec/frontend/ide/stores/modules/commit/actions_spec.js
+++ b/spec/frontend/ide/stores/modules/commit/actions_spec.js
@@ -76,59 +76,38 @@ describe('IDE commit module actions', () => {
.then(done)
.catch(done.fail);
});
+ });
- it('sets shouldCreateMR to true if "Create new MR" option is visible', done => {
- Object.assign(store.state, {
- shouldHideNewMrOption: false,
- });
+ describe('updateBranchName', () => {
+ let originalGon;
- testAction(
- actions.updateCommitAction,
- {},
- store.state,
- [
- {
- type: mutationTypes.UPDATE_COMMIT_ACTION,
- payload: { commitAction: expect.anything() },
- },
- { type: mutationTypes.TOGGLE_SHOULD_CREATE_MR, payload: true },
- ],
- [],
- done,
- );
+ beforeEach(() => {
+ originalGon = window.gon;
+ window.gon = { current_username: 'johndoe' };
+
+ store.state.currentBranchId = 'master';
});
- it('sets shouldCreateMR to false if "Create new MR" option is hidden', done => {
- Object.assign(store.state, {
- shouldHideNewMrOption: true,
- });
+ afterEach(() => {
+ window.gon = originalGon;
+ });
- testAction(
- actions.updateCommitAction,
- {},
- store.state,
- [
- {
- type: mutationTypes.UPDATE_COMMIT_ACTION,
- payload: { commitAction: expect.anything() },
- },
- { type: mutationTypes.TOGGLE_SHOULD_CREATE_MR, payload: false },
- ],
- [],
- done,
- );
+ it('updates store with new branch name', async () => {
+ await store.dispatch('commit/updateBranchName', 'branch-name');
+
+ expect(store.state.commit.newBranchName).toBe('branch-name');
});
});
- describe('updateBranchName', () => {
- it('updates store with new branch name', done => {
- store
- .dispatch('commit/updateBranchName', 'branch-name')
- .then(() => {
- expect(store.state.commit.newBranchName).toBe('branch-name');
- })
- .then(done)
- .catch(done.fail);
+ describe('addSuffixToBranchName', () => {
+ it('adds suffix to branchName', async () => {
+ jest.spyOn(Math, 'random').mockReturnValue(0.391352525);
+
+ store.state.commit.newBranchName = 'branch-name';
+
+ await store.dispatch('commit/addSuffixToBranchName');
+
+ expect(store.state.commit.newBranchName).toBe('branch-name-39135');
});
});
@@ -318,13 +297,16 @@ describe('IDE commit module actions', () => {
currentBranchId: 'master',
projects: {
abcproject: {
+ default_branch: 'master',
web_url: 'webUrl',
branches: {
master: {
+ name: 'master',
workingReference: '1',
commit: {
id: TEST_COMMIT_SHA,
},
+ can_push: true,
},
},
userPermissions: {
@@ -499,6 +481,16 @@ describe('IDE commit module actions', () => {
.catch(done.fail);
});
+ it('does not redirect to merge request page if shouldCreateMR is checked, but branch is the default branch', async () => {
+ jest.spyOn(eventHub, '$on').mockImplementation();
+
+ store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ store.state.commit.shouldCreateMR = true;
+
+ await store.dispatch('commit/commitChanges');
+ expect(visitUrl).not.toHaveBeenCalled();
+ });
+
it('resets changed files before redirecting', () => {
jest.spyOn(eventHub, '$on').mockImplementation();
diff --git a/spec/frontend/ide/utils_spec.js b/spec/frontend/ide/utils_spec.js
index 97dc8217ecc..6cd2128d356 100644
--- a/spec/frontend/ide/utils_spec.js
+++ b/spec/frontend/ide/utils_spec.js
@@ -9,6 +9,7 @@ import {
getPathParents,
getPathParent,
readFileAsDataURL,
+ addNumericSuffix,
} from '~/ide/utils';
describe('WebIDE utils', () => {
@@ -291,4 +292,43 @@ describe('WebIDE utils', () => {
});
});
});
+
+ /*
+ * hello-2425 -> hello-2425
+ * hello.md -> hello-1.md
+ * hello_2.md -> hello_3.md
+ * hello_ -> hello_1
+ * master-patch-22432 -> master-patch-22433
+ * patch_332 -> patch_333
+ */
+
+ describe('addNumericSuffix', () => {
+ it.each`
+ input | output
+ ${'hello'} | ${'hello-1'}
+ ${'hello2'} | ${'hello-3'}
+ ${'hello.md'} | ${'hello-1.md'}
+ ${'hello_2.md'} | ${'hello_3.md'}
+ ${'hello_'} | ${'hello_1'}
+ ${'master-patch-22432'} | ${'master-patch-22433'}
+ ${'patch_332'} | ${'patch_333'}
+ `('adds a numeric suffix to a given filename/branch name: $input', ({ input, output }) => {
+ expect(addNumericSuffix(input)).toBe(output);
+ });
+
+ it.each`
+ input | output
+ ${'hello'} | ${'hello-39135'}
+ ${'hello2'} | ${'hello-39135'}
+ ${'hello.md'} | ${'hello-39135.md'}
+ ${'hello_2.md'} | ${'hello_39135.md'}
+ ${'hello_'} | ${'hello_39135'}
+ ${'master-patch-22432'} | ${'master-patch-39135'}
+ ${'patch_332'} | ${'patch_39135'}
+ `('adds a random suffix if randomize=true is passed for name: $input', ({ input, output }) => {
+ jest.spyOn(Math, 'random').mockReturnValue(0.391352525);
+
+ expect(addNumericSuffix(input, true)).toBe(output);
+ });
+ });
});
diff --git a/spec/frontend/incidents/components/incidents_list_spec.js b/spec/frontend/incidents/components/incidents_list_spec.js
index c4dfaabe36d..935e4ec8b8e 100644
--- a/spec/frontend/incidents/components/incidents_list_spec.js
+++ b/spec/frontend/incidents/components/incidents_list_spec.js
@@ -10,6 +10,7 @@ import {
GlBadge,
GlEmptyState,
} from '@gitlab/ui';
+import Tracking from '~/tracking';
import { visitUrl, joinPaths, mergeUrlParams } from '~/lib/utils/url_utility';
import IncidentsList from '~/incidents/components/incidents_list.vue';
import SeverityToken from '~/sidebar/components/severity/severity.vue';
@@ -22,6 +23,7 @@ import {
TH_CREATED_AT_TEST_ID,
TH_SEVERITY_TEST_ID,
TH_PUBLISHED_TEST_ID,
+ trackIncidentCreateNewOptions,
} from '~/incidents/constants';
import mockIncidents from '../mocks/incidents.json';
import mockFilters from '../mocks/incidents_filter.json';
@@ -33,6 +35,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
setUrlParams: jest.fn(),
updateHistory: jest.fn(),
}));
+jest.mock('~/tracking');
describe('Incidents List', () => {
let wrapper;
@@ -52,7 +55,7 @@ describe('Incidents List', () => {
const findLoader = () => wrapper.find(GlLoadingIcon);
const findTimeAgo = () => wrapper.findAll(TimeAgoTooltip);
const findSearch = () => wrapper.find(FilteredSearchBar);
- const findAssingees = () => wrapper.findAll('[data-testid="incident-assignees"]');
+ const findAssignees = () => wrapper.findAll('[data-testid="incident-assignees"]');
const findCreateIncidentBtn = () => wrapper.find('[data-testid="createIncidentBtn"]');
const findClosedIcon = () => wrapper.findAll("[data-testid='incident-closed']");
const findPagination = () => wrapper.find(GlPagination);
@@ -164,14 +167,14 @@ describe('Incidents List', () => {
describe('Assignees', () => {
it('shows Unassigned when there are no assignees', () => {
expect(
- findAssingees()
+ findAssignees()
.at(0)
.text(),
).toBe(I18N.unassigned);
});
it('renders an avatar component when there is an assignee', () => {
- const avatar = findAssingees()
+ const avatar = findAssignees()
.at(1)
.find(GlAvatar);
const { src, label } = avatar.attributes();
@@ -211,7 +214,7 @@ describe('Incidents List', () => {
});
});
- it('shows the button linking to new incidents page with prefilled incident template when clicked', () => {
+ it('shows the button linking to new incidents page with pre-filled incident template when clicked', () => {
expect(findCreateIncidentBtn().exists()).toBe(true);
findCreateIncidentBtn().trigger('click');
expect(mergeUrlParams).toHaveBeenCalledWith(
@@ -233,6 +236,13 @@ describe('Incidents List', () => {
});
expect(findCreateIncidentBtn().exists()).toBe(false);
});
+
+ it('should track alert list page views', async () => {
+ findCreateIncidentBtn().vm.$emit('click');
+ await wrapper.vm.$nextTick();
+ const { category, action } = trackIncidentCreateNewOptions;
+ expect(Tracking.event).toHaveBeenCalledWith(category, action);
+ });
});
describe('Pagination', () => {
diff --git a/spec/frontend/issuable_show/components/issuable_description_spec.js b/spec/frontend/issuable_show/components/issuable_description_spec.js
new file mode 100644
index 00000000000..1dd8348b098
--- /dev/null
+++ b/spec/frontend/issuable_show/components/issuable_description_spec.js
@@ -0,0 +1,41 @@
+import $ from 'jquery';
+import { shallowMount } from '@vue/test-utils';
+
+import IssuableDescription from '~/issuable_show/components/issuable_description.vue';
+
+import { mockIssuable } from '../mock_data';
+
+const createComponent = (issuable = mockIssuable) =>
+ shallowMount(IssuableDescription, {
+ propsData: { issuable },
+ });
+
+describe('IssuableDescription', () => {
+ let renderGFMSpy;
+ let wrapper;
+
+ beforeEach(() => {
+ renderGFMSpy = jest.spyOn($.fn, 'renderGFM');
+ wrapper = createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('mounted', () => {
+ it('calls `renderGFM`', () => {
+ expect(renderGFMSpy).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('methods', () => {
+ describe('renderGFM', () => {
+ it('calls `renderGFM` on container element', () => {
+ wrapper.vm.renderGFM();
+
+ expect(renderGFMSpy).toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/issuable_show/components/issuable_edit_form_spec.js b/spec/frontend/issuable_show/components/issuable_edit_form_spec.js
new file mode 100644
index 00000000000..352e66cdffe
--- /dev/null
+++ b/spec/frontend/issuable_show/components/issuable_edit_form_spec.js
@@ -0,0 +1,122 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlFormInput } from '@gitlab/ui';
+import MarkdownField from '~/vue_shared/components/markdown/field.vue';
+
+import IssuableEditForm from '~/issuable_show/components/issuable_edit_form.vue';
+import IssuableEventHub from '~/issuable_show/event_hub';
+
+import { mockIssuableShowProps, mockIssuable } from '../mock_data';
+
+const issuableEditFormProps = {
+ issuable: mockIssuable,
+ ...mockIssuableShowProps,
+};
+
+const createComponent = ({ propsData = issuableEditFormProps } = {}) =>
+ shallowMount(IssuableEditForm, {
+ propsData,
+ stubs: {
+ MarkdownField,
+ },
+ slots: {
+ 'edit-form-actions': `
+ <button class="js-save">Save changes</button>
+ <button class="js-cancel">Cancel</button>
+ `,
+ },
+ });
+
+describe('IssuableEditForm', () => {
+ let wrapper;
+ const assertEvent = eventSpy => {
+ expect(eventSpy).toHaveBeenNthCalledWith(1, 'update.issuable', expect.any(Function));
+ expect(eventSpy).toHaveBeenNthCalledWith(2, 'close.form', expect.any(Function));
+ };
+
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('created', () => {
+ it('binds `update.issuable` and `close.form` event listeners', () => {
+ const eventOnSpy = jest.spyOn(IssuableEventHub, '$on');
+ const wrapperTemp = createComponent();
+
+ assertEvent(eventOnSpy);
+
+ wrapperTemp.destroy();
+ });
+ });
+
+ describe('beforeDestroy', () => {
+ it('unbinds `update.issuable` and `close.form` event listeners', () => {
+ const wrapperTemp = createComponent();
+ const eventOffSpy = jest.spyOn(IssuableEventHub, '$off');
+
+ wrapperTemp.destroy();
+
+ assertEvent(eventOffSpy);
+ });
+ });
+
+ describe('methods', () => {
+ describe('initAutosave', () => {
+ it('initializes `autosaveTitle` and `autosaveDescription` props', () => {
+ expect(wrapper.vm.autosaveTitle).toBeDefined();
+ expect(wrapper.vm.autosaveDescription).toBeDefined();
+ });
+ });
+
+ describe('resetAutosave', () => {
+ it('calls `reset` on `autosaveTitle` and `autosaveDescription` props', () => {
+ jest.spyOn(wrapper.vm.autosaveTitle, 'reset').mockImplementation(jest.fn);
+ jest.spyOn(wrapper.vm.autosaveDescription, 'reset').mockImplementation(jest.fn);
+
+ wrapper.vm.resetAutosave();
+
+ expect(wrapper.vm.autosaveTitle.reset).toHaveBeenCalled();
+ expect(wrapper.vm.autosaveDescription.reset).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders title input field', () => {
+ const titleInputEl = wrapper.find('[data-testid="title"]');
+
+ expect(titleInputEl.exists()).toBe(true);
+ expect(titleInputEl.find(GlFormInput).attributes()).toMatchObject({
+ 'aria-label': 'Title',
+ placeholder: 'Title',
+ });
+ });
+
+ it('renders description textarea field', () => {
+ const descriptionEl = wrapper.find('[data-testid="description"]');
+
+ expect(descriptionEl.exists()).toBe(true);
+ expect(descriptionEl.find(MarkdownField).props()).toMatchObject({
+ markdownPreviewPath: issuableEditFormProps.descriptionPreviewPath,
+ markdownDocsPath: issuableEditFormProps.descriptionHelpPath,
+ enableAutocomplete: issuableEditFormProps.enableAutocomplete,
+ textareaValue: mockIssuable.description,
+ });
+ expect(descriptionEl.find('textarea').attributes()).toMatchObject({
+ 'data-supports-quick-actions': 'true',
+ 'aria-label': 'Description',
+ placeholder: 'Write a comment or drag your files hereā€¦',
+ });
+ });
+
+ it('renders form actions', () => {
+ const actionsEl = wrapper.find('[data-testid="actions"]');
+
+ expect(actionsEl.find('button.js-save').exists()).toBe(true);
+ expect(actionsEl.find('button.js-cancel').exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/issuable_show/components/issuable_title_spec.js b/spec/frontend/issuable_show/components/issuable_title_spec.js
new file mode 100644
index 00000000000..e8621c763b3
--- /dev/null
+++ b/spec/frontend/issuable_show/components/issuable_title_spec.js
@@ -0,0 +1,100 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlIcon, GlButton, GlIntersectionObserver } from '@gitlab/ui';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+
+import IssuableTitle from '~/issuable_show/components/issuable_title.vue';
+
+import { mockIssuableShowProps, mockIssuable } from '../mock_data';
+
+const issuableTitleProps = {
+ issuable: mockIssuable,
+ ...mockIssuableShowProps,
+};
+
+const createComponent = (propsData = issuableTitleProps) =>
+ shallowMount(IssuableTitle, {
+ propsData,
+ stubs: {
+ transition: true,
+ },
+ slots: {
+ 'status-badge': 'Open',
+ },
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ });
+
+describe('IssuableTitle', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('methods', () => {
+ describe('handleTitleAppear', () => {
+ it('sets value of `stickyTitleVisible` prop to false', () => {
+ wrapper.find(GlIntersectionObserver).vm.$emit('appear');
+
+ expect(wrapper.vm.stickyTitleVisible).toBe(false);
+ });
+ });
+
+ describe('handleTitleDisappear', () => {
+ it('sets value of `stickyTitleVisible` prop to true', () => {
+ wrapper.find(GlIntersectionObserver).vm.$emit('disappear');
+
+ expect(wrapper.vm.stickyTitleVisible).toBe(true);
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders issuable title', async () => {
+ const wrapperWithTitle = createComponent({
+ ...mockIssuableShowProps,
+ issuable: {
+ ...mockIssuable,
+ titleHtml: '<b>Sample</b> title',
+ },
+ });
+
+ await wrapperWithTitle.vm.$nextTick();
+ const titleEl = wrapperWithTitle.find('h2');
+
+ expect(titleEl.exists()).toBe(true);
+ expect(titleEl.html()).toBe('<h2 dir="auto" class="title qa-title"><b>Sample</b> title</h2>');
+
+ wrapperWithTitle.destroy();
+ });
+
+ it('renders edit button', () => {
+ const editButtonEl = wrapper.find(GlButton);
+ const tooltip = getBinding(editButtonEl.element, 'gl-tooltip');
+
+ expect(editButtonEl.exists()).toBe(true);
+ expect(editButtonEl.props('icon')).toBe('pencil');
+ expect(editButtonEl.attributes('title')).toBe('Edit title and description');
+ expect(tooltip).toBeDefined();
+ });
+
+ it('renders sticky header when `stickyTitleVisible` prop is true', async () => {
+ wrapper.setData({
+ stickyTitleVisible: true,
+ });
+
+ await wrapper.vm.$nextTick();
+ const stickyHeaderEl = wrapper.find('[data-testid="header"]');
+
+ expect(stickyHeaderEl.exists()).toBe(true);
+ expect(stickyHeaderEl.find(GlIcon).props('name')).toBe(issuableTitleProps.statusIcon);
+ expect(stickyHeaderEl.text()).toContain('Open');
+ expect(stickyHeaderEl.text()).toContain(issuableTitleProps.issuable.title);
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/router_spec.js b/spec/frontend/monitoring/router_spec.js
index 8b97c8ed125..2bf2065b178 100644
--- a/spec/frontend/monitoring/router_spec.js
+++ b/spec/frontend/monitoring/router_spec.js
@@ -105,8 +105,7 @@ describe('Monitoring router', () => {
path | currentDashboard
${'/panel/new'} | ${undefined}
${'/dashboard.yml/panel/new'} | ${'dashboard.yml'}
- ${'/config/prometheus/common_metrics.yml/panel/new'} | ${'config/prometheus/common_metrics.yml'}
- ${'/config%2Fprometheus%2Fcommon_metrics.yml/panel/new'} | ${'config/prometheus/common_metrics.yml'}
+ ${'/config%2Fprometheus%2Fcommon_metrics.yml/panel/new'} | ${'config%2Fprometheus%2Fcommon_metrics.yml'}
`('"$path" renders page with dashboard "$currentDashboard"', ({ path, currentDashboard }) => {
const wrapper = createWrapper(BASE_PATH, path);
diff --git a/spec/frontend/packages/details/components/composer_installation_spec.js b/spec/frontend/packages/details/components/composer_installation_spec.js
index c13981fbb87..f5dab1b3b7c 100644
--- a/spec/frontend/packages/details/components/composer_installation_spec.js
+++ b/spec/frontend/packages/details/components/composer_installation_spec.js
@@ -15,15 +15,18 @@ describe('ComposerInstallation', () => {
const composerRegistryIncludeStr = 'foo/registry';
const composerPackageIncludeStr = 'foo/package';
+ const groupExists = true;
const store = new Vuex.Store({
state: {
packageEntity,
composerHelpPath,
+ groupExists,
},
getters: {
composerRegistryInclude: () => composerRegistryIncludeStr,
composerPackageInclude: () => composerPackageIncludeStr,
+ groupExists: () => groupExists,
},
});
@@ -62,7 +65,7 @@ describe('ComposerInstallation', () => {
});
it('has the correct title', () => {
- expect(findRegistryInclude().props('label')).toBe('composer.json registry include');
+ expect(findRegistryInclude().props('label')).toBe('Add composer registry');
});
});
@@ -78,7 +81,7 @@ describe('ComposerInstallation', () => {
});
it('has the correct title', () => {
- expect(findPackageInclude().props('label')).toBe('composer.json require package include');
+ expect(findPackageInclude().props('label')).toBe('Install package version');
});
it('has the correct help text', () => {
diff --git a/spec/frontend/packages/details/store/getters_spec.js b/spec/frontend/packages/details/store/getters_spec.js
index 378d259ad3f..b8c2138e7f5 100644
--- a/spec/frontend/packages/details/store/getters_spec.js
+++ b/spec/frontend/packages/details/store/getters_spec.js
@@ -15,6 +15,7 @@ import {
pypiSetupCommand,
composerRegistryInclude,
composerPackageInclude,
+ groupExists,
} from '~/packages/details/store/getters';
import {
conanPackage,
@@ -68,10 +69,11 @@ describe('Getters PackageDetails Store', () => {
const nugetSetupCommandStr = `nuget source Add -Name "GitLab" -Source "${registryUrl}" -UserName <your_username> -Password <your_token>`;
const pypiPipCommandStr = `pip install ${pypiPackage.name} --extra-index-url ${registryUrl}`;
- const composerRegistryIncludeStr = '{"type":"composer","url":"foo"}';
- const composerPackageIncludeStr = JSON.stringify({
- [packageWithoutBuildInfo.name]: packageWithoutBuildInfo.version,
- });
+ const composerRegistryIncludeStr =
+ 'composer config repositories.gitlab.com/123 \'{"type": "composer", "url": "foo"}\'';
+ const composerPackageIncludeStr = `composer req ${[packageWithoutBuildInfo.name]}:${
+ packageWithoutBuildInfo.version
+ }`;
describe('packagePipeline', () => {
it('should return the pipeline info when pipeline exists', () => {
@@ -221,7 +223,7 @@ describe('Getters PackageDetails Store', () => {
describe('composer string getters', () => {
it('gets the correct composerRegistryInclude command', () => {
- setupState({ composerPath: 'foo' });
+ setupState({ composerPath: 'foo', composerConfigRepositoryName: 'gitlab.com/123' });
expect(composerRegistryInclude(state)).toBe(composerRegistryIncludeStr);
});
@@ -232,4 +234,18 @@ describe('Getters PackageDetails Store', () => {
expect(composerPackageInclude(state)).toBe(composerPackageIncludeStr);
});
});
+
+ describe('check if group', () => {
+ it('is set', () => {
+ setupState({ groupListUrl: '/groups/composer/-/packages' });
+
+ expect(groupExists(state)).toBe(true);
+ });
+
+ it('is not set', () => {
+ setupState({ groupListUrl: '' });
+
+ expect(groupExists(state)).toBe(false);
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/__snapshots__/split_button_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/split_button_spec.js.snap
index fcb9c4b8b02..8eb0e8f9550 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/split_button_spec.js.snap
+++ b/spec/frontend/vue_shared/components/__snapshots__/split_button_spec.js.snap
@@ -1,15 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SplitButton renders actionItems 1`] = `
-<gl-deprecated-dropdown-stub
- menu-class="dropdown-menu-selectable "
+<gl-dropdown-stub
+ category="tertiary"
+ headertext=""
+ menu-class=""
+ size="medium"
split="true"
text="professor"
- variant="secondary"
+ variant="default"
>
- <gl-deprecated-dropdown-item-stub
- active="true"
- active-class="is-active"
+ <gl-dropdown-item-stub
+ avatarurl=""
+ iconcolor=""
+ iconname=""
+ iconrightname=""
+ ischecked="true"
+ ischeckitem="true"
+ secondarytext=""
>
<strong>
professor
@@ -18,11 +26,16 @@ exports[`SplitButton renders actionItems 1`] = `
<div>
very symphonic
</div>
- </gl-deprecated-dropdown-item-stub>
+ </gl-dropdown-item-stub>
- <gl-deprecated-dropdown-divider-stub />
- <gl-deprecated-dropdown-item-stub
- active-class="is-active"
+ <gl-dropdown-divider-stub />
+ <gl-dropdown-item-stub
+ avatarurl=""
+ iconcolor=""
+ iconname=""
+ iconrightname=""
+ ischeckitem="true"
+ secondarytext=""
>
<strong>
captain
@@ -31,8 +44,8 @@ exports[`SplitButton renders actionItems 1`] = `
<div>
warp drive
</div>
- </gl-deprecated-dropdown-item-stub>
+ </gl-dropdown-item-stub>
<!---->
-</gl-deprecated-dropdown-stub>
+</gl-dropdown-stub>
`;
diff --git a/spec/frontend/vue_shared/components/split_button_spec.js b/spec/frontend/vue_shared/components/split_button_spec.js
index f3bd4c14717..e09bc073042 100644
--- a/spec/frontend/vue_shared/components/split_button_spec.js
+++ b/spec/frontend/vue_shared/components/split_button_spec.js
@@ -1,4 +1,4 @@
-import { GlDeprecatedDropdown, GlDeprecatedDropdownItem } from '@gitlab/ui';
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import SplitButton from '~/vue_shared/components/split_button.vue';
@@ -25,10 +25,10 @@ describe('SplitButton', () => {
});
};
- const findDropdown = () => wrapper.find(GlDeprecatedDropdown);
+ const findDropdown = () => wrapper.find(GlDropdown);
const findDropdownItem = (index = 0) =>
findDropdown()
- .findAll(GlDeprecatedDropdownItem)
+ .findAll(GlDropdownItem)
.at(index);
const selectItem = index => {
findDropdownItem(index).vm.$emit('click');