diff options
Diffstat (limited to 'spec/frontend/custom_metrics')
-rw-r--r-- | spec/frontend/custom_metrics/components/custom_metrics_form_fields_spec.js | 334 | ||||
-rw-r--r-- | spec/frontend/custom_metrics/components/custom_metrics_form_spec.js | 48 |
2 files changed, 382 insertions, 0 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'); + }); + }); +}); |