diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-13 12:08:41 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-13 12:08:41 +0000 |
commit | 6e91fbf77476011a7fd86ca3467aad6d7b110ff3 (patch) | |
tree | cace6db4e7ebef8b15a6a7fc8fbe8ff0d89bea90 /spec/frontend | |
parent | 15ae4a8da83661f2b714d804721001a53b354d28 (diff) | |
download | gitlab-ce-6e91fbf77476011a7fd86ca3467aad6d7b110ff3.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
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'); |