diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-24 21:09:14 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-24 21:09:14 +0300 |
commit | ca386bfc0cf083e0ccb477995378061fc2a15b66 (patch) | |
tree | f53d4a2f288ba64e9f440080817f14d62965398c /spec/frontend/admin | |
parent | 61ebd5753018a1f4b6032122f6ea625dc4e4fc8e (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/admin')
5 files changed, 240 insertions, 218 deletions
diff --git a/spec/frontend/admin/abuse_report/components/abuse_report_app_spec.js b/spec/frontend/admin/abuse_report/components/abuse_report_app_spec.js index cabbb5e1591..e519684bbc5 100644 --- a/spec/frontend/admin/abuse_report/components/abuse_report_app_spec.js +++ b/spec/frontend/admin/abuse_report/components/abuse_report_app_spec.js @@ -1,14 +1,17 @@ import { shallowMount } from '@vue/test-utils'; +import { GlAlert } from '@gitlab/ui'; import AbuseReportApp from '~/admin/abuse_report/components/abuse_report_app.vue'; import ReportHeader from '~/admin/abuse_report/components/report_header.vue'; import UserDetails from '~/admin/abuse_report/components/user_details.vue'; import ReportedContent from '~/admin/abuse_report/components/reported_content.vue'; import HistoryItems from '~/admin/abuse_report/components/history_items.vue'; +import { SUCCESS_ALERT } from '~/admin/abuse_report/constants'; import { mockAbuseReport } from '../mock_data'; describe('AbuseReportApp', () => { let wrapper; + const findAlert = () => wrapper.findComponent(GlAlert); const findReportHeader = () => wrapper.findComponent(ReportHeader); const findUserDetails = () => wrapper.findComponent(UserDetails); const findReportedContent = () => wrapper.findComponent(ReportedContent); @@ -27,10 +30,44 @@ describe('AbuseReportApp', () => { createComponent(); }); + it('does not show the alert by default', () => { + expect(findAlert().exists()).toBe(false); + }); + + describe('when emitting the showAlert event from the report header', () => { + const message = 'alert message'; + + beforeEach(() => { + findReportHeader().vm.$emit('showAlert', SUCCESS_ALERT, message); + }); + + it('shows the alert', () => { + expect(findAlert().exists()).toBe(true); + }); + + it('displays the message', () => { + expect(findAlert().text()).toBe(message); + }); + + it('sets the variant property', () => { + expect(findAlert().props('variant')).toBe(SUCCESS_ALERT); + }); + + describe('when dismissing the alert', () => { + beforeEach(() => { + findAlert().vm.$emit('dismiss'); + }); + + it('hides the alert', () => { + expect(findAlert().exists()).toBe(false); + }); + }); + }); + describe('ReportHeader', () => { it('renders ReportHeader', () => { expect(findReportHeader().props('user')).toBe(mockAbuseReport.user); - expect(findReportHeader().props('actions')).toBe(mockAbuseReport.actions); + expect(findReportHeader().props('report')).toBe(mockAbuseReport.report); }); describe('when no user is present', () => { diff --git a/spec/frontend/admin/abuse_report/components/report_actions_spec.js b/spec/frontend/admin/abuse_report/components/report_actions_spec.js new file mode 100644 index 00000000000..a1a78902b58 --- /dev/null +++ b/spec/frontend/admin/abuse_report/components/report_actions_spec.js @@ -0,0 +1,158 @@ +import MockAdapter from 'axios-mock-adapter'; +import { GlDrawer } from '@gitlab/ui'; +import { nextTick } from 'vue'; +import axios from '~/lib/utils/axios_utils'; +import { + HTTP_STATUS_OK, + HTTP_STATUS_UNPROCESSABLE_ENTITY, + HTTP_STATUS_INTERNAL_SERVER_ERROR, +} from '~/lib/utils/http_status'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import ReportActions from '~/admin/abuse_report/components/report_actions.vue'; +import { + ACTIONS_I18N, + SUCCESS_ALERT, + FAILED_ALERT, + ERROR_MESSAGE, +} from '~/admin/abuse_report/constants'; +import { mockAbuseReport } from '../mock_data'; + +describe('ReportActions', () => { + let wrapper; + let axiosMock; + + const params = { + user_action: 'ban_user', + close: true, + comment: 'my comment', + reason: 'spam', + }; + + const { report } = mockAbuseReport; + + const clickActionsButton = () => wrapper.findByTestId('actions-button').vm.$emit('click'); + const isDrawerOpen = () => wrapper.findComponent(GlDrawer).props('open'); + const findErrorFor = (id) => wrapper.findByTestId(id).find('.d-block.invalid-feedback'); + const setCloseReport = (close) => wrapper.findByTestId('close').find('input').setChecked(close); + const setSelectOption = (id, value) => + wrapper.findByTestId(`${id}-select`).find(`option[value=${value}]`).setSelected(); + const selectAction = (action) => setSelectOption('action', action); + const selectReason = (reason) => setSelectOption('reason', reason); + const setComment = (comment) => wrapper.findByTestId('comment').find('input').setValue(comment); + const submitForm = () => wrapper.findByTestId('submit-button').vm.$emit('click'); + + const createComponent = (props = {}) => { + wrapper = mountExtended(ReportActions, { + propsData: { + report, + ...props, + }, + }); + }; + + beforeEach(() => { + axiosMock = new MockAdapter(axios); + createComponent(); + }); + + afterEach(() => { + axiosMock.restore(); + }); + + it('initially hides the drawer', () => { + expect(isDrawerOpen()).toBe(false); + }); + + describe('when clicking the actions button', () => { + beforeEach(() => { + clickActionsButton(); + }); + + it('shows the drawer', () => { + expect(isDrawerOpen()).toBe(true); + }); + + describe.each` + input | errorFor | messageShown + ${null} | ${'action'} | ${true} + ${null} | ${'reason'} | ${true} + ${'close'} | ${'action'} | ${false} + ${'action'} | ${'action'} | ${false} + ${'reason'} | ${'reason'} | ${false} + `('when submitting an invalid form', ({ input, errorFor, messageShown }) => { + describe(`when ${ + input ? `providing a value for the ${input} field` : 'not providing any values' + }`, () => { + beforeEach(() => { + submitForm(); + + if (input === 'close') { + setCloseReport(params.close); + } else if (input === 'action') { + selectAction(params.user_action); + } else if (input === 'reason') { + selectReason(params.reason); + } + }); + + it(`${messageShown ? 'shows' : 'hides'} ${errorFor} error message`, () => { + if (messageShown) { + expect(findErrorFor(errorFor).text()).toBe(ACTIONS_I18N.requiredFieldFeedback); + } else { + expect(findErrorFor(errorFor).exists()).toBe(false); + } + }); + }); + }); + + describe('when submitting a valid form', () => { + describe.each` + response | success | responseStatus | responseData | alertType | alertMessage + ${'successful'} | ${true} | ${HTTP_STATUS_OK} | ${{ message: 'success!' }} | ${SUCCESS_ALERT} | ${'success!'} + ${'custom failure'} | ${false} | ${HTTP_STATUS_UNPROCESSABLE_ENTITY} | ${{ message: 'fail!' }} | ${FAILED_ALERT} | ${'fail!'} + ${'generic failure'} | ${false} | ${HTTP_STATUS_INTERNAL_SERVER_ERROR} | ${{}} | ${FAILED_ALERT} | ${ERROR_MESSAGE} + `( + 'when the server responds with a $response response', + ({ success, responseStatus, responseData, alertType, alertMessage }) => { + beforeEach(async () => { + jest.spyOn(axios, 'put'); + + axiosMock.onPut(report.updatePath).replyOnce(responseStatus, responseData); + + selectAction(params.user_action); + setCloseReport(params.close); + selectReason(params.reason); + setComment(params.comment); + + await nextTick(); + + submitForm(); + + await waitForPromises(); + }); + + it('does a put call with the right data', () => { + expect(axios.put).toHaveBeenCalledWith(report.updatePath, params); + }); + + it('closes the drawer', () => { + expect(isDrawerOpen()).toBe(false); + }); + + it('emits the showAlert event', () => { + expect(wrapper.emitted('showAlert')).toStrictEqual([[alertType, alertMessage]]); + }); + + it(`${success ? 'does' : 'does not'} emit the closeReport event`, () => { + if (success) { + expect(wrapper.emitted('closeReport')).toBeDefined(); + } else { + expect(wrapper.emitted('closeReport')).toBeUndefined(); + } + }); + }, + ); + }); + }); +}); diff --git a/spec/frontend/admin/abuse_report/components/report_header_spec.js b/spec/frontend/admin/abuse_report/components/report_header_spec.js index d584cab05b3..f22f3af091f 100644 --- a/spec/frontend/admin/abuse_report/components/report_header_spec.js +++ b/spec/frontend/admin/abuse_report/components/report_header_spec.js @@ -1,25 +1,27 @@ -import { GlAvatar, GlLink, GlButton } from '@gitlab/ui'; +import { GlBadge, GlIcon, GlAvatar, GlLink, GlButton } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import ReportHeader from '~/admin/abuse_report/components/report_header.vue'; -import AbuseReportActions from '~/admin/abuse_reports/components/abuse_report_actions.vue'; -import { REPORT_HEADER_I18N } from '~/admin/abuse_report/constants'; +import ReportActions from '~/admin/abuse_report/components/report_actions.vue'; +import { REPORT_HEADER_I18N, STATUS_OPEN, STATUS_CLOSED } from '~/admin/abuse_report/constants'; import { mockAbuseReport } from '../mock_data'; describe('ReportHeader', () => { let wrapper; - const { user, actions } = mockAbuseReport; + const { user, report } = mockAbuseReport; + const findBadge = () => wrapper.findComponent(GlBadge); + const findIcon = () => wrapper.findComponent(GlIcon); const findAvatar = () => wrapper.findComponent(GlAvatar); const findLink = () => wrapper.findComponent(GlLink); const findButton = () => wrapper.findComponent(GlButton); - const findActions = () => wrapper.findComponent(AbuseReportActions); + const findActions = () => wrapper.findComponent(ReportActions); const createComponent = (props = {}) => { wrapper = shallowMount(ReportHeader, { propsData: { user, - actions, + report, ...props, }, }); @@ -51,9 +53,42 @@ describe('ReportHeader', () => { expect(button.text()).toBe(REPORT_HEADER_I18N.adminProfile); }); + describe.each` + status | text | variant | className | badgeIcon + ${STATUS_OPEN} | ${REPORT_HEADER_I18N[STATUS_OPEN]} | ${'success'} | ${'issuable-status-badge-open'} | ${'issues'} + ${STATUS_CLOSED} | ${REPORT_HEADER_I18N[STATUS_CLOSED]} | ${'info'} | ${'issuable-status-badge-closed'} | ${'issue-closed'} + `( + 'rendering the report $status status badge', + ({ status, text, variant, className, badgeIcon }) => { + beforeEach(() => { + createComponent({ report: { ...report, status } }); + }); + + it(`indicates the ${status} status`, () => { + expect(findBadge().text()).toBe(text); + }); + + it(`with the ${variant} variant`, () => { + expect(findBadge().props('variant')).toBe(variant); + }); + + it(`with the text '${text}' as 'aria-label'`, () => { + expect(findBadge().attributes('aria-label')).toBe(text); + }); + + it(`contains the ${className} class`, () => { + expect(findBadge().element.classList).toContain(className); + }); + + it(`has an icon with the ${badgeIcon} name`, () => { + expect(findIcon().props('name')).toBe(badgeIcon); + }); + }, + ); + it('renders the actions', () => { const actionsComponent = findActions(); - expect(actionsComponent.props('report')).toMatchObject(actions); + expect(actionsComponent.props('report')).toMatchObject(report); }); }); diff --git a/spec/frontend/admin/abuse_report/mock_data.js b/spec/frontend/admin/abuse_report/mock_data.js index ee0f0967735..8c0ae223c87 100644 --- a/spec/frontend/admin/abuse_report/mock_data.js +++ b/spec/frontend/admin/abuse_report/mock_data.js @@ -40,6 +40,7 @@ export const mockAbuseReport = { path: '/reporter', }, report: { + status: 'open', message: 'This is obvious spam', reportedAt: '2023-03-29T09:39:50.502Z', category: 'spam', @@ -49,13 +50,6 @@ export const mockAbuseReport = { url: 'http://localhost:3000/spamuser417/project/-/merge_requests/1#note_1375', screenshot: '/uploads/-/system/abuse_report/screenshot/27/Screenshot_2023-03-30_at_16.56.37.png', - }, - actions: { - reportedUser: { name: 'Sp4m User', createdAt: '2023-03-29T09:30:23.885Z' }, - userBlocked: false, - blockUserPath: '/admin/users/spamuser417/block', - removeReportPath: '/admin/abuse_reports/27', - removeUserAndReportPath: '/admin/abuse_reports/27?remove_user=true', - redirectPath: '/admin/abuse_reports', + updatePath: '/admin/abuse_reports/27', }, }; diff --git a/spec/frontend/admin/abuse_reports/components/abuse_report_actions_spec.js b/spec/frontend/admin/abuse_reports/components/abuse_report_actions_spec.js deleted file mode 100644 index bc648e52fad..00000000000 --- a/spec/frontend/admin/abuse_reports/components/abuse_report_actions_spec.js +++ /dev/null @@ -1,202 +0,0 @@ -import { nextTick } from 'vue'; -import axios from 'axios'; -import MockAdapter from 'axios-mock-adapter'; -import { GlDisclosureDropdown, GlDisclosureDropdownItem, GlModal } from '@gitlab/ui'; -import { mountExtended } from 'helpers/vue_test_utils_helper'; -import AbuseReportActions from '~/admin/abuse_reports/components/abuse_report_actions.vue'; -import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; -import { redirectTo, refreshCurrentPage } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated -import { createAlert, VARIANT_SUCCESS } from '~/alert'; -import { sprintf } from '~/locale'; -import { ACTIONS_I18N } from '~/admin/abuse_reports/constants'; -import { mockAbuseReports } from '../mock_data'; - -jest.mock('~/alert'); -jest.mock('~/lib/utils/url_utility'); - -describe('AbuseReportActions', () => { - let wrapper; - - const findRemoveUserAndReportButton = () => wrapper.findByText('Remove user & report'); - const findBlockUserButton = () => wrapper.findByTestId('block-user-button'); - const findRemoveReportButton = () => wrapper.findByText('Remove report'); - const findConfirmationModal = () => wrapper.findComponent(GlModal); - - const report = mockAbuseReports[0]; - - const createComponent = (props = {}) => { - wrapper = mountExtended(AbuseReportActions, { - propsData: { - report, - ...props, - }, - stubs: { - GlDisclosureDropdown, - GlDisclosureDropdownItem, - }, - }); - }; - - describe('default', () => { - beforeEach(() => { - createComponent(); - }); - - it('displays "Block user", "Remove user & report", and "Remove report" buttons', () => { - expect(findRemoveUserAndReportButton().text()).toBe(ACTIONS_I18N.removeUserAndReport); - - const blockButton = findBlockUserButton(); - expect(blockButton.text()).toBe(ACTIONS_I18N.blockUser); - expect(blockButton.attributes('disabled')).toBeUndefined(); - - expect(findRemoveReportButton().text()).toBe(ACTIONS_I18N.removeReport); - }); - - it('does not show the confirmation modal initially', () => { - expect(findConfirmationModal().props('visible')).toBe(false); - }); - }); - - describe('block button when user is already blocked', () => { - it('is disabled and has the correct text', () => { - createComponent({ report: { ...report, userBlocked: true } }); - - const button = findBlockUserButton(); - expect(button.text()).toBe(ACTIONS_I18N.alreadyBlocked); - expect(button.attributes('disabled')).toBeDefined(); - }); - }); - - describe('actions', () => { - let axiosMock; - - beforeEach(() => { - axiosMock = new MockAdapter(axios); - - createComponent(); - }); - - afterEach(() => { - axiosMock.restore(); - createAlert.mockClear(); - }); - - describe('on remove user and report', () => { - it('shows confirmation modal and reloads the page on success', async () => { - findRemoveUserAndReportButton().trigger('click'); - await nextTick(); - - expect(findConfirmationModal().props()).toMatchObject({ - visible: true, - title: sprintf(ACTIONS_I18N.removeUserAndReportConfirm, { - user: report.reportedUser.name, - }), - }); - - axiosMock.onDelete(report.removeUserAndReportPath).reply(HTTP_STATUS_OK); - - findConfirmationModal().vm.$emit('primary'); - await axios.waitForAll(); - - expect(refreshCurrentPage).toHaveBeenCalled(); - }); - - describe('when a redirect path is present', () => { - beforeEach(() => { - createComponent({ report: { ...report, redirectPath: '/redirect_path' } }); - }); - - it('redirects to the given path', async () => { - findRemoveUserAndReportButton().trigger('click'); - await nextTick(); - - axiosMock.onDelete(report.removeUserAndReportPath).reply(HTTP_STATUS_OK); - - findConfirmationModal().vm.$emit('primary'); - await axios.waitForAll(); - - expect(redirectTo).toHaveBeenCalledWith('/redirect_path'); // eslint-disable-line import/no-deprecated - }); - }); - }); - - describe('on block user', () => { - beforeEach(async () => { - findBlockUserButton().trigger('click'); - await nextTick(); - }); - - it('shows confirmation modal', () => { - expect(findConfirmationModal().props()).toMatchObject({ - visible: true, - title: ACTIONS_I18N.blockUserConfirm, - }); - }); - - describe.each([ - { - responseData: { notice: 'Notice' }, - createAlertArgs: { message: 'Notice', variant: VARIANT_SUCCESS }, - blockButtonText: ACTIONS_I18N.alreadyBlocked, - blockButtonDisabled: 'disabled', - }, - { - responseData: { error: 'Error' }, - createAlertArgs: { message: 'Error' }, - blockButtonText: ACTIONS_I18N.blockUser, - blockButtonDisabled: undefined, - }, - ])( - 'when response JSON is $responseData', - ({ responseData, createAlertArgs, blockButtonText, blockButtonDisabled }) => { - beforeEach(async () => { - axiosMock.onPut(report.blockUserPath).reply(HTTP_STATUS_OK, responseData); - - findConfirmationModal().vm.$emit('primary'); - await axios.waitForAll(); - }); - - it('updates the block button correctly', () => { - const button = findBlockUserButton(); - expect(button.text()).toBe(blockButtonText); - expect(button.attributes('disabled')).toBe(blockButtonDisabled); - }); - - it('displays the returned message', () => { - expect(createAlert).toHaveBeenCalledWith(createAlertArgs); - }); - }, - ); - }); - - describe('on remove report', () => { - it('reloads the page on success', async () => { - axiosMock.onDelete(report.removeReportPath).reply(HTTP_STATUS_OK); - - findRemoveReportButton().trigger('click'); - - expect(findConfirmationModal().props('visible')).toBe(false); - - await axios.waitForAll(); - - expect(refreshCurrentPage).toHaveBeenCalled(); - }); - - describe('when a redirect path is present', () => { - beforeEach(() => { - createComponent({ report: { ...report, redirectPath: '/redirect_path' } }); - }); - - it('redirects to the given path', async () => { - axiosMock.onDelete(report.removeReportPath).reply(HTTP_STATUS_OK); - - findRemoveReportButton().trigger('click'); - - await axios.waitForAll(); - - expect(redirectTo).toHaveBeenCalledWith('/redirect_path'); // eslint-disable-line import/no-deprecated - }); - }); - }); - }); -}); |