2021-09-30 23:02:18 +05:30
|
|
|
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
|
|
|
import VueApollo from 'vue-apollo';
|
|
|
|
import createMockApollo from 'helpers/mock_apollo_helper';
|
2021-09-04 01:27:46 +05:30
|
|
|
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
|
|
|
import waitForPromises from 'helpers/wait_for_promises';
|
2021-09-30 23:02:18 +05:30
|
|
|
import createFlash from '~/flash';
|
2021-09-04 01:27:46 +05:30
|
|
|
import RunnerActionCell from '~/runner/components/cells/runner_actions_cell.vue';
|
|
|
|
import getRunnersQuery from '~/runner/graphql/get_runners.query.graphql';
|
2021-09-30 23:02:18 +05:30
|
|
|
import runnerDeleteMutation from '~/runner/graphql/runner_delete.mutation.graphql';
|
2021-09-04 01:27:46 +05:30
|
|
|
import runnerUpdateMutation from '~/runner/graphql/runner_update.mutation.graphql';
|
2021-09-30 23:02:18 +05:30
|
|
|
import { captureException } from '~/runner/sentry_utils';
|
|
|
|
import { runnerData } from '../../mock_data';
|
2021-09-04 01:27:46 +05:30
|
|
|
|
2021-09-30 23:02:18 +05:30
|
|
|
const mockRunner = runnerData.data.runner;
|
2021-09-04 01:27:46 +05:30
|
|
|
|
|
|
|
const getRunnersQueryName = getRunnersQuery.definitions[0].name.value;
|
|
|
|
|
2021-09-30 23:02:18 +05:30
|
|
|
const localVue = createLocalVue();
|
|
|
|
localVue.use(VueApollo);
|
|
|
|
|
|
|
|
jest.mock('~/flash');
|
|
|
|
jest.mock('~/runner/sentry_utils');
|
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
describe('RunnerTypeCell', () => {
|
|
|
|
let wrapper;
|
2021-09-30 23:02:18 +05:30
|
|
|
const runnerDeleteMutationHandler = jest.fn();
|
|
|
|
const runnerUpdateMutationHandler = jest.fn();
|
2021-09-04 01:27:46 +05:30
|
|
|
|
|
|
|
const findEditBtn = () => wrapper.findByTestId('edit-runner');
|
|
|
|
const findToggleActiveBtn = () => wrapper.findByTestId('toggle-active-runner');
|
|
|
|
const findDeleteBtn = () => wrapper.findByTestId('delete-runner');
|
|
|
|
|
|
|
|
const createComponent = ({ active = true } = {}, options) => {
|
|
|
|
wrapper = extendedWrapper(
|
|
|
|
shallowMount(RunnerActionCell, {
|
|
|
|
propsData: {
|
|
|
|
runner: {
|
2021-09-30 23:02:18 +05:30
|
|
|
id: mockRunner.id,
|
2021-09-04 01:27:46 +05:30
|
|
|
active,
|
|
|
|
},
|
|
|
|
},
|
2021-09-30 23:02:18 +05:30
|
|
|
localVue,
|
|
|
|
apolloProvider: createMockApollo([
|
|
|
|
[runnerDeleteMutation, runnerDeleteMutationHandler],
|
|
|
|
[runnerUpdateMutation, runnerUpdateMutationHandler],
|
|
|
|
]),
|
2021-09-04 01:27:46 +05:30
|
|
|
...options,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
beforeEach(() => {
|
2021-09-30 23:02:18 +05:30
|
|
|
runnerDeleteMutationHandler.mockResolvedValue({
|
|
|
|
data: {
|
|
|
|
runnerDelete: {
|
|
|
|
errors: [],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
runnerUpdateMutationHandler.mockResolvedValue({
|
|
|
|
data: {
|
|
|
|
runnerUpdate: {
|
|
|
|
runner: runnerData.data.runner,
|
|
|
|
errors: [],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
2021-09-30 23:02:18 +05:30
|
|
|
runnerDeleteMutationHandler.mockReset();
|
|
|
|
runnerUpdateMutationHandler.mockReset();
|
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
wrapper.destroy();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Displays the runner edit link with the correct href', () => {
|
|
|
|
createComponent();
|
|
|
|
|
|
|
|
expect(findEditBtn().attributes('href')).toBe('/admin/runners/1');
|
|
|
|
});
|
|
|
|
|
|
|
|
describe.each`
|
|
|
|
state | label | icon | isActive | newActiveValue
|
|
|
|
${'active'} | ${'Pause'} | ${'pause'} | ${true} | ${false}
|
|
|
|
${'paused'} | ${'Resume'} | ${'play'} | ${false} | ${true}
|
|
|
|
`('When the runner is $state', ({ label, icon, isActive, newActiveValue }) => {
|
|
|
|
beforeEach(() => {
|
|
|
|
createComponent({ active: isActive });
|
|
|
|
});
|
|
|
|
|
|
|
|
it(`Displays a ${icon} button`, () => {
|
|
|
|
expect(findToggleActiveBtn().props('loading')).toBe(false);
|
|
|
|
expect(findToggleActiveBtn().props('icon')).toBe(icon);
|
|
|
|
expect(findToggleActiveBtn().attributes('title')).toBe(label);
|
|
|
|
expect(findToggleActiveBtn().attributes('aria-label')).toBe(label);
|
|
|
|
});
|
|
|
|
|
|
|
|
it(`After clicking the ${icon} button, the button has a loading state`, async () => {
|
|
|
|
await findToggleActiveBtn().vm.$emit('click');
|
|
|
|
|
|
|
|
expect(findToggleActiveBtn().props('loading')).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it(`After the ${icon} button is clicked, stale tooltip is removed`, async () => {
|
|
|
|
await findToggleActiveBtn().vm.$emit('click');
|
|
|
|
|
|
|
|
expect(findToggleActiveBtn().attributes('title')).toBe('');
|
|
|
|
expect(findToggleActiveBtn().attributes('aria-label')).toBe('');
|
|
|
|
});
|
|
|
|
|
|
|
|
describe(`When clicking on the ${icon} button`, () => {
|
2021-09-30 23:02:18 +05:30
|
|
|
it(`The apollo mutation to set active to ${newActiveValue} is called`, async () => {
|
|
|
|
expect(runnerUpdateMutationHandler).toHaveBeenCalledTimes(0);
|
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
await findToggleActiveBtn().vm.$emit('click');
|
|
|
|
|
2021-09-30 23:02:18 +05:30
|
|
|
expect(runnerUpdateMutationHandler).toHaveBeenCalledTimes(1);
|
|
|
|
expect(runnerUpdateMutationHandler).toHaveBeenCalledWith({
|
|
|
|
input: {
|
|
|
|
id: mockRunner.id,
|
|
|
|
active: newActiveValue,
|
2021-09-04 01:27:46 +05:30
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2021-09-30 23:02:18 +05:30
|
|
|
it('The button does not have a loading state after the mutation occurs', async () => {
|
|
|
|
await findToggleActiveBtn().vm.$emit('click');
|
|
|
|
|
|
|
|
expect(findToggleActiveBtn().props('loading')).toBe(true);
|
|
|
|
|
|
|
|
await waitForPromises();
|
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
expect(findToggleActiveBtn().props('loading')).toBe(false);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2021-09-30 23:02:18 +05:30
|
|
|
describe('When update fails', () => {
|
|
|
|
describe('On a network error', () => {
|
|
|
|
const mockErrorMsg = 'Update error!';
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
runnerUpdateMutationHandler.mockRejectedValueOnce(new Error(mockErrorMsg));
|
|
|
|
|
|
|
|
await findToggleActiveBtn().vm.$emit('click');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('error is reported to sentry', () => {
|
|
|
|
expect(captureException).toHaveBeenCalledWith({
|
|
|
|
error: new Error(`Network error: ${mockErrorMsg}`),
|
|
|
|
component: 'RunnerActionsCell',
|
|
|
|
});
|
|
|
|
});
|
2021-09-04 01:27:46 +05:30
|
|
|
|
2021-09-30 23:02:18 +05:30
|
|
|
it('error is shown to the user', () => {
|
|
|
|
expect(createFlash).toHaveBeenCalledTimes(1);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('On a validation error', () => {
|
|
|
|
const mockErrorMsg = 'Runner not found!';
|
|
|
|
const mockErrorMsg2 = 'User not allowed!';
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
runnerUpdateMutationHandler.mockResolvedValue({
|
|
|
|
data: {
|
|
|
|
runnerUpdate: {
|
|
|
|
runner: runnerData.data.runner,
|
|
|
|
errors: [mockErrorMsg, mockErrorMsg2],
|
|
|
|
},
|
2021-09-04 01:27:46 +05:30
|
|
|
},
|
2021-09-30 23:02:18 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
await findToggleActiveBtn().vm.$emit('click');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('error is reported to sentry', () => {
|
|
|
|
expect(captureException).toHaveBeenCalledWith({
|
|
|
|
error: new Error(`${mockErrorMsg} ${mockErrorMsg2}`),
|
|
|
|
component: 'RunnerActionsCell',
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('error is shown to the user', () => {
|
|
|
|
expect(createFlash).toHaveBeenCalledTimes(1);
|
|
|
|
});
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
2021-09-30 23:02:18 +05:30
|
|
|
});
|
|
|
|
});
|
2021-09-04 01:27:46 +05:30
|
|
|
|
2021-09-30 23:02:18 +05:30
|
|
|
describe('When the user clicks a runner', () => {
|
|
|
|
beforeEach(() => {
|
2021-09-04 01:27:46 +05:30
|
|
|
jest.spyOn(window, 'confirm');
|
2021-09-30 23:02:18 +05:30
|
|
|
|
|
|
|
createComponent();
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
window.confirm.mockRestore();
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
describe('When the user confirms deletion', () => {
|
|
|
|
beforeEach(async () => {
|
|
|
|
window.confirm.mockReturnValue(true);
|
|
|
|
await findDeleteBtn().vm.$emit('click');
|
|
|
|
});
|
|
|
|
|
2021-09-30 23:02:18 +05:30
|
|
|
it('The user sees a confirmation alert', () => {
|
2021-09-04 01:27:46 +05:30
|
|
|
expect(window.confirm).toHaveBeenCalledTimes(1);
|
|
|
|
expect(window.confirm).toHaveBeenCalledWith(expect.any(String));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('The delete mutation is called correctly', () => {
|
2021-09-30 23:02:18 +05:30
|
|
|
expect(runnerDeleteMutationHandler).toHaveBeenCalledTimes(1);
|
|
|
|
expect(runnerDeleteMutationHandler).toHaveBeenCalledWith({
|
|
|
|
input: { id: mockRunner.id },
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('When delete mutation is called, current runners are refetched', async () => {
|
|
|
|
jest.spyOn(wrapper.vm.$apollo, 'mutate');
|
|
|
|
|
|
|
|
await findDeleteBtn().vm.$emit('click');
|
|
|
|
|
|
|
|
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
|
|
|
|
mutation: runnerDeleteMutation,
|
2021-09-04 01:27:46 +05:30
|
|
|
variables: {
|
|
|
|
input: {
|
2021-09-30 23:02:18 +05:30
|
|
|
id: mockRunner.id,
|
2021-09-04 01:27:46 +05:30
|
|
|
},
|
|
|
|
},
|
|
|
|
awaitRefetchQueries: true,
|
|
|
|
refetchQueries: [getRunnersQueryName],
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('The delete button does not have a loading state', () => {
|
|
|
|
expect(findDeleteBtn().props('loading')).toBe(false);
|
|
|
|
expect(findDeleteBtn().attributes('title')).toBe('Remove');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('After the delete button is clicked, loading state is shown', async () => {
|
|
|
|
await findDeleteBtn().vm.$emit('click');
|
|
|
|
|
|
|
|
expect(findDeleteBtn().props('loading')).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('After the delete button is clicked, stale tooltip is removed', async () => {
|
|
|
|
await findDeleteBtn().vm.$emit('click');
|
|
|
|
|
|
|
|
expect(findDeleteBtn().attributes('title')).toBe('');
|
|
|
|
});
|
2021-09-30 23:02:18 +05:30
|
|
|
|
|
|
|
describe('When delete fails', () => {
|
|
|
|
describe('On a network error', () => {
|
|
|
|
const mockErrorMsg = 'Delete error!';
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
runnerDeleteMutationHandler.mockRejectedValueOnce(new Error(mockErrorMsg));
|
|
|
|
|
|
|
|
await findDeleteBtn().vm.$emit('click');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('error is reported to sentry', () => {
|
|
|
|
expect(captureException).toHaveBeenCalledWith({
|
|
|
|
error: new Error(`Network error: ${mockErrorMsg}`),
|
|
|
|
component: 'RunnerActionsCell',
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('error is shown to the user', () => {
|
|
|
|
expect(createFlash).toHaveBeenCalledTimes(1);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('On a validation error', () => {
|
|
|
|
const mockErrorMsg = 'Runner not found!';
|
|
|
|
const mockErrorMsg2 = 'User not allowed!';
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
runnerDeleteMutationHandler.mockResolvedValue({
|
|
|
|
data: {
|
|
|
|
runnerDelete: {
|
|
|
|
errors: [mockErrorMsg, mockErrorMsg2],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
await findDeleteBtn().vm.$emit('click');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('error is reported to sentry', () => {
|
|
|
|
expect(captureException).toHaveBeenCalledWith({
|
|
|
|
error: new Error(`${mockErrorMsg} ${mockErrorMsg2}`),
|
|
|
|
component: 'RunnerActionsCell',
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('error is shown to the user', () => {
|
|
|
|
expect(createFlash).toHaveBeenCalledTimes(1);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
describe('When the user does not confirm deletion', () => {
|
|
|
|
beforeEach(async () => {
|
|
|
|
window.confirm.mockReturnValue(false);
|
|
|
|
await findDeleteBtn().vm.$emit('click');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('The user sees a confirmation alert', () => {
|
|
|
|
expect(window.confirm).toHaveBeenCalledTimes(1);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('The delete mutation is not called', () => {
|
2021-09-30 23:02:18 +05:30
|
|
|
expect(runnerDeleteMutationHandler).toHaveBeenCalledTimes(0);
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('The delete button does not have a loading state', () => {
|
|
|
|
expect(findDeleteBtn().props('loading')).toBe(false);
|
|
|
|
expect(findDeleteBtn().attributes('title')).toBe('Remove');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|