diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-20 18:38:24 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-20 18:38:24 +0000 |
commit | 983a0bba5d2a042c4a3bbb22432ec192c7501d82 (patch) | |
tree | b153cd387c14ba23bd5a07514c7c01fddf6a78a0 /spec/frontend/ci_variable_list | |
parent | a2bddee2cdb38673df0e004d5b32d9f77797de64 (diff) | |
download | gitlab-ce-983a0bba5d2a042c4a3bbb22432ec192c7501d82.tar.gz |
Add latest changes from gitlab-org/gitlab@12-10-stable-ee
Diffstat (limited to 'spec/frontend/ci_variable_list')
-rw-r--r-- | spec/frontend/ci_variable_list/components/ci_key_field_spec.js | 244 | ||||
-rw-r--r-- | spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js | 153 |
2 files changed, 387 insertions, 10 deletions
diff --git a/spec/frontend/ci_variable_list/components/ci_key_field_spec.js b/spec/frontend/ci_variable_list/components/ci_key_field_spec.js new file mode 100644 index 00000000000..bcc29f22dd1 --- /dev/null +++ b/spec/frontend/ci_variable_list/components/ci_key_field_spec.js @@ -0,0 +1,244 @@ +import { mount } from '@vue/test-utils'; +import { GlButton, GlFormInput } from '@gitlab/ui'; +import { AWS_ACCESS_KEY_ID, AWS_DEFAULT_REGION } from '~/ci_variable_list/constants'; +import CiKeyField from '~/ci_variable_list/components/ci_key_field.vue'; + +import { + awsTokens, + awsTokenList, +} from '~/ci_variable_list/components/ci_variable_autocomplete_tokens'; + +const doTimes = (num, fn) => { + for (let i = 0; i < num; i += 1) { + fn(); + } +}; + +describe('Ci Key field', () => { + let wrapper; + + const createComponent = () => { + wrapper = mount({ + data() { + return { + inputVal: '', + tokens: awsTokenList, + }; + }, + components: { CiKeyField }, + template: ` + <div> + <ci-key-field + v-model="inputVal" + :token-list="tokens" + /> + </div> + `, + }); + }; + + const findDropdown = () => wrapper.find('#ci-variable-dropdown'); + const findDropdownOptions = () => wrapper.findAll(GlButton).wrappers.map(item => item.text()); + const findInput = () => wrapper.find(GlFormInput); + const findInputValue = () => findInput().element.value; + const setInput = val => findInput().setValue(val); + const clickDown = () => findInput().trigger('keydown.down'); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('match and filter functionality', () => { + beforeEach(() => { + createComponent(); + }); + + it('is closed when the input is empty', () => { + expect(findInput().isVisible()).toBe(true); + expect(findInputValue()).toBe(''); + expect(findDropdown().isVisible()).toBe(false); + }); + + it('is open when the input text matches a token', () => { + setInput('AWS'); + return wrapper.vm.$nextTick().then(() => { + expect(findDropdown().isVisible()).toBe(true); + }); + }); + + it('shows partial matches at string start', () => { + setInput('AWS'); + return wrapper.vm.$nextTick().then(() => { + expect(findDropdown().isVisible()).toBe(true); + expect(findDropdownOptions()).toEqual(awsTokenList); + }); + }); + + it('shows partial matches mid-string', () => { + setInput('D'); + return wrapper.vm.$nextTick().then(() => { + expect(findDropdown().isVisible()).toBe(true); + expect(findDropdownOptions()).toEqual([ + awsTokens[AWS_ACCESS_KEY_ID].name, + awsTokens[AWS_DEFAULT_REGION].name, + ]); + }); + }); + + it('is closed when the text does not match', () => { + setInput('elephant'); + return wrapper.vm.$nextTick().then(() => { + expect(findDropdown().isVisible()).toBe(false); + }); + }); + }); + + describe('keyboard navigation in dropdown', () => { + beforeEach(() => { + createComponent(); + }); + + describe('on down arrow + enter', () => { + it('selects the next item in the list and closes the dropdown', () => { + setInput('AWS'); + return wrapper.vm + .$nextTick() + .then(() => { + findInput().trigger('keydown.down'); + findInput().trigger('keydown.enter'); + return wrapper.vm.$nextTick(); + }) + .then(() => { + expect(findInputValue()).toBe(awsTokenList[0]); + }); + }); + + it('loops to the top when it reaches the bottom', () => { + setInput('AWS'); + return wrapper.vm + .$nextTick() + .then(() => { + doTimes(findDropdownOptions().length + 1, clickDown); + findInput().trigger('keydown.enter'); + return wrapper.vm.$nextTick(); + }) + .then(() => { + expect(findInputValue()).toBe(awsTokenList[0]); + }); + }); + }); + + describe('on up arrow + enter', () => { + it('selects the previous item in the list and closes the dropdown', () => { + setInput('AWS'); + return wrapper.vm + .$nextTick() + .then(() => { + doTimes(3, clickDown); + findInput().trigger('keydown.up'); + findInput().trigger('keydown.enter'); + return wrapper.vm.$nextTick(); + }) + .then(() => { + expect(findInputValue()).toBe(awsTokenList[1]); + }); + }); + + it('loops to the bottom when it reaches the top', () => { + setInput('AWS'); + return wrapper.vm + .$nextTick() + .then(() => { + findInput().trigger('keydown.down'); + findInput().trigger('keydown.up'); + findInput().trigger('keydown.enter'); + return wrapper.vm.$nextTick(); + }) + .then(() => { + expect(findInputValue()).toBe(awsTokenList[awsTokenList.length - 1]); + }); + }); + }); + + describe('on enter with no item highlighted', () => { + it('does not select any item and closes the dropdown', () => { + setInput('AWS'); + return wrapper.vm + .$nextTick() + .then(() => { + findInput().trigger('keydown.enter'); + return wrapper.vm.$nextTick(); + }) + .then(() => { + expect(findInputValue()).toBe('AWS'); + }); + }); + }); + + describe('on click', () => { + it('selects the clicked item regardless of arrow highlight', () => { + setInput('AWS'); + return wrapper.vm + .$nextTick() + .then(() => { + wrapper.find(GlButton).trigger('click'); + return wrapper.vm.$nextTick(); + }) + .then(() => { + expect(findInputValue()).toBe(awsTokenList[0]); + }); + }); + }); + + describe('on tab', () => { + it('selects entered text, closes dropdown', () => { + setInput('AWS'); + return wrapper.vm + .$nextTick() + .then(() => { + findInput().trigger('keydown.tab'); + doTimes(2, clickDown); + return wrapper.vm.$nextTick(); + }) + .then(() => { + expect(findInputValue()).toBe('AWS'); + expect(findDropdown().isVisible()).toBe(false); + }); + }); + }); + + describe('on esc', () => { + describe('when dropdown is open', () => { + it('closes dropdown and does not select anything', () => { + setInput('AWS'); + return wrapper.vm + .$nextTick() + .then(() => { + findInput().trigger('keydown.esc'); + return wrapper.vm.$nextTick(); + }) + .then(() => { + expect(findInputValue()).toBe('AWS'); + expect(findDropdown().isVisible()).toBe(false); + }); + }); + }); + + describe('when dropdown is closed', () => { + it('clears the input field', () => { + setInput('elephant'); + return wrapper.vm + .$nextTick() + .then(() => { + expect(findDropdown().isVisible()).toBe(false); + findInput().trigger('keydown.esc'); + return wrapper.vm.$nextTick(); + }) + .then(() => { + expect(findInputValue()).toBe(''); + }); + }); + }); + }); + }); +}); diff --git a/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js index 70edd36669b..7b8d69df35e 100644 --- a/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js +++ b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js @@ -1,7 +1,10 @@ import Vuex from 'vuex'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { createLocalVue, shallowMount, mount } from '@vue/test-utils'; import { GlDeprecatedButton } from '@gitlab/ui'; +import { AWS_ACCESS_KEY_ID } from '~/ci_variable_list/constants'; import CiVariableModal from '~/ci_variable_list/components/ci_variable_modal.vue'; +import CiKeyField from '~/ci_variable_list/components/ci_key_field.vue'; +import { awsTokens } from '~/ci_variable_list/components/ci_variable_autocomplete_tokens'; import createStore from '~/ci_variable_list/store'; import mockData from '../services/mock_data'; import ModalStub from '../stubs'; @@ -13,14 +16,17 @@ describe('Ci variable modal', () => { let wrapper; let store; - const createComponent = () => { + const createComponent = (method, options = {}) => { store = createStore(); - wrapper = shallowMount(CiVariableModal, { + wrapper = method(CiVariableModal, { + attachToDocument: true, + provide: { glFeatures: { ciKeyAutocomplete: true } }, stubs: { GlModal: ModalStub, }, localVue, store, + ...options, }); }; @@ -34,22 +40,46 @@ describe('Ci variable modal', () => { .findAll(GlDeprecatedButton) .at(1); - beforeEach(() => { - createComponent(); - jest.spyOn(store, 'dispatch').mockImplementation(); - }); - afterEach(() => { wrapper.destroy(); }); - it('button is disabled when no key/value pair are present', () => { - expect(addOrUpdateButton(1).attributes('disabled')).toBeTruthy(); + describe('Feature flag', () => { + describe('when off', () => { + beforeEach(() => { + createComponent(shallowMount, { provide: { glFeatures: { ciKeyAutocomplete: false } } }); + }); + + it('does not render the autocomplete dropdown', () => { + expect(wrapper.contains(CiKeyField)).toBe(false); + }); + }); + + describe('when on', () => { + beforeEach(() => { + createComponent(shallowMount); + }); + it('renders the autocomplete dropdown', () => { + expect(wrapper.find(CiKeyField).exists()).toBe(true); + }); + }); + }); + + describe('Basic interactions', () => { + beforeEach(() => { + createComponent(shallowMount); + }); + + it('button is disabled when no key/value pair are present', () => { + expect(addOrUpdateButton(1).attributes('disabled')).toBeTruthy(); + }); }); describe('Adding a new variable', () => { beforeEach(() => { const [variable] = mockData.mockVariables; + createComponent(shallowMount); + jest.spyOn(store, 'dispatch').mockImplementation(); store.state.variable = variable; }); @@ -71,6 +101,8 @@ describe('Ci variable modal', () => { describe('Editing a variable', () => { beforeEach(() => { const [variable] = mockData.mockVariables; + createComponent(shallowMount); + jest.spyOn(store, 'dispatch').mockImplementation(); store.state.variableBeingEdited = variable; }); @@ -96,4 +128,105 @@ describe('Ci variable modal', () => { expect(store.dispatch).toHaveBeenCalledWith('deleteVariable', mockData.mockVariables[0]); }); }); + + describe('Validations', () => { + const maskError = 'This variable can not be masked.'; + + describe('when the key state is invalid', () => { + beforeEach(() => { + const [variable] = mockData.mockVariables; + const invalidKeyVariable = { + ...variable, + key: AWS_ACCESS_KEY_ID, + value: 'AKIAIOSFODNN7EXAMPLEjdhy', + secret_value: 'AKIAIOSFODNN7EXAMPLEjdhy', + }; + createComponent(mount); + store.state.variable = invalidKeyVariable; + }); + + it('disables the submit button', () => { + expect(addOrUpdateButton(1).attributes('disabled')).toBeTruthy(); + }); + + it('shows the correct error text', () => { + const errorText = awsTokens[AWS_ACCESS_KEY_ID].invalidMessage; + expect(findModal().text()).toContain(errorText); + }); + }); + + describe('when the mask state is invalid', () => { + beforeEach(() => { + const [variable] = mockData.mockVariables; + const invalidMaskVariable = { + ...variable, + key: 'qs', + value: 'd:;', + secret_value: 'd:;', + masked: true, + }; + createComponent(mount); + store.state.variable = invalidMaskVariable; + }); + + it('disables the submit button', () => { + expect(addOrUpdateButton(1).attributes('disabled')).toBeTruthy(); + }); + + it('shows the correct error text', () => { + expect(findModal().text()).toContain(maskError); + }); + }); + + describe('when the mask and key states are invalid', () => { + beforeEach(() => { + const [variable] = mockData.mockVariables; + const invalidMaskandKeyVariable = { + ...variable, + key: AWS_ACCESS_KEY_ID, + value: 'AKIAIOSFODNN7EXAMPLEjdhyd:;', + secret_value: 'AKIAIOSFODNN7EXAMPLEjdhyd:;', + masked: true, + }; + createComponent(mount); + store.state.variable = invalidMaskandKeyVariable; + }); + + it('disables the submit button', () => { + expect(addOrUpdateButton(1).attributes('disabled')).toBeTruthy(); + }); + + it('shows the correct error text', () => { + const errorText = awsTokens[AWS_ACCESS_KEY_ID].invalidMessage; + expect(findModal().text()).toContain(maskError); + expect(findModal().text()).toContain(errorText); + }); + }); + + describe('when both states are valid', () => { + beforeEach(() => { + const [variable] = mockData.mockVariables; + const validMaskandKeyVariable = { + ...variable, + key: AWS_ACCESS_KEY_ID, + value: 'AKIAIOSFODNN7EXAMPLE', + secret_value: 'AKIAIOSFODNN7EXAMPLE', + masked: true, + }; + createComponent(mount); + store.state.variable = validMaskandKeyVariable; + store.state.maskableRegex = /^[a-zA-Z0-9_+=/@:-]{8,}$/; + }); + + it('does not disable the submit button', () => { + expect(addOrUpdateButton(1).attributes('disabled')).toBeFalsy(); + }); + + it('shows no error text', () => { + const errorText = awsTokens[AWS_ACCESS_KEY_ID].invalidMessage; + expect(findModal().text()).not.toContain(maskError); + expect(findModal().text()).not.toContain(errorText); + }); + }); + }); }); |