summaryrefslogtreecommitdiff
path: root/spec/frontend/ci_variable_list
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/ci_variable_list')
-rw-r--r--spec/frontend/ci_variable_list/components/ci_key_field_spec.js244
-rw-r--r--spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js153
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);
+ });
+ });
+ });
});