Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/runner/components')
-rw-r--r--spec/frontend/runner/components/cells/runner_summary_cell_spec.js13
-rw-r--r--spec/frontend/runner/components/registration/registration_token_spec.js27
-rw-r--r--spec/frontend/runner/components/runner_assigned_item_spec.js16
-rw-r--r--spec/frontend/runner/components/runner_bulk_delete_checkbox_spec.js101
-rw-r--r--spec/frontend/runner/components/runner_bulk_delete_spec.js176
-rw-r--r--spec/frontend/runner/components/runner_filtered_search_bar_spec.js6
-rw-r--r--spec/frontend/runner/components/runner_jobs_spec.js3
-rw-r--r--spec/frontend/runner/components/runner_list_spec.js8
-rw-r--r--spec/frontend/runner/components/runner_pagination_spec.js138
-rw-r--r--spec/frontend/runner/components/runner_projects_spec.js1
-rw-r--r--spec/frontend/runner/components/stat/runner_count_spec.js4
-rw-r--r--spec/frontend/runner/components/stat/runner_single_stat_spec.js61
-rw-r--r--spec/frontend/runner/components/stat/runner_stats_spec.js37
-rw-r--r--spec/frontend/runner/components/stat/runner_status_stat_spec.js67
14 files changed, 423 insertions, 235 deletions
diff --git a/spec/frontend/runner/components/cells/runner_summary_cell_spec.js b/spec/frontend/runner/components/cells/runner_summary_cell_spec.js
index b2e8c5a3ad9..b06ab652212 100644
--- a/spec/frontend/runner/components/cells/runner_summary_cell_spec.js
+++ b/spec/frontend/runner/components/cells/runner_summary_cell_spec.js
@@ -1,3 +1,4 @@
+import { __ } from '~/locale';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import RunnerSummaryCell from '~/runner/components/cells/runner_summary_cell.vue';
import { INSTANCE_TYPE, PROJECT_TYPE } from '~/runner/constants';
@@ -61,8 +62,16 @@ describe('RunnerTypeCell', () => {
expect(wrapper.text()).toContain(mockDescription);
});
- it('Displays the runner ip address', () => {
- expect(wrapper.text()).toContain(mockIpAddress);
+ it('Displays ip address', () => {
+ expect(wrapper.text()).toContain(`${__('IP Address')} ${mockIpAddress}`);
+ });
+
+ it('Displays no ip address', () => {
+ createComponent({
+ ipAddress: null,
+ });
+
+ expect(wrapper.text()).not.toContain(__('IP Address'));
});
it('Displays a custom slot', () => {
diff --git a/spec/frontend/runner/components/registration/registration_token_spec.js b/spec/frontend/runner/components/registration/registration_token_spec.js
index ed1a698d36f..19344a68f79 100644
--- a/spec/frontend/runner/components/registration/registration_token_spec.js
+++ b/spec/frontend/runner/components/registration/registration_token_spec.js
@@ -1,5 +1,5 @@
import { GlToast } from '@gitlab/ui';
-import { createLocalVue } from '@vue/test-utils';
+import Vue from 'vue';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import RegistrationToken from '~/runner/components/registration/registration_token.vue';
import InputCopyToggleVisibility from '~/vue_shared/components/form/input_copy_toggle_visibility.vue';
@@ -11,28 +11,17 @@ describe('RegistrationToken', () => {
let wrapper;
let showToast;
- const findInputCopyToggleVisibility = () => wrapper.findComponent(InputCopyToggleVisibility);
-
- const vueWithGlToast = () => {
- const localVue = createLocalVue();
- localVue.use(GlToast);
- return localVue;
- };
+ Vue.use(GlToast);
- const createComponent = ({
- props = {},
- withGlToast = true,
- mountFn = shallowMountExtended,
- } = {}) => {
- const localVue = withGlToast ? vueWithGlToast() : undefined;
+ const findInputCopyToggleVisibility = () => wrapper.findComponent(InputCopyToggleVisibility);
+ const createComponent = ({ props = {}, mountFn = shallowMountExtended } = {}) => {
wrapper = mountFn(RegistrationToken, {
propsData: {
value: mockToken,
inputId: 'token-value',
...props,
},
- localVue,
});
showToast = wrapper.vm.$toast ? jest.spyOn(wrapper.vm.$toast, 'show') : null;
@@ -69,13 +58,5 @@ describe('RegistrationToken', () => {
expect(showToast).toHaveBeenCalledTimes(1);
expect(showToast).toHaveBeenCalledWith('Registration token copied!');
});
-
- it('does not fail when toast is not defined', () => {
- createComponent({ withGlToast: false });
- findInputCopyToggleVisibility().vm.$emit('copy');
-
- // This block also tests for unhandled errors
- expect(showToast).toBeNull();
- });
});
});
diff --git a/spec/frontend/runner/components/runner_assigned_item_spec.js b/spec/frontend/runner/components/runner_assigned_item_spec.js
index 1ff6983fbe7..cc09046c000 100644
--- a/spec/frontend/runner/components/runner_assigned_item_spec.js
+++ b/spec/frontend/runner/components/runner_assigned_item_spec.js
@@ -1,10 +1,12 @@
-import { GlAvatar } from '@gitlab/ui';
+import { GlAvatar, GlBadge } from '@gitlab/ui';
+import { s__ } from '~/locale';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import RunnerAssignedItem from '~/runner/components/runner_assigned_item.vue';
import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
const mockHref = '/group/project';
const mockName = 'Project';
+const mockDescription = 'Project description';
const mockFullName = 'Group / Project';
const mockAvatarUrl = '/avatar.png';
@@ -12,6 +14,7 @@ describe('RunnerAssignedItem', () => {
let wrapper;
const findAvatar = () => wrapper.findByTestId('item-avatar');
+ const findBadge = () => wrapper.findComponent(GlBadge);
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMountExtended(RunnerAssignedItem, {
@@ -20,6 +23,7 @@ describe('RunnerAssignedItem', () => {
name: mockName,
fullName: mockFullName,
avatarUrl: mockAvatarUrl,
+ description: mockDescription,
...props,
},
});
@@ -51,4 +55,14 @@ describe('RunnerAssignedItem', () => {
expect(groupFullName.attributes('href')).toBe(mockHref);
});
+
+ it('Shows description', () => {
+ expect(wrapper.text()).toContain(mockDescription);
+ });
+
+ it('Shows owner badge', () => {
+ createComponent({ props: { isOwner: true } });
+
+ expect(findBadge().text()).toBe(s__('Runner|Owner'));
+ });
});
diff --git a/spec/frontend/runner/components/runner_bulk_delete_checkbox_spec.js b/spec/frontend/runner/components/runner_bulk_delete_checkbox_spec.js
new file mode 100644
index 00000000000..0ac89e82314
--- /dev/null
+++ b/spec/frontend/runner/components/runner_bulk_delete_checkbox_spec.js
@@ -0,0 +1,101 @@
+import Vue from 'vue';
+import { GlFormCheckbox } from '@gitlab/ui';
+import VueApollo from 'vue-apollo';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import RunnerBulkDeleteCheckbox from '~/runner/components/runner_bulk_delete_checkbox.vue';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { createLocalState } from '~/runner/graphql/list/local_state';
+import { allRunnersData } from '../mock_data';
+
+Vue.use(VueApollo);
+
+jest.mock('~/flash');
+
+describe('RunnerBulkDeleteCheckbox', () => {
+ let wrapper;
+ let mockState;
+ let mockCheckedRunnerIds;
+
+ const findCheckbox = () => wrapper.findComponent(GlFormCheckbox);
+
+ const mockRunners = allRunnersData.data.runners.nodes;
+ const mockIds = allRunnersData.data.runners.nodes.map(({ id }) => id);
+ const mockId = mockIds[0];
+ const mockIdAnotherPage = 'RUNNER_IN_ANOTHER_PAGE_ID';
+
+ const createComponent = ({ props = {} } = {}) => {
+ const { cacheConfig, localMutations } = mockState;
+ const apolloProvider = createMockApollo(undefined, undefined, cacheConfig);
+
+ wrapper = shallowMountExtended(RunnerBulkDeleteCheckbox, {
+ apolloProvider,
+ provide: {
+ localMutations,
+ },
+ propsData: {
+ runners: mockRunners,
+ ...props,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ mockState = createLocalState();
+
+ jest
+ .spyOn(mockState.cacheConfig.typePolicies.Query.fields, 'checkedRunnerIds')
+ .mockImplementation(() => mockCheckedRunnerIds);
+
+ jest.spyOn(mockState.localMutations, 'setRunnersChecked');
+ });
+
+ describe.each`
+ case | is | checkedRunnerIds | disabled | checked | indeterminate
+ ${'no runners'} | ${'unchecked'} | ${[]} | ${undefined} | ${undefined} | ${undefined}
+ ${'no runners in this page'} | ${'unchecked'} | ${[mockIdAnotherPage]} | ${undefined} | ${undefined} | ${undefined}
+ ${'all runners'} | ${'checked'} | ${mockIds} | ${undefined} | ${'true'} | ${undefined}
+ ${'some runners'} | ${'indeterminate'} | ${[mockId]} | ${undefined} | ${undefined} | ${'true'}
+ ${'all plus other runners'} | ${'checked'} | ${[...mockIds, mockIdAnotherPage]} | ${undefined} | ${'true'} | ${undefined}
+ `('When $case are checked', ({ is, checkedRunnerIds, disabled, checked, indeterminate }) => {
+ beforeEach(async () => {
+ mockCheckedRunnerIds = checkedRunnerIds;
+
+ createComponent();
+ });
+
+ it(`is ${is}`, () => {
+ expect(findCheckbox().attributes('disabled')).toBe(disabled);
+ expect(findCheckbox().attributes('checked')).toBe(checked);
+ expect(findCheckbox().attributes('indeterminate')).toBe(indeterminate);
+ });
+ });
+
+ describe('When user selects', () => {
+ beforeEach(() => {
+ mockCheckedRunnerIds = mockIds;
+ createComponent();
+ });
+
+ it.each([[true], [false]])('sets checked to %s', (checked) => {
+ findCheckbox().vm.$emit('change', checked);
+
+ expect(mockState.localMutations.setRunnersChecked).toHaveBeenCalledTimes(1);
+ expect(mockState.localMutations.setRunnersChecked).toHaveBeenCalledWith({
+ isChecked: checked,
+ runners: mockRunners,
+ });
+ });
+ });
+
+ describe('When runners are loading', () => {
+ beforeEach(() => {
+ createComponent({ props: { runners: [] } });
+ });
+
+ it(`is disabled`, () => {
+ expect(findCheckbox().attributes('disabled')).toBe('true');
+ expect(findCheckbox().attributes('checked')).toBe(undefined);
+ expect(findCheckbox().attributes('indeterminate')).toBe(undefined);
+ });
+ });
+});
diff --git a/spec/frontend/runner/components/runner_bulk_delete_spec.js b/spec/frontend/runner/components/runner_bulk_delete_spec.js
index f5b56396cf1..6df918c684f 100644
--- a/spec/frontend/runner/components/runner_bulk_delete_spec.js
+++ b/spec/frontend/runner/components/runner_bulk_delete_spec.js
@@ -1,37 +1,65 @@
import Vue from 'vue';
-import { GlSprintf } from '@gitlab/ui';
+import { GlModal, GlSprintf } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
+import { createAlert } from '~/flash';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
+import { s__ } from '~/locale';
import RunnerBulkDelete from '~/runner/components/runner_bulk_delete.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
+import BulkRunnerDeleteMutation from '~/runner/graphql/list/bulk_runner_delete.mutation.graphql';
import { createLocalState } from '~/runner/graphql/list/local_state';
import waitForPromises from 'helpers/wait_for_promises';
+import { allRunnersData } from '../mock_data';
Vue.use(VueApollo);
-jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal');
+jest.mock('~/flash');
describe('RunnerBulkDelete', () => {
let wrapper;
+ let apolloCache;
let mockState;
let mockCheckedRunnerIds;
- const findClearBtn = () => wrapper.findByTestId('clear-btn');
- const findDeleteBtn = () => wrapper.findByTestId('delete-btn');
+ const findClearBtn = () => wrapper.findByText(s__('Runners|Clear selection'));
+ const findDeleteBtn = () => wrapper.findByText(s__('Runners|Delete selected'));
+ const findModal = () => wrapper.findComponent(GlModal);
+
+ const mockRunners = allRunnersData.data.runners.nodes;
+ const mockId1 = allRunnersData.data.runners.nodes[0].id;
+ const mockId2 = allRunnersData.data.runners.nodes[1].id;
+
+ const bulkRunnerDeleteHandler = jest.fn();
const createComponent = () => {
const { cacheConfig, localMutations } = mockState;
+ const apolloProvider = createMockApollo(
+ [[BulkRunnerDeleteMutation, bulkRunnerDeleteHandler]],
+ undefined,
+ cacheConfig,
+ );
wrapper = shallowMountExtended(RunnerBulkDelete, {
- apolloProvider: createMockApollo(undefined, undefined, cacheConfig),
+ apolloProvider,
provide: {
localMutations,
},
+ propsData: {
+ runners: mockRunners,
+ },
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
stubs: {
GlSprintf,
+ GlModal,
},
});
+
+ apolloCache = apolloProvider.defaultClient.cache;
+ jest.spyOn(apolloCache, 'evict');
+ jest.spyOn(apolloCache, 'gc');
};
beforeEach(() => {
@@ -43,6 +71,7 @@ describe('RunnerBulkDelete', () => {
});
afterEach(() => {
+ bulkRunnerDeleteHandler.mockReset();
wrapper.destroy();
});
@@ -61,10 +90,10 @@ describe('RunnerBulkDelete', () => {
});
describe.each`
- count | ids | text
- ${1} | ${['gid:Runner/1']} | ${'1 runner'}
- ${2} | ${['gid:Runner/1', 'gid:Runner/2']} | ${'2 runners'}
- `('When $count runner(s) are checked', ({ count, ids, text }) => {
+ count | ids | text
+ ${1} | ${[mockId1]} | ${'1 runner'}
+ ${2} | ${[mockId1, mockId2]} | ${'2 runners'}
+ `('When $count runner(s) are checked', ({ ids, text }) => {
beforeEach(() => {
mockCheckedRunnerIds = ids;
@@ -86,18 +115,129 @@ describe('RunnerBulkDelete', () => {
});
it('shows confirmation modal', () => {
- expect(confirmAction).toHaveBeenCalledTimes(0);
+ const modalId = getBinding(findDeleteBtn().element, 'gl-modal');
+
+ expect(findModal().props('modal-id')).toBe(modalId);
+ expect(findModal().text()).toContain(text);
+ });
+ });
+
+ describe('when runners are deleted', () => {
+ let evt;
+ let mockHideModal;
+
+ beforeEach(() => {
+ mockCheckedRunnerIds = [mockId1, mockId2];
+
+ createComponent();
+
+ jest.spyOn(mockState.localMutations, 'clearChecked').mockImplementation(() => {});
+ mockHideModal = jest.spyOn(findModal().vm, 'hide');
+ });
+
+ describe('when deletion is successful', () => {
+ beforeEach(() => {
+ bulkRunnerDeleteHandler.mockResolvedValue({
+ data: {
+ bulkRunnerDelete: { deletedIds: mockCheckedRunnerIds, errors: [] },
+ },
+ });
+
+ evt = {
+ preventDefault: jest.fn(),
+ };
+ findModal().vm.$emit('primary', evt);
+ });
+
+ it('has loading state', async () => {
+ expect(findModal().props('actionPrimary').attributes.loading).toBe(true);
+ expect(findModal().props('actionCancel').attributes.loading).toBe(true);
+
+ await waitForPromises();
+
+ expect(findModal().props('actionPrimary').attributes.loading).toBe(false);
+ expect(findModal().props('actionCancel').attributes.loading).toBe(false);
+ });
+
+ it('modal is not prevented from closing', () => {
+ expect(evt.preventDefault).toHaveBeenCalledTimes(1);
+ });
+
+ it('mutation is called', async () => {
+ expect(bulkRunnerDeleteHandler).toHaveBeenCalledWith({
+ input: { ids: mockCheckedRunnerIds },
+ });
+ });
+
+ it('user interface is updated', async () => {
+ const { evict, gc } = apolloCache;
+
+ expect(evict).toHaveBeenCalledTimes(mockCheckedRunnerIds.length);
+ expect(evict).toHaveBeenCalledWith({
+ id: expect.stringContaining(mockCheckedRunnerIds[0]),
+ });
+ expect(evict).toHaveBeenCalledWith({
+ id: expect.stringContaining(mockCheckedRunnerIds[1]),
+ });
+
+ expect(gc).toHaveBeenCalledTimes(1);
+ });
+
+ it('modal is hidden', () => {
+ expect(mockHideModal).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('when deletion fails', () => {
+ beforeEach(() => {
+ bulkRunnerDeleteHandler.mockRejectedValue(new Error('error!'));
+
+ evt = {
+ preventDefault: jest.fn(),
+ };
+ findModal().vm.$emit('primary', evt);
+ });
+
+ it('has loading state', async () => {
+ expect(findModal().props('actionPrimary').attributes.loading).toBe(true);
+ expect(findModal().props('actionCancel').attributes.loading).toBe(true);
+
+ await waitForPromises();
+
+ expect(findModal().props('actionPrimary').attributes.loading).toBe(false);
+ expect(findModal().props('actionCancel').attributes.loading).toBe(false);
+ });
+
+ it('modal is not prevented from closing', () => {
+ expect(evt.preventDefault).toHaveBeenCalledTimes(1);
+ });
+
+ it('mutation is called', () => {
+ expect(bulkRunnerDeleteHandler).toHaveBeenCalledWith({
+ input: { ids: mockCheckedRunnerIds },
+ });
+ });
+
+ it('user interface is not updated', async () => {
+ await waitForPromises();
- findDeleteBtn().vm.$emit('click');
+ const { evict, gc } = apolloCache;
- expect(confirmAction).toHaveBeenCalledTimes(1);
+ expect(evict).not.toHaveBeenCalled();
+ expect(gc).not.toHaveBeenCalled();
+ expect(mockState.localMutations.clearChecked).not.toHaveBeenCalled();
+ });
- const [, confirmOptions] = confirmAction.mock.calls[0];
- const { title, modalHtmlMessage, primaryBtnText } = confirmOptions;
+ it('alert is called', async () => {
+ await waitForPromises();
- expect(title).toMatch(text);
- expect(primaryBtnText).toMatch(text);
- expect(modalHtmlMessage).toMatch(`<strong>${count}</strong>`);
+ expect(createAlert).toHaveBeenCalled();
+ expect(createAlert).toHaveBeenCalledWith({
+ message: expect.any(String),
+ captureError: true,
+ error: expect.any(Error),
+ });
+ });
});
});
});
diff --git a/spec/frontend/runner/components/runner_filtered_search_bar_spec.js b/spec/frontend/runner/components/runner_filtered_search_bar_spec.js
index 83fb1764c6d..e35bec3aa38 100644
--- a/spec/frontend/runner/components/runner_filtered_search_bar_spec.js
+++ b/spec/frontend/runner/components/runner_filtered_search_bar_spec.js
@@ -143,7 +143,7 @@ describe('RunnerList', () => {
runnerType: INSTANCE_TYPE,
filters: mockFilters,
sort: mockOtherSort,
- pagination: { page: 1 },
+ pagination: {},
});
});
});
@@ -156,7 +156,7 @@ describe('RunnerList', () => {
runnerType: null,
filters: mockFilters,
sort: mockDefaultSort,
- pagination: { page: 1 },
+ pagination: {},
});
});
@@ -167,7 +167,7 @@ describe('RunnerList', () => {
runnerType: null,
filters: [],
sort: mockOtherSort,
- pagination: { page: 1 },
+ pagination: {},
});
});
});
diff --git a/spec/frontend/runner/components/runner_jobs_spec.js b/spec/frontend/runner/components/runner_jobs_spec.js
index 20582aaaf40..4d38afb25ee 100644
--- a/spec/frontend/runner/components/runner_jobs_spec.js
+++ b/spec/frontend/runner/components/runner_jobs_spec.js
@@ -73,8 +73,7 @@ describe('RunnerJobs', () => {
it('Shows jobs', () => {
const jobs = findRunnerJobsTable().props('jobs');
- expect(jobs).toHaveLength(mockJobs.length);
- expect(jobs[0]).toMatchObject(mockJobs[0]);
+ expect(jobs).toEqual(mockJobs);
});
describe('When "Next" page is clicked', () => {
diff --git a/spec/frontend/runner/components/runner_list_spec.js b/spec/frontend/runner/components/runner_list_spec.js
index eca4bbc3490..7b58a81bb0d 100644
--- a/spec/frontend/runner/components/runner_list_spec.js
+++ b/spec/frontend/runner/components/runner_list_spec.js
@@ -88,9 +88,7 @@ describe('RunnerList', () => {
createComponent({}, mountExtended);
// Badges
- expect(findCell({ fieldKey: 'status' }).text()).toMatchInterpolatedText(
- 'never contacted paused',
- );
+ expect(findCell({ fieldKey: 'status' }).text()).toMatchInterpolatedText('never contacted');
// Runner summary
expect(findCell({ fieldKey: 'summary' }).text()).toContain(
@@ -124,10 +122,10 @@ describe('RunnerList', () => {
expect(findCell({ fieldKey: 'checkbox' }).find('input').exists()).toBe(true);
});
- it('Emits a checked event', () => {
+ it('Emits a checked event', async () => {
const checkbox = findCell({ fieldKey: 'checkbox' }).find('input');
- checkbox.setChecked();
+ await checkbox.setChecked();
expect(wrapper.emitted('checked')).toHaveLength(1);
expect(wrapper.emitted('checked')[0][0]).toEqual({
diff --git a/spec/frontend/runner/components/runner_pagination_spec.js b/spec/frontend/runner/components/runner_pagination_spec.js
index e144b52ceb3..499cc59250d 100644
--- a/spec/frontend/runner/components/runner_pagination_spec.js
+++ b/spec/frontend/runner/components/runner_pagination_spec.js
@@ -1,5 +1,5 @@
-import { GlPagination } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
+import { GlKeysetPagination } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import RunnerPagination from '~/runner/components/runner_pagination.vue';
const mockStartCursor = 'START_CURSOR';
@@ -8,21 +8,11 @@ const mockEndCursor = 'END_CURSOR';
describe('RunnerPagination', () => {
let wrapper;
- const findPagination = () => wrapper.findComponent(GlPagination);
+ const findPagination = () => wrapper.findComponent(GlKeysetPagination);
- const createComponent = ({ page = 1, hasPreviousPage = false, hasNextPage = true } = {}) => {
- wrapper = mount(RunnerPagination, {
- propsData: {
- value: {
- page,
- },
- pageInfo: {
- hasPreviousPage,
- hasNextPage,
- startCursor: mockStartCursor,
- endCursor: mockEndCursor,
- },
- },
+ const createComponent = (propsData = {}) => {
+ wrapper = shallowMount(RunnerPagination, {
+ propsData,
});
};
@@ -30,114 +20,96 @@ describe('RunnerPagination', () => {
wrapper.destroy();
});
- describe('When on the first page', () => {
- beforeEach(() => {
- createComponent({
- page: 1,
- hasPreviousPage: false,
- hasNextPage: true,
- });
- });
-
- it('Contains the current page information', () => {
- expect(findPagination().props('value')).toBe(1);
- expect(findPagination().props('prevPage')).toBe(null);
- expect(findPagination().props('nextPage')).toBe(2);
- });
-
- it('Goes to the second page', () => {
- findPagination().vm.$emit('input', 2);
-
- expect(wrapper.emitted('input')[0]).toEqual([
- {
- after: mockEndCursor,
- page: 2,
- },
- ]);
- });
- });
-
describe('When in between pages', () => {
+ const mockPageInfo = {
+ startCursor: mockStartCursor,
+ endCursor: mockEndCursor,
+ hasPreviousPage: true,
+ hasNextPage: true,
+ };
+
beforeEach(() => {
createComponent({
- page: 2,
- hasPreviousPage: true,
- hasNextPage: true,
+ pageInfo: mockPageInfo,
});
});
it('Contains the current page information', () => {
- expect(findPagination().props('value')).toBe(2);
- expect(findPagination().props('prevPage')).toBe(1);
- expect(findPagination().props('nextPage')).toBe(3);
+ expect(findPagination().props()).toMatchObject(mockPageInfo);
});
- it('Shows the next and previous pages', () => {
- const links = findPagination().findAll('a');
-
- expect(links).toHaveLength(2);
- expect(links.at(0).text()).toBe('Previous');
- expect(links.at(1).text()).toBe('Next');
- });
-
- it('Goes to the last page', () => {
- findPagination().vm.$emit('input', 3);
+ it('Goes to the prev page', () => {
+ findPagination().vm.$emit('prev');
expect(wrapper.emitted('input')[0]).toEqual([
{
- after: mockEndCursor,
- page: 3,
+ before: mockStartCursor,
},
]);
});
- it('Goes to the first page', () => {
- findPagination().vm.$emit('input', 1);
+ it('Goes to the next page', () => {
+ findPagination().vm.$emit('next');
expect(wrapper.emitted('input')[0]).toEqual([
{
- page: 1,
+ after: mockEndCursor,
},
]);
});
});
- describe('When in the last page', () => {
+ describe.each`
+ page | hasPreviousPage | hasNextPage
+ ${'first'} | ${false} | ${true}
+ ${'last'} | ${true} | ${false}
+ `('When on the $page page', ({ page, hasPreviousPage, hasNextPage }) => {
+ const mockPageInfo = {
+ startCursor: mockStartCursor,
+ endCursor: mockEndCursor,
+ hasPreviousPage,
+ hasNextPage,
+ };
+
beforeEach(() => {
createComponent({
- page: 3,
- hasPreviousPage: true,
- hasNextPage: false,
+ pageInfo: mockPageInfo,
});
});
- it('Contains the current page', () => {
- expect(findPagination().props('value')).toBe(3);
- expect(findPagination().props('prevPage')).toBe(2);
- expect(findPagination().props('nextPage')).toBe(null);
+ it(`Contains the ${page} page information`, () => {
+ expect(findPagination().props()).toMatchObject(mockPageInfo);
});
});
- describe('When only one page', () => {
+ describe('When no other pages', () => {
beforeEach(() => {
createComponent({
- page: 1,
- hasPreviousPage: false,
- hasNextPage: false,
+ pageInfo: {
+ hasPreviousPage: false,
+ hasNextPage: false,
+ },
});
});
- it('does not display pagination', () => {
- expect(wrapper.html()).toBe('');
+ it('is not shown', () => {
+ expect(findPagination().exists()).toBe(false);
});
+ });
- it('Contains the current page', () => {
- expect(findPagination().props('value')).toBe(1);
+ describe('When adding more attributes', () => {
+ beforeEach(() => {
+ createComponent({
+ pageInfo: {
+ hasPreviousPage: true,
+ hasNextPage: false,
+ },
+ disabled: true,
+ });
});
- it('Shows no more page buttons', () => {
- expect(findPagination().props('prevPage')).toBe(null);
- expect(findPagination().props('nextPage')).toBe(null);
+ it('attributes are passed', () => {
+ expect(findPagination().props('disabled')).toBe(true);
});
});
});
diff --git a/spec/frontend/runner/components/runner_projects_spec.js b/spec/frontend/runner/components/runner_projects_spec.js
index 6932b3b5197..c988fb8477d 100644
--- a/spec/frontend/runner/components/runner_projects_spec.js
+++ b/spec/frontend/runner/components/runner_projects_spec.js
@@ -95,6 +95,7 @@ describe('RunnerProjects', () => {
name,
fullName: nameWithNamespace,
avatarUrl,
+ isOwner: true, // first project is always owner
});
});
diff --git a/spec/frontend/runner/components/stat/runner_count_spec.js b/spec/frontend/runner/components/stat/runner_count_spec.js
index 89b51b1b4a7..2a6a745099f 100644
--- a/spec/frontend/runner/components/stat/runner_count_spec.js
+++ b/spec/frontend/runner/components/stat/runner_count_spec.js
@@ -7,8 +7,8 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { captureException } from '~/runner/sentry_utils';
-import allRunnersCountQuery from '~/runner/graphql/list/all_runners_count.query.graphql';
-import groupRunnersCountQuery from '~/runner/graphql/list/group_runners_count.query.graphql';
+import allRunnersCountQuery from 'ee_else_ce/runner/graphql/list/all_runners_count.query.graphql';
+import groupRunnersCountQuery from 'ee_else_ce/runner/graphql/list/group_runners_count.query.graphql';
import { runnersCountData, groupRunnersCountData } from '../../mock_data';
diff --git a/spec/frontend/runner/components/stat/runner_single_stat_spec.js b/spec/frontend/runner/components/stat/runner_single_stat_spec.js
new file mode 100644
index 00000000000..964a6a6ff71
--- /dev/null
+++ b/spec/frontend/runner/components/stat/runner_single_stat_spec.js
@@ -0,0 +1,61 @@
+import { GlSingleStat } from '@gitlab/ui/dist/charts';
+import { shallowMount } from '@vue/test-utils';
+import RunnerSingleStat from '~/runner/components/stat/runner_single_stat.vue';
+import RunnerCount from '~/runner/components/stat/runner_count.vue';
+import { INSTANCE_TYPE, GROUP_TYPE } from '~/runner/constants';
+
+describe('RunnerStats', () => {
+ let wrapper;
+
+ const findRunnerCount = () => wrapper.findComponent(RunnerCount);
+ const findGlSingleStat = () => wrapper.findComponent(GlSingleStat);
+
+ const createComponent = ({ props = {}, count, mountFn = shallowMount, ...options } = {}) => {
+ wrapper = mountFn(RunnerSingleStat, {
+ propsData: {
+ scope: INSTANCE_TYPE,
+ title: 'My title',
+ variables: {},
+ ...props,
+ },
+ stubs: {
+ RunnerCount: {
+ props: ['scope', 'variables', 'skip'],
+ render() {
+ return this.$scopedSlots.default({
+ count,
+ });
+ },
+ },
+ },
+ ...options,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it.each`
+ case | count | value
+ ${'number'} | ${99} | ${'99'}
+ ${'long number'} | ${1000} | ${'1,000'}
+ ${'empty number'} | ${null} | ${'-'}
+ `('formats $case', ({ count, value }) => {
+ createComponent({ count });
+
+ expect(findGlSingleStat().props('value')).toBe(value);
+ });
+
+ it('Passes runner count props', () => {
+ const props = {
+ scope: GROUP_TYPE,
+ variables: { paused: true },
+ skip: true,
+ };
+
+ createComponent({ props });
+
+ expect(findRunnerCount().props()).toEqual(props);
+ });
+});
diff --git a/spec/frontend/runner/components/stat/runner_stats_spec.js b/spec/frontend/runner/components/stat/runner_stats_spec.js
index f1ba6403dfb..7f1f22be94f 100644
--- a/spec/frontend/runner/components/stat/runner_stats_spec.js
+++ b/spec/frontend/runner/components/stat/runner_stats_spec.js
@@ -1,15 +1,13 @@
import { shallowMount, mount } from '@vue/test-utils';
import { s__ } from '~/locale';
import RunnerStats from '~/runner/components/stat/runner_stats.vue';
-import RunnerCount from '~/runner/components/stat/runner_count.vue';
-import RunnerStatusStat from '~/runner/components/stat/runner_status_stat.vue';
+import RunnerSingleStat from '~/runner/components/stat/runner_single_stat.vue';
import { INSTANCE_TYPE, STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '~/runner/constants';
describe('RunnerStats', () => {
let wrapper;
- const findRunnerCountAt = (i) => wrapper.findAllComponents(RunnerCount).at(i);
- const findRunnerStatusStatAt = (i) => wrapper.findAllComponents(RunnerStatusStat).at(i);
+ const findSingleStats = () => wrapper.findAllComponents(RunnerSingleStat).wrappers;
const createComponent = ({ props = {}, mountFn = shallowMount, ...options } = {}) => {
wrapper = mountFn(RunnerStats, {
@@ -53,31 +51,12 @@ describe('RunnerStats', () => {
expect(text).toMatch(`${s__('Runners|Stale runners')} 1`);
});
- it('Displays counts for filtered searches', () => {
- createComponent({ props: { variables: { paused: true } } });
+ it('Displays all counts for filtered searches', () => {
+ const mockVariables = { paused: true };
+ createComponent({ props: { variables: mockVariables } });
- expect(findRunnerCountAt(0).props('variables').paused).toBe(true);
- expect(findRunnerCountAt(1).props('variables').paused).toBe(true);
- expect(findRunnerCountAt(2).props('variables').paused).toBe(true);
- });
-
- it('Skips overlapping statuses', () => {
- createComponent({ props: { variables: { status: STATUS_ONLINE } } });
-
- expect(findRunnerCountAt(0).props('skip')).toBe(false);
- expect(findRunnerCountAt(1).props('skip')).toBe(true);
- expect(findRunnerCountAt(2).props('skip')).toBe(true);
- });
-
- it.each`
- i | status
- ${0} | ${STATUS_ONLINE}
- ${1} | ${STATUS_OFFLINE}
- ${2} | ${STATUS_STALE}
- `('Displays status $status at index $i', ({ i, status }) => {
- createComponent({ mountFn: mount });
-
- expect(findRunnerCountAt(i).props('variables').status).toBe(status);
- expect(findRunnerStatusStatAt(i).props('status')).toBe(status);
+ findSingleStats().forEach((stat) => {
+ expect(stat.props('variables')).toMatchObject(mockVariables);
+ });
});
});
diff --git a/spec/frontend/runner/components/stat/runner_status_stat_spec.js b/spec/frontend/runner/components/stat/runner_status_stat_spec.js
deleted file mode 100644
index 3218272eac7..00000000000
--- a/spec/frontend/runner/components/stat/runner_status_stat_spec.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import { GlSingleStat } from '@gitlab/ui/dist/charts';
-import { shallowMount, mount } from '@vue/test-utils';
-import RunnerStatusStat from '~/runner/components/stat/runner_status_stat.vue';
-import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '~/runner/constants';
-
-describe('RunnerStatusStat', () => {
- let wrapper;
-
- const findSingleStat = () => wrapper.findComponent(GlSingleStat);
-
- const createComponent = ({ props = {} } = {}, mountFn = shallowMount) => {
- wrapper = mountFn(RunnerStatusStat, {
- propsData: {
- status: STATUS_ONLINE,
- value: 99,
- ...props,
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe.each`
- status | variant | title | badge
- ${STATUS_ONLINE} | ${'success'} | ${'Online runners'} | ${'online'}
- ${STATUS_OFFLINE} | ${'muted'} | ${'Offline runners'} | ${'offline'}
- ${STATUS_STALE} | ${'warning'} | ${'Stale runners'} | ${'stale'}
- `('Renders a stat for status "$status"', ({ status, variant, title, badge }) => {
- beforeEach(() => {
- createComponent({ props: { status } }, mount);
- });
-
- it('Renders text', () => {
- expect(wrapper.text()).toMatch(new RegExp(`${title} 99\\s+${badge}`));
- });
-
- it(`Uses variant ${variant}`, () => {
- expect(findSingleStat().props('variant')).toBe(variant);
- });
- });
-
- it('Formats stat number', () => {
- createComponent({ props: { value: 1000 } }, mount);
-
- expect(wrapper.text()).toMatch('Online runners 1,000');
- });
-
- it('Shows a null result', () => {
- createComponent({ props: { value: null } }, mount);
-
- expect(wrapper.text()).toMatch('Online runners -');
- });
-
- it('Shows an undefined result', () => {
- createComponent({ props: { value: undefined } }, mount);
-
- expect(wrapper.text()).toMatch('Online runners -');
- });
-
- it('Shows result for an unknown status', () => {
- createComponent({ props: { status: 'UNKNOWN' } }, mount);
-
- expect(wrapper.text()).toMatch('Runners 99');
- });
-});