diff options
Diffstat (limited to 'spec/frontend/terraform')
3 files changed, 240 insertions, 38 deletions
diff --git a/spec/frontend/terraform/components/states_table_actions_spec.js b/spec/frontend/terraform/components/states_table_actions_spec.js index 3f5df8a96f8..61f6e9f0f7b 100644 --- a/spec/frontend/terraform/components/states_table_actions_spec.js +++ b/spec/frontend/terraform/components/states_table_actions_spec.js @@ -1,7 +1,8 @@ import { GlDropdown, GlModal, GlSprintf } from '@gitlab/ui'; import { createLocalVue, shallowMount } from '@vue/test-utils'; -import createMockApollo from 'helpers/mock_apollo_helper'; import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; import StateActions from '~/terraform/components/states_table_actions.vue'; import lockStateMutation from '~/terraform/graphql/mutations/lock_state.mutation.graphql'; import removeStateMutation from '~/terraform/graphql/mutations/remove_state.mutation.graphql'; @@ -13,7 +14,9 @@ localVue.use(VueApollo); describe('StatesTableActions', () => { let lockResponse; let removeResponse; + let toast; let unlockResponse; + let updateStateResponse; let wrapper; const defaultProps = { @@ -26,7 +29,9 @@ describe('StatesTableActions', () => { }; const createMockApolloProvider = () => { - lockResponse = jest.fn().mockResolvedValue({ data: { terraformStateLock: { errors: [] } } }); + lockResponse = jest + .fn() + .mockResolvedValue({ data: { terraformStateLock: { errors: ['There was an error'] } } }); removeResponse = jest .fn() @@ -36,26 +41,39 @@ describe('StatesTableActions', () => { .fn() .mockResolvedValue({ data: { terraformStateUnlock: { errors: [] } } }); - return createMockApollo([ - [lockStateMutation, lockResponse], - [removeStateMutation, removeResponse], - [unlockStateMutation, unlockResponse], - ]); + updateStateResponse = jest.fn().mockResolvedValue({}); + + return createMockApollo( + [ + [lockStateMutation, lockResponse], + [removeStateMutation, removeResponse], + [unlockStateMutation, unlockResponse], + ], + { + Mutation: { + addDataToTerraformState: updateStateResponse, + }, + }, + ); }; const createComponent = (propsData = defaultProps) => { const apolloProvider = createMockApolloProvider(); + toast = jest.fn(); + wrapper = shallowMount(StateActions, { apolloProvider, localVue, propsData, + mocks: { $toast: { show: toast } }, stubs: { GlDropdown, GlModal, GlSprintf }, }); return wrapper.vm.$nextTick(); }; + const findActionsDropdown = () => wrapper.find(GlDropdown); const findLockBtn = () => wrapper.find('[data-testid="terraform-state-lock"]'); const findUnlockBtn = () => wrapper.find('[data-testid="terraform-state-unlock"]'); const findDownloadBtn = () => wrapper.find('[data-testid="terraform-state-download"]'); @@ -69,10 +87,44 @@ describe('StatesTableActions', () => { afterEach(() => { lockResponse = null; removeResponse = null; + toast = null; unlockResponse = null; + updateStateResponse = null; wrapper.destroy(); }); + describe('when the state is loading', () => { + describe('when lock/unlock is processing', () => { + beforeEach(() => { + return createComponent({ + state: { + ...defaultProps.state, + loadingLock: true, + }, + }); + }); + + it('disables the actions dropdown', () => { + expect(findActionsDropdown().props('disabled')).toBe(true); + }); + }); + + describe('when remove is processing', () => { + beforeEach(() => { + return createComponent({ + state: { + ...defaultProps.state, + loadingRemove: true, + }, + }); + }); + + it('disables the actions dropdown', () => { + expect(findActionsDropdown().props('disabled')).toBe(true); + }); + }); + }); + describe('download button', () => { it('displays a download button', () => { expect(findDownloadBtn().text()).toBe('Download JSON'); @@ -104,7 +156,8 @@ describe('StatesTableActions', () => { describe('when clicking the unlock button', () => { beforeEach(() => { findUnlockBtn().vm.$emit('click'); - return wrapper.vm.$nextTick(); + + return waitForPromises(); }); it('calls the unlock mutation', () => { @@ -137,7 +190,8 @@ describe('StatesTableActions', () => { describe('when clicking the lock button', () => { beforeEach(() => { findLockBtn().vm.$emit('click'); - return wrapper.vm.$nextTick(); + + return waitForPromises(); }); it('calls the lock mutation', () => { @@ -145,6 +199,44 @@ describe('StatesTableActions', () => { stateID: unlockedProps.state.id, }); }); + + it('calls mutations to set loading and errors', () => { + // loading update + expect(updateStateResponse).toHaveBeenNthCalledWith( + 1, + {}, + { + terraformState: { + ...unlockedProps.state, + _showDetails: false, + errorMessages: [], + loadingLock: true, + loadingRemove: false, + }, + }, + // Apollo fields + expect.any(Object), + expect.any(Object), + ); + + // final update + expect(updateStateResponse).toHaveBeenNthCalledWith( + 2, + {}, + { + terraformState: { + ...unlockedProps.state, + _showDetails: true, + errorMessages: ['There was an error'], + loadingLock: false, + loadingRemove: false, + }, + }, + // Apollo fields + expect.any(Object), + expect.any(Object), + ); + }); }); }); @@ -156,7 +248,7 @@ describe('StatesTableActions', () => { describe('when clicking the remove button', () => { beforeEach(() => { findRemoveBtn().vm.$emit('click'); - return wrapper.vm.$nextTick(); + return waitForPromises(); }); it('displays a remove modal', () => { @@ -166,21 +258,70 @@ describe('StatesTableActions', () => { }); describe('when submitting the remove modal', () => { - it('does not call the remove mutation when state name is missing', async () => { - findRemoveModal().vm.$emit('ok'); - await wrapper.vm.$nextTick(); + describe('when state name is missing', () => { + beforeEach(() => { + findRemoveModal().vm.$emit('ok'); + return waitForPromises(); + }); - expect(removeResponse).not.toHaveBeenCalledWith(); + it('does not call the remove mutation', () => { + expect(removeResponse).not.toHaveBeenCalledWith(); + }); }); - it('calls the remove mutation when state name is present', async () => { - await wrapper.setData({ removeConfirmText: defaultProps.state.name }); + describe('when state name is present', () => { + beforeEach(async () => { + await wrapper.setData({ removeConfirmText: defaultProps.state.name }); + + findRemoveModal().vm.$emit('ok'); - findRemoveModal().vm.$emit('ok'); - await wrapper.vm.$nextTick(); + await waitForPromises(); + }); + + it('calls the remove mutation', () => { + expect(removeResponse).toHaveBeenCalledWith({ stateID: defaultProps.state.id }); + }); + + it('calls the toast action', () => { + expect(toast).toHaveBeenCalledWith(`${defaultProps.state.name} successfully removed`); + }); - expect(removeResponse).toHaveBeenCalledWith({ - stateID: defaultProps.state.id, + it('calls mutations to set loading and errors', () => { + // loading update + expect(updateStateResponse).toHaveBeenNthCalledWith( + 1, + {}, + { + terraformState: { + ...defaultProps.state, + _showDetails: false, + errorMessages: [], + loadingLock: false, + loadingRemove: true, + }, + }, + // Apollo fields + expect.any(Object), + expect.any(Object), + ); + + // final update + expect(updateStateResponse).toHaveBeenNthCalledWith( + 2, + {}, + { + terraformState: { + ...defaultProps.state, + _showDetails: false, + errorMessages: [], + loadingLock: false, + loadingRemove: false, + }, + }, + // Apollo fields + expect.any(Object), + expect.any(Object), + ); }); }); }); diff --git a/spec/frontend/terraform/components/states_table_spec.js b/spec/frontend/terraform/components/states_table_spec.js index f2b7bc00e5b..100e577f514 100644 --- a/spec/frontend/terraform/components/states_table_spec.js +++ b/spec/frontend/terraform/components/states_table_spec.js @@ -1,8 +1,8 @@ -import { GlIcon, GlTooltip } from '@gitlab/ui'; +import { GlIcon, GlLoadingIcon, GlTooltip } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import { useFakeDate } from 'helpers/fake_date'; -import StateActions from '~/terraform/components/states_table_actions.vue'; import StatesTable from '~/terraform/components/states_table.vue'; +import StateActions from '~/terraform/components/states_table_actions.vue'; describe('StatesTable', () => { let wrapper; @@ -11,7 +11,11 @@ describe('StatesTable', () => { const defaultProps = { states: [ { + _showDetails: true, + errorMessages: ['State 1 has errored'], name: 'state-1', + loadingLock: false, + loadingRemove: false, lockedAt: '2020-10-13T00:00:00Z', lockedByUser: { name: 'user-1', @@ -20,14 +24,22 @@ describe('StatesTable', () => { latestVersion: null, }, { + _showDetails: false, + errorMessages: [], name: 'state-2', + loadingLock: true, + loadingRemove: false, lockedAt: null, lockedByUser: null, updatedAt: '2020-10-10T00:00:00Z', latestVersion: null, }, { + _showDetails: false, + errorMessages: [], name: 'state-3', + loadingLock: true, + loadingRemove: false, lockedAt: '2020-10-10T00:00:00Z', lockedByUser: { name: 'user-2', @@ -54,7 +66,11 @@ describe('StatesTable', () => { }, }, { + _showDetails: true, + errorMessages: ['State 4 has errored'], name: 'state-4', + loadingLock: false, + loadingRemove: false, lockedAt: '2020-10-10T00:00:00Z', lockedByUser: null, updatedAt: '2020-10-10T00:00:00Z', @@ -76,6 +92,17 @@ describe('StatesTable', () => { }, }, }, + { + _showDetails: false, + errorMessages: [], + name: 'state-5', + loadingLock: false, + loadingRemove: true, + lockedAt: null, + lockedByUser: null, + updatedAt: '2020-10-10T00:00:00Z', + latestVersion: null, + }, ], }; @@ -96,14 +123,15 @@ describe('StatesTable', () => { }); it.each` - name | toolTipText | locked | lineNumber - ${'state-1'} | ${'Locked by user-1 2 days ago'} | ${true} | ${0} - ${'state-2'} | ${null} | ${false} | ${1} - ${'state-3'} | ${'Locked by user-2 5 days ago'} | ${true} | ${2} - ${'state-4'} | ${'Locked by Unknown User 5 days ago'} | ${true} | ${3} + name | toolTipText | locked | loading | lineNumber + ${'state-1'} | ${'Locked by user-1 2 days ago'} | ${true} | ${false} | ${0} + ${'state-2'} | ${'Locking state'} | ${false} | ${true} | ${1} + ${'state-3'} | ${'Unlocking state'} | ${false} | ${true} | ${2} + ${'state-4'} | ${'Locked by Unknown User 5 days ago'} | ${true} | ${false} | ${3} + ${'state-5'} | ${'Removing'} | ${false} | ${true} | ${4} `( 'displays the name and locked information "$name" for line "$lineNumber"', - ({ name, toolTipText, locked, lineNumber }) => { + ({ name, toolTipText, locked, loading, lineNumber }) => { const states = wrapper.findAll('[data-testid="terraform-states-table-name"]'); const state = states.at(lineNumber); @@ -111,6 +139,7 @@ describe('StatesTable', () => { expect(state.text()).toContain(name); expect(state.find(GlIcon).exists()).toBe(locked); + expect(state.find(GlLoadingIcon).exists()).toBe(loading); expect(toolTip.exists()).toBe(locked); if (locked) { @@ -154,6 +183,17 @@ describe('StatesTable', () => { expect(findActions().length).toEqual(0); }); + it.each` + errorMessage | lineNumber + ${defaultProps.states[0].errorMessages[0]} | ${0} + ${defaultProps.states[3].errorMessages[0]} | ${1} + `('displays table error message "$errorMessage"', ({ errorMessage, lineNumber }) => { + const states = wrapper.findAll('[data-testid="terraform-states-table-error"]'); + const state = states.at(lineNumber); + + expect(state.text()).toBe(errorMessage); + }); + describe('when user is a terraform administrator', () => { beforeEach(() => { return createComponent({ diff --git a/spec/frontend/terraform/components/terraform_list_spec.js b/spec/frontend/terraform/components/terraform_list_spec.js index fb56a7135a3..882b7b55b3e 100644 --- a/spec/frontend/terraform/components/terraform_list_spec.js +++ b/spec/frontend/terraform/components/terraform_list_spec.js @@ -1,7 +1,8 @@ import { GlAlert, GlBadge, GlKeysetPagination, GlLoadingIcon, GlTab } from '@gitlab/ui'; import { createLocalVue, shallowMount } from '@vue/test-utils'; -import createMockApollo from 'helpers/mock_apollo_helper'; import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; import EmptyState from '~/terraform/components/empty_state.vue'; import StatesTable from '~/terraform/components/states_table.vue'; import TerraformList from '~/terraform/components/terraform_list.vue'; @@ -27,8 +28,20 @@ describe('TerraformList', () => { }, }; + const mockResolvers = { + TerraformState: { + _showDetails: jest.fn().mockResolvedValue(false), + errorMessages: jest.fn().mockResolvedValue([]), + loadingLock: jest.fn().mockResolvedValue(false), + loadingRemove: jest.fn().mockResolvedValue(false), + }, + Mutation: { + addDataToTerraformState: jest.fn().mockResolvedValue({}), + }, + }; + const statsQueryResponse = queryResponse || jest.fn().mockResolvedValue(apolloQueryResponse); - const apolloProvider = createMockApollo([[getStatesQuery, statsQueryResponse]]); + const apolloProvider = createMockApollo([[getStatesQuery, statsQueryResponse]], mockResolvers); wrapper = shallowMount(TerraformList, { localVue, @@ -52,20 +65,28 @@ describe('TerraformList', () => { describe('when there is a list of terraform states', () => { const states = [ { + _showDetails: false, + errorMessages: [], id: 'gid://gitlab/Terraform::State/1', name: 'state-1', + latestVersion: null, + loadingLock: false, + loadingRemove: false, lockedAt: null, - updatedAt: null, lockedByUser: null, - latestVersion: null, + updatedAt: null, }, { + _showDetails: false, + errorMessages: [], id: 'gid://gitlab/Terraform::State/2', name: 'state-2', + latestVersion: null, + loadingLock: false, + loadingRemove: false, lockedAt: null, - updatedAt: null, lockedByUser: null, - latestVersion: null, + updatedAt: null, }, ]; @@ -83,7 +104,7 @@ describe('TerraformList', () => { }, }); - return wrapper.vm.$nextTick(); + return waitForPromises(); }); it('displays a states tab and count', () => { @@ -111,7 +132,7 @@ describe('TerraformList', () => { }, }); - return wrapper.vm.$nextTick(); + return waitForPromises(); }); it('renders the states table without pagination buttons', () => { @@ -131,7 +152,7 @@ describe('TerraformList', () => { }, }); - return wrapper.vm.$nextTick(); + return waitForPromises(); }); it('displays a states tab with no count', () => { @@ -149,7 +170,7 @@ describe('TerraformList', () => { beforeEach(() => { createWrapper({ terraformStates: null, queryResponse: jest.fn().mockRejectedValue() }); - return wrapper.vm.$nextTick(); + return waitForPromises(); }); it('displays an alert message', () => { |