diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-08 21:09:50 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-08 21:09:50 +0000 |
commit | 76358aee81a471a5e71eaf3e8c2d91b7c9a0a5a9 (patch) | |
tree | df9ba3dcc09eb404de31e0d79cb8f0b77812e655 /spec/frontend | |
parent | 80e9fdc9682cfbcfb9202a2733605a6a6bd23f05 (diff) | |
download | gitlab-ce-76358aee81a471a5e71eaf3e8c2d91b7c9a0a5a9.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
4 files changed, 501 insertions, 2 deletions
diff --git a/spec/frontend/custom_metrics/components/custom_metrics_form_fields_spec.js b/spec/frontend/custom_metrics/components/custom_metrics_form_fields_spec.js new file mode 100644 index 00000000000..61cbef0c557 --- /dev/null +++ b/spec/frontend/custom_metrics/components/custom_metrics_form_fields_spec.js @@ -0,0 +1,334 @@ +import { mount } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import { TEST_HOST } from 'helpers/test_constants'; +import waitForPromises from 'helpers/wait_for_promises'; +import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue'; +import axios from '~/lib/utils/axios_utils'; + +const { CancelToken } = axios; + +describe('custom metrics form fields component', () => { + let component; + let mockAxios; + + const getNamedInput = name => component.element.querySelector(`input[name="${name}"]`); + const validateQueryPath = `${TEST_HOST}/mock/path`; + const validQueryResponse = { data: { success: true, query: { valid: true, error: '' } } }; + const csrfToken = 'mockToken'; + const formOperation = 'post'; + const debouncedValidateQueryMock = jest.fn(); + const makeFormData = (data = {}) => ({ + formData: { + title: '', + yLabel: '', + query: '', + unit: '', + group: '', + legend: '', + ...data, + }, + }); + const mountComponent = (props, methods = {}) => { + component = mount(CustomMetricsFormFields, { + propsData: { + formOperation, + validateQueryPath, + ...props, + }, + csrfToken, + methods, + }); + }; + + beforeEach(() => { + mockAxios = new MockAdapter(axios); + mockAxios.onPost(validateQueryPath).reply(validQueryResponse); + }); + + afterEach(() => { + component.destroy(); + mockAxios.restore(); + }); + + it('checks form validity', done => { + mountComponent({ + metricPersisted: true, + ...makeFormData({ + title: 'title', + yLabel: 'yLabel', + unit: 'unit', + group: 'group', + }), + }); + + component.vm.$nextTick(() => { + expect(component.vm.formIsValid).toBe(false); + done(); + }); + }); + + describe('hidden inputs', () => { + beforeEach(() => { + mountComponent(); + }); + + it('specifies form operation _method', () => { + expect(getNamedInput('_method', 'input').value).toBe('post'); + }); + + it('specifies authenticity token', () => { + expect(getNamedInput('authenticity_token', 'input').value).toBe(csrfToken); + }); + }); + + describe('name input', () => { + const name = 'prometheus_metric[title]'; + + it('is empty by default', () => { + mountComponent(); + + expect(getNamedInput(name).value).toBe(''); + }); + + it('receives a persisted value', () => { + const title = 'mockTitle'; + mountComponent(makeFormData({ title })); + + expect(getNamedInput(name).value).toBe(title); + }); + }); + + describe('group input', () => { + it('has a default value', () => { + mountComponent(); + + expect(getNamedInput('prometheus_metric[group]', 'glformradiogroup-stub').value).toBe( + 'business', + ); + }); + }); + + describe('query input', () => { + const queryInputName = 'prometheus_metric[query]'; + beforeEach(() => { + mockAxios.onPost(validateQueryPath).reply(validQueryResponse); + }); + + it('is empty by default', () => { + mountComponent(); + + expect(getNamedInput(queryInputName).value).toBe(''); + }); + + it('receives and validates a persisted value', () => { + const query = 'persistedQuery'; + const axiosPost = jest.spyOn(axios, 'post'); + const source = CancelToken.source(); + mountComponent({ metricPersisted: true, ...makeFormData({ query }) }); + + expect(axiosPost).toHaveBeenCalledWith( + validateQueryPath, + { query }, + { cancelToken: source.token }, + ); + expect(getNamedInput(queryInputName).value).toBe(query); + jest.runAllTimers(); + }); + + it('checks validity on user input', () => { + const query = 'changedQuery'; + mountComponent( + {}, + { + debouncedValidateQuery: debouncedValidateQueryMock, + }, + ); + const queryInput = component.find(`input[name="${queryInputName}"]`); + queryInput.setValue(query); + queryInput.trigger('input'); + + expect(debouncedValidateQueryMock).toHaveBeenCalledWith(query); + }); + + describe('when query validation is in flight', () => { + beforeEach(() => { + jest.useFakeTimers(); + mountComponent( + { metricPersisted: true, ...makeFormData({ query: 'validQuery' }) }, + { + requestValidation: jest.fn().mockImplementation( + () => + new Promise(resolve => + setTimeout(() => { + resolve(validQueryResponse); + }, 4000), + ), + ), + }, + ); + }); + + afterEach(() => { + jest.clearAllTimers(); + }); + + it('expect queryValidateInFlight is in flight', done => { + const queryInput = component.find(`input[name="${queryInputName}"]`); + queryInput.setValue('query'); + queryInput.trigger('input'); + + component.vm.$nextTick(() => { + expect(component.vm.queryValidateInFlight).toBe(true); + jest.runOnlyPendingTimers(); + waitForPromises() + .then(() => { + component.vm.$nextTick(() => { + expect(component.vm.queryValidateInFlight).toBe(false); + expect(component.vm.queryIsValid).toBe(true); + done(); + }); + }) + .catch(done.fail); + }); + }); + + it('expect loading message to display', done => { + const queryInput = component.find(`input[name="${queryInputName}"]`); + queryInput.setValue('query'); + queryInput.trigger('input'); + component.vm.$nextTick(() => { + expect(component.text()).toContain('Validating query'); + jest.runOnlyPendingTimers(); + done(); + }); + }); + + it('expect loading message to disappear', done => { + const queryInput = component.find(`input[name="${queryInputName}"]`); + queryInput.setValue('query'); + queryInput.trigger('input'); + component.vm.$nextTick(() => { + jest.runOnlyPendingTimers(); + waitForPromises() + .then(() => { + component.vm.$nextTick(() => { + expect(component.vm.queryValidateInFlight).toBe(false); + expect(component.vm.queryIsValid).toBe(true); + expect(component.vm.errorMessage).toBe(''); + done(); + }); + }) + .catch(done.fail); + }); + }); + }); + + describe('when query is invalid', () => { + const errorMessage = 'mockErrorMessage'; + const invalidQueryResponse = { + data: { success: true, query: { valid: false, error: errorMessage } }, + }; + + beforeEach(() => { + mountComponent( + { metricPersisted: true, ...makeFormData({ query: 'invalidQuery' }) }, + { + requestValidation: jest + .fn() + .mockImplementation(() => Promise.resolve(invalidQueryResponse)), + }, + ); + }); + + it('sets queryIsValid to false', done => { + component.vm.$nextTick(() => { + expect(component.vm.queryValidateInFlight).toBe(false); + expect(component.vm.queryIsValid).toBe(false); + done(); + }); + }); + + it('shows invalid query message', done => { + component.vm.$nextTick(() => { + expect(component.text()).toContain(errorMessage); + done(); + }); + }); + }); + + describe('when query is valid', () => { + beforeEach(() => { + mountComponent( + { metricPersisted: true, ...makeFormData({ query: 'validQuery' }) }, + { + requestValidation: jest + .fn() + .mockImplementation(() => Promise.resolve(validQueryResponse)), + }, + ); + }); + + it('sets queryIsValid to true when query is valid', done => { + component.vm.$nextTick(() => { + expect(component.vm.queryIsValid).toBe(true); + done(); + }); + }); + + it('shows valid query message', () => { + expect(component.text()).toContain('PromQL query is valid'); + }); + }); + }); + + describe('yLabel input', () => { + const name = 'prometheus_metric[y_label]'; + + it('is empty by default', () => { + mountComponent(); + + expect(getNamedInput(name).value).toBe(''); + }); + + it('receives a persisted value', () => { + const yLabel = 'mockYLabel'; + mountComponent(makeFormData({ yLabel })); + + expect(getNamedInput(name).value).toBe(yLabel); + }); + }); + + describe('unit input', () => { + const name = 'prometheus_metric[unit]'; + + it('is empty by default', () => { + mountComponent(); + + expect(getNamedInput(name).value).toBe(''); + }); + + it('receives a persisted value', () => { + const unit = 'mockUnit'; + mountComponent(makeFormData({ unit })); + + expect(getNamedInput(name).value).toBe(unit); + }); + }); + + describe('legend input', () => { + const name = 'prometheus_metric[legend]'; + + it('is empty by default', () => { + mountComponent(); + + expect(getNamedInput(name).value).toBe(''); + }); + + it('receives a persisted value', () => { + const legend = 'mockLegend'; + mountComponent(makeFormData({ legend })); + + expect(getNamedInput(name).value).toBe(legend); + }); + }); +}); diff --git a/spec/frontend/custom_metrics/components/custom_metrics_form_spec.js b/spec/frontend/custom_metrics/components/custom_metrics_form_spec.js new file mode 100644 index 00000000000..384d6699150 --- /dev/null +++ b/spec/frontend/custom_metrics/components/custom_metrics_form_spec.js @@ -0,0 +1,48 @@ +import { shallowMount } from '@vue/test-utils'; +import CustomMetricsForm from '~/custom_metrics/components/custom_metrics_form.vue'; + +describe('CustomMetricsForm', () => { + let wrapper; + + function mountComponent({ + metricPersisted = false, + formData = { + title: '', + query: '', + yLabel: '', + unit: '', + group: '', + legend: '', + }, + }) { + wrapper = shallowMount(CustomMetricsForm, { + propsData: { + customMetricsPath: '', + editProjectServicePath: '', + metricPersisted, + validateQueryPath: '', + formData, + }, + }); + } + + afterEach(() => { + wrapper.destroy(); + }); + + describe('Computed', () => { + it('Form button and title text indicate the custom metric is being edited', () => { + mountComponent({ metricPersisted: true }); + + expect(wrapper.vm.saveButtonText).toBe('Save Changes'); + expect(wrapper.vm.titleText).toBe('Edit metric'); + }); + + it('Form button and title text indicate the custom metric is being created', () => { + mountComponent({ metricPersisted: false }); + + expect(wrapper.vm.saveButtonText).toBe('Create metric'); + expect(wrapper.vm.titleText).toBe('New metric'); + }); + }); +}); diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js index 7c559aed2c5..b37c10791bf 100644 --- a/spec/frontend/monitoring/store/actions_spec.js +++ b/spec/frontend/monitoring/store/actions_spec.js @@ -6,6 +6,7 @@ import statusCodes from '~/lib/utils/http_status'; import * as commonUtils from '~/lib/utils/common_utils'; import createFlash from '~/flash'; import { defaultTimeRange } from '~/vue_shared/constants'; +import { ENVIRONMENT_AVAILABLE_STATE } from '~/monitoring/constants'; import store from '~/monitoring/stores'; import * as types from '~/monitoring/stores/mutation_types'; @@ -157,17 +158,21 @@ describe('Monitoring store actions', () => { variables: { projectPath: state.projectPath, search: searchTerm, + states: [ENVIRONMENT_AVAILABLE_STATE], }, }; state.environmentsSearchTerm = searchTerm; - mockMutate.mockReturnValue(Promise.resolve()); + mockMutate.mockResolvedValue({}); return testAction( fetchEnvironmentsData, null, state, [], - [{ type: 'requestEnvironmentsData' }, { type: 'receiveEnvironmentsDataFailure' }], + [ + { type: 'requestEnvironmentsData' }, + { type: 'receiveEnvironmentsDataSuccess', payload: [] }, + ], () => { expect(mockMutate).toHaveBeenCalledWith(mutationVariables); }, diff --git a/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js b/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js new file mode 100644 index 00000000000..794deca42ac --- /dev/null +++ b/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js @@ -0,0 +1,112 @@ +import { shallowMount } from '@vue/test-utils'; +import AccessibilityIssueBody from '~/reports/accessibility_report/components/accessibility_issue_body.vue'; + +const issue = { + name: + 'The accessibility scanning found 2 errors of the following type: WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent', + code: 'WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent', + message: 'This element has insufficient contrast at this conformance level.', + status: 'failed', + className: 'spec.test_spec', + learnMoreUrl: 'https://www.w3.org/TR/WCAG20-TECHS/H91.html', +}; + +describe('CustomMetricsForm', () => { + let wrapper; + + const mountComponent = ({ name, code, message, status, className }, isNew = false) => { + wrapper = shallowMount(AccessibilityIssueBody, { + propsData: { + issue: { + name, + code, + message, + status, + className, + }, + isNew, + }, + }); + }; + + const findIsNewBadge = () => wrapper.find({ ref: 'accessibility-issue-is-new-badge' }); + + beforeEach(() => { + mountComponent(issue); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('Displays the issue message', () => { + const description = wrapper.find({ ref: 'accessibility-issue-description' }).text(); + + expect(description).toContain(`Message: ${issue.message}`); + }); + + describe('When an issue code is present', () => { + it('Creates the correct URL for learning more about the issue code', () => { + const learnMoreUrl = wrapper + .find({ ref: 'accessibility-issue-learn-more' }) + .attributes('href'); + + expect(learnMoreUrl).toEqual(issue.learnMoreUrl); + }); + }); + + describe('When an issue code is not present', () => { + beforeEach(() => { + mountComponent({ + ...issue, + code: undefined, + }); + }); + + it('Creates a URL leading to the overview documentation page', () => { + const learnMoreUrl = wrapper + .find({ ref: 'accessibility-issue-learn-more' }) + .attributes('href'); + + expect(learnMoreUrl).toEqual('https://www.w3.org/TR/WCAG20-TECHS/Overview.html'); + }); + }); + + describe('When an issue code does not contain the TECHS code', () => { + beforeEach(() => { + mountComponent({ + ...issue, + code: 'WCAG2AA.Principle4.Guideline4_1.4_1_2', + }); + }); + + it('Creates a URL leading to the overview documentation page', () => { + const learnMoreUrl = wrapper + .find({ ref: 'accessibility-issue-learn-more' }) + .attributes('href'); + + expect(learnMoreUrl).toEqual('https://www.w3.org/TR/WCAG20-TECHS/Overview.html'); + }); + }); + + describe('When issue is new', () => { + beforeEach(() => { + mountComponent(issue, true); + }); + + it('Renders the new badge', () => { + expect(findIsNewBadge().exists()).toEqual(true); + }); + }); + + describe('When issue is not new', () => { + beforeEach(() => { + mountComponent(issue, false); + }); + + it('Does not render the new badge', () => { + expect(findIsNewBadge().exists()).toEqual(false); + }); + }); +}); |