diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-23 18:10:43 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-23 18:10:43 +0300 |
commit | a7f478c9b1806a67ec9d991c3f54c242bb596f60 (patch) | |
tree | b3a6ea0db1327002ced7d0bbc51bd37ef1abee4b /spec/frontend | |
parent | d67a86595f0866927815e0945bff032a35c76da9 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r-- | spec/frontend/fixtures/runner.rb | 47 | ||||
-rw-r--r-- | spec/frontend/projects/storage_counter/app_spec.js | 22 | ||||
-rw-r--r-- | spec/frontend/runner/admin_runners/admin_runners_app_spec.js | 97 | ||||
-rw-r--r-- | spec/frontend/runner/components/runner_filtered_search_bar_spec.js | 33 | ||||
-rw-r--r-- | spec/frontend/runner/components/runner_list_spec.js | 2 | ||||
-rw-r--r-- | spec/frontend/runner/group_runners/group_runners_app_spec.js | 239 | ||||
-rw-r--r-- | spec/frontend/runner/mock_data.js | 16 |
7 files changed, 399 insertions, 57 deletions
diff --git a/spec/frontend/fixtures/runner.rb b/spec/frontend/fixtures/runner.rb index e29a58f43b9..d5d6f534def 100644 --- a/spec/frontend/fixtures/runner.rb +++ b/spec/frontend/fixtures/runner.rb @@ -14,6 +14,7 @@ RSpec.describe 'Runner (JavaScript fixtures)' do let_it_be(:instance_runner) { create(:ci_runner, :instance, version: '1.0.0', revision: '123', description: 'Instance runner', ip_address: '127.0.0.1') } let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], active: false, version: '2.0.0', revision: '456', description: 'Group runner', ip_address: '127.0.0.1') } + let_it_be(:group_runner_2) { create(:ci_runner, :group, groups: [group], active: false, version: '2.0.0', revision: '456', description: 'Group runner 2', ip_address: '127.0.0.1') } let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project], active: false, version: '2.0.0', revision: '456', description: 'Project runner', ip_address: '127.0.0.1') } query_path = 'runner/graphql/' @@ -27,14 +28,14 @@ RSpec.describe 'Runner (JavaScript fixtures)' do remove_repository(project) end - before do - sign_in(admin) - enable_admin_mode!(admin) - end - describe GraphQL::Query, type: :request do get_runners_query_name = 'get_runners.query.graphql' + before do + sign_in(admin) + enable_admin_mode!(admin) + end + let_it_be(:query) do get_graphql_query_as_string("#{query_path}#{get_runners_query_name}") end @@ -55,6 +56,11 @@ RSpec.describe 'Runner (JavaScript fixtures)' do describe GraphQL::Query, type: :request do get_runner_query_name = 'get_runner.query.graphql' + before do + sign_in(admin) + enable_admin_mode!(admin) + end + let_it_be(:query) do get_graphql_query_as_string("#{query_path}#{get_runner_query_name}") end @@ -67,4 +73,35 @@ RSpec.describe 'Runner (JavaScript fixtures)' do expect_graphql_errors_to_be_empty end end + + describe GraphQL::Query, type: :request do + get_group_runners_query_name = 'get_group_runners.query.graphql' + + let_it_be(:group_owner) { create(:user) } + + before do + group.add_owner(group_owner) + end + + let_it_be(:query) do + get_graphql_query_as_string("#{query_path}#{get_group_runners_query_name}") + end + + it "#{fixtures_path}#{get_group_runners_query_name}.json" do + post_graphql(query, current_user: group_owner, variables: { + groupFullPath: group.full_path + }) + + expect_graphql_errors_to_be_empty + end + + it "#{fixtures_path}#{get_group_runners_query_name}.paginated.json" do + post_graphql(query, current_user: group_owner, variables: { + groupFullPath: group.full_path, + first: 1 + }) + + expect_graphql_errors_to_be_empty + end + end end diff --git a/spec/frontend/projects/storage_counter/app_spec.js b/spec/frontend/projects/storage_counter/app_spec.js new file mode 100644 index 00000000000..cf71a782f21 --- /dev/null +++ b/spec/frontend/projects/storage_counter/app_spec.js @@ -0,0 +1,22 @@ +import { shallowMount } from '@vue/test-utils'; +import StorageCounterApp from '~/projects/storage_counter/components/app.vue'; + +describe('Storage counter app', () => { + let wrapper; + + const createComponent = (propsData = {}) => { + wrapper = shallowMount(StorageCounterApp, { propsData }); + }; + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders app successfully', () => { + expect(wrapper.text()).toBe('Usage'); + }); +}); diff --git a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js index c1596711be7..3292f635f6b 100644 --- a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js +++ b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js @@ -2,6 +2,7 @@ import { createLocalVue, mount, shallowMount } from '@vue/test-utils'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import setWindowLocation from 'helpers/set_window_location_helper'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import createFlash from '~/flash'; import { updateHistory } from '~/lib/utils/url_utility'; @@ -14,16 +15,20 @@ import RunnerPagination from '~/runner/components/runner_pagination.vue'; import RunnerTypeHelp from '~/runner/components/runner_type_help.vue'; import { + ADMIN_FILTERED_SEARCH_NAMESPACE, CREATED_ASC, CREATED_DESC, DEFAULT_SORT, INSTANCE_TYPE, PARAM_KEY_STATUS, + PARAM_KEY_RUNNER_TYPE, + PARAM_KEY_TAG, STATUS_ACTIVE, RUNNER_PAGE_SIZE, } from '~/runner/constants'; import getRunnersQuery from '~/runner/graphql/get_runners.query.graphql'; import { captureException } from '~/runner/sentry_utils'; +import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; import { runnersData, runnersDataPaginated } from '../mock_data'; @@ -47,10 +52,14 @@ describe('AdminRunnersApp', () => { const findRunnerTypeHelp = () => wrapper.findComponent(RunnerTypeHelp); const findRunnerManualSetupHelp = () => wrapper.findComponent(RunnerManualSetupHelp); const findRunnerList = () => wrapper.findComponent(RunnerList); - const findRunnerPagination = () => wrapper.findComponent(RunnerPagination); + const findRunnerPagination = () => extendedWrapper(wrapper.findComponent(RunnerPagination)); + const findRunnerPaginationPrev = () => + findRunnerPagination().findByLabelText('Go to previous page'); + const findRunnerPaginationNext = () => findRunnerPagination().findByLabelText('Go to next page'); const findRunnerFilteredSearchBar = () => wrapper.findComponent(RunnerFilteredSearchBar); + const findFilteredSearch = () => wrapper.findComponent(FilteredSearch); - const createComponentWithApollo = ({ props = {}, mountFn = shallowMount } = {}) => { + const createComponent = ({ props = {}, mountFn = shallowMount } = {}) => { const handlers = [[getRunnersQuery, mockRunnersQuery]]; wrapper = mountFn(AdminRunnersApp, { @@ -68,7 +77,7 @@ describe('AdminRunnersApp', () => { setWindowLocation('/admin/runners'); mockRunnersQuery = jest.fn().mockResolvedValue(runnersData); - createComponentWithApollo(); + createComponent(); await waitForPromises(); }); @@ -77,8 +86,16 @@ describe('AdminRunnersApp', () => { wrapper.destroy(); }); + it('shows the runner type help', () => { + expect(findRunnerTypeHelp().exists()).toBe(true); + }); + + it('shows the runner setup instructions', () => { + expect(findRunnerManualSetupHelp().props('registrationToken')).toBe(mockRegistrationToken); + }); + it('shows the runners list', () => { - expect(runnersData.data.runners.nodes).toMatchObject(findRunnerList().props('runners')); + expect(findRunnerList().props('runners')).toEqual(runnersData.data.runners.nodes); }); it('requests the runners with no filters', () => { @@ -90,20 +107,38 @@ describe('AdminRunnersApp', () => { }); }); - it('shows the runner type help', () => { - expect(findRunnerTypeHelp().exists()).toBe(true); + it('sets tokens in the filtered search', () => { + createComponent({ mountFn: mount }); + + expect(findFilteredSearch().props('tokens')).toEqual([ + expect.objectContaining({ + type: PARAM_KEY_STATUS, + options: expect.any(Array), + }), + expect.objectContaining({ + type: PARAM_KEY_RUNNER_TYPE, + options: expect.any(Array), + }), + expect.objectContaining({ + type: PARAM_KEY_TAG, + recentTokenValuesStorageKey: `${ADMIN_FILTERED_SEARCH_NAMESPACE}-recent-tags`, + }), + ]); }); - it('shows the runner setup instructions', () => { - expect(findRunnerManualSetupHelp().exists()).toBe(true); - expect(findRunnerManualSetupHelp().props('registrationToken')).toBe(mockRegistrationToken); + it('shows the active runner count', () => { + createComponent({ mountFn: mount }); + + expect(findRunnerFilteredSearchBar().text()).toMatch( + `Runners currently online: ${mockActiveRunnersCount}`, + ); }); describe('when a filter is preselected', () => { beforeEach(async () => { setWindowLocation(`?status[]=${STATUS_ACTIVE}&runner_type[]=${INSTANCE_TYPE}&tag[]=tag1`); - createComponentWithApollo(); + createComponent(); await waitForPromises(); }); @@ -133,7 +168,7 @@ describe('AdminRunnersApp', () => { describe('when a filter is selected by the user', () => { beforeEach(() => { findRunnerFilteredSearchBar().vm.$emit('input', { - filters: [{ type: PARAM_KEY_STATUS, value: { data: 'ACTIVE', operator: '=' } }], + filters: [{ type: PARAM_KEY_STATUS, value: { data: STATUS_ACTIVE, operator: '=' } }], sort: CREATED_ASC, }); }); @@ -154,11 +189,19 @@ describe('AdminRunnersApp', () => { }); }); + it('when runners have not loaded, shows a loading state', () => { + createComponent(); + expect(findRunnerList().props('loading')).toBe(true); + }); + describe('when no runners are found', () => { beforeEach(async () => { - mockRunnersQuery = jest.fn().mockResolvedValue({ data: { runners: { nodes: [] } } }); - createComponentWithApollo(); - await waitForPromises(); + mockRunnersQuery = jest.fn().mockResolvedValue({ + data: { + runners: { nodes: [] }, + }, + }); + createComponent(); }); it('shows a message for no results', async () => { @@ -166,17 +209,14 @@ describe('AdminRunnersApp', () => { }); }); - it('when runners have not loaded, shows a loading state', () => { - createComponentWithApollo(); - expect(findRunnerList().props('loading')).toBe(true); - }); - describe('when runners query fails', () => { - beforeEach(async () => { + beforeEach(() => { mockRunnersQuery = jest.fn().mockRejectedValue(new Error('Error!')); - createComponentWithApollo(); + createComponent(); + }); - await waitForPromises(); + it('error is shown to the user', async () => { + expect(createFlash).toHaveBeenCalledTimes(1); }); it('error is reported to sentry', async () => { @@ -185,17 +225,13 @@ describe('AdminRunnersApp', () => { component: 'AdminRunnersApp', }); }); - - it('error is shown to the user', async () => { - expect(createFlash).toHaveBeenCalledTimes(1); - }); }); describe('Pagination', () => { beforeEach(() => { mockRunnersQuery = jest.fn().mockResolvedValue(runnersDataPaginated); - createComponentWithApollo({ mountFn: mount }); + createComponent({ mountFn: mount }); }); it('more pages can be selected', () => { @@ -203,14 +239,11 @@ describe('AdminRunnersApp', () => { }); it('cannot navigate to the previous page', () => { - expect(findRunnerPagination().find('[aria-disabled]').text()).toBe('Prev'); + expect(findRunnerPaginationPrev().attributes('aria-disabled')).toBe('true'); }); it('navigates to the next page', async () => { - const nextPageBtn = findRunnerPagination().find('a'); - expect(nextPageBtn.text()).toBe('Next'); - - await nextPageBtn.trigger('click'); + await findRunnerPaginationNext().trigger('click'); expect(mockRunnersQuery).toHaveBeenLastCalledWith({ sort: CREATED_DESC, 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 85cf7ea92df..46948af1f28 100644 --- a/spec/frontend/runner/components/runner_filtered_search_bar_spec.js +++ b/spec/frontend/runner/components/runner_filtered_search_bar_spec.js @@ -2,8 +2,16 @@ import { GlFilteredSearch, GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue'; +import { statusTokenConfig } from '~/runner/components/search_tokens/status_token_config'; import TagToken from '~/runner/components/search_tokens/tag_token.vue'; -import { PARAM_KEY_STATUS, PARAM_KEY_RUNNER_TYPE, PARAM_KEY_TAG } from '~/runner/constants'; +import { tagTokenConfig } from '~/runner/components/search_tokens/tag_token_config'; +import { typeTokenConfig } from '~/runner/components/search_tokens/type_token_config'; +import { + PARAM_KEY_STATUS, + PARAM_KEY_RUNNER_TYPE, + PARAM_KEY_TAG, + STATUS_ACTIVE, +} from '~/runner/constants'; import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue'; @@ -13,12 +21,12 @@ describe('RunnerList', () => { const findFilteredSearch = () => wrapper.findComponent(FilteredSearch); const findGlFilteredSearch = () => wrapper.findComponent(GlFilteredSearch); const findSortOptions = () => wrapper.findAllComponents(GlDropdownItem); - const findActiveRunnersMessage = () => wrapper.findByTestId('active-runners-message'); + const findActiveRunnersMessage = () => wrapper.findByTestId('runner-count'); const mockDefaultSort = 'CREATED_DESC'; const mockOtherSort = 'CONTACTED_DESC'; const mockFilters = [ - { type: PARAM_KEY_STATUS, value: { data: 'ACTIVE', operator: '=' } }, + { type: PARAM_KEY_STATUS, value: { data: STATUS_ACTIVE, operator: '=' } }, { type: 'filtered-search-term', value: { data: '' } }, ]; const mockActiveRunnersCount = 2; @@ -28,13 +36,16 @@ describe('RunnerList', () => { shallowMount(RunnerFilteredSearchBar, { propsData: { namespace: 'runners', + tokens: [], value: { filters: [], sort: mockDefaultSort, }, - activeRunnersCount: mockActiveRunnersCount, ...props, }, + slots: { + 'runner-count': `Runners currently online: ${mockActiveRunnersCount}`, + }, stubs: { FilteredSearch, GlFilteredSearch, @@ -64,12 +75,6 @@ describe('RunnerList', () => { ); }); - it('Displays a large active runner count', () => { - createComponent({ props: { activeRunnersCount: 2000 } }); - - expect(findActiveRunnersMessage().text()).toBe('Runners currently online: 2,000'); - }); - it('sets sorting options', () => { const SORT_OPTIONS_COUNT = 2; @@ -78,7 +83,13 @@ describe('RunnerList', () => { expect(findSortOptions().at(1).text()).toBe('Last contact'); }); - it('sets tokens', () => { + it('sets tokens to the filtered search', () => { + createComponent({ + props: { + tokens: [statusTokenConfig, typeTokenConfig, tagTokenConfig], + }, + }); + expect(findFilteredSearch().props('tokens')).toEqual([ expect.objectContaining({ type: PARAM_KEY_STATUS, diff --git a/spec/frontend/runner/components/runner_list_spec.js b/spec/frontend/runner/components/runner_list_spec.js index 5fff3581e39..344d1e5c150 100644 --- a/spec/frontend/runner/components/runner_list_spec.js +++ b/spec/frontend/runner/components/runner_list_spec.js @@ -56,7 +56,7 @@ describe('RunnerList', () => { }); it('Displays a list of runners', () => { - expect(findRows()).toHaveLength(3); + expect(findRows()).toHaveLength(4); expect(findSkeletonLoader().exists()).toBe(false); }); diff --git a/spec/frontend/runner/group_runners/group_runners_app_spec.js b/spec/frontend/runner/group_runners/group_runners_app_spec.js index 6a0863e92b4..e80da40e3bd 100644 --- a/spec/frontend/runner/group_runners/group_runners_app_spec.js +++ b/spec/frontend/runner/group_runners/group_runners_app_spec.js @@ -1,26 +1,85 @@ -import { shallowMount } from '@vue/test-utils'; +import { createLocalVue, shallowMount, mount } from '@vue/test-utils'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import setWindowLocation from 'helpers/set_window_location_helper'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import createFlash from '~/flash'; +import { updateHistory } from '~/lib/utils/url_utility'; + +import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue'; +import RunnerList from '~/runner/components/runner_list.vue'; import RunnerManualSetupHelp from '~/runner/components/runner_manual_setup_help.vue'; +import RunnerPagination from '~/runner/components/runner_pagination.vue'; import RunnerTypeHelp from '~/runner/components/runner_type_help.vue'; + +import { + CREATED_ASC, + CREATED_DESC, + DEFAULT_SORT, + INSTANCE_TYPE, + PARAM_KEY_STATUS, + PARAM_KEY_RUNNER_TYPE, + STATUS_ACTIVE, + RUNNER_PAGE_SIZE, +} from '~/runner/constants'; +import getGroupRunnersQuery from '~/runner/graphql/get_group_runners.query.graphql'; import GroupRunnersApp from '~/runner/group_runners/group_runners_app.vue'; +import { captureException } from '~/runner/sentry_utils'; +import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; +import { groupRunnersData, groupRunnersDataPaginated } from '../mock_data'; + +const localVue = createLocalVue(); +localVue.use(VueApollo); +const mockGroupFullPath = 'group1'; const mockRegistrationToken = 'AABBCC'; +const mockRunners = groupRunnersData.data.group.runners.nodes; +const mockGroupRunnersLimitedCount = mockRunners.length; + +jest.mock('~/flash'); +jest.mock('~/runner/sentry_utils'); +jest.mock('~/lib/utils/url_utility', () => ({ + ...jest.requireActual('~/lib/utils/url_utility'), + updateHistory: jest.fn(), +})); describe('GroupRunnersApp', () => { let wrapper; + let mockGroupRunnersQuery; const findRunnerTypeHelp = () => wrapper.findComponent(RunnerTypeHelp); const findRunnerManualSetupHelp = () => wrapper.findComponent(RunnerManualSetupHelp); + const findRunnerList = () => wrapper.findComponent(RunnerList); + const findRunnerPagination = () => extendedWrapper(wrapper.findComponent(RunnerPagination)); + const findRunnerPaginationPrev = () => + findRunnerPagination().findByLabelText('Go to previous page'); + const findRunnerPaginationNext = () => findRunnerPagination().findByLabelText('Go to next page'); + const findRunnerFilteredSearchBar = () => wrapper.findComponent(RunnerFilteredSearchBar); + const findFilteredSearch = () => wrapper.findComponent(FilteredSearch); + + const createComponent = ({ props = {}, mountFn = shallowMount } = {}) => { + const handlers = [[getGroupRunnersQuery, mockGroupRunnersQuery]]; - const createComponent = ({ mountFn = shallowMount } = {}) => { wrapper = mountFn(GroupRunnersApp, { + localVue, + apolloProvider: createMockApollo(handlers), propsData: { registrationToken: mockRegistrationToken, + groupFullPath: mockGroupFullPath, + groupRunnersLimitedCount: mockGroupRunnersLimitedCount, + ...props, }, }); }; - beforeEach(() => { + beforeEach(async () => { + setWindowLocation(`/groups/${mockGroupFullPath}/-/runners`); + + mockGroupRunnersQuery = jest.fn().mockResolvedValue(groupRunnersData); + createComponent(); + await waitForPromises(); }); it('shows the runner type help', () => { @@ -28,7 +87,179 @@ describe('GroupRunnersApp', () => { }); it('shows the runner setup instructions', () => { - expect(findRunnerManualSetupHelp().exists()).toBe(true); expect(findRunnerManualSetupHelp().props('registrationToken')).toBe(mockRegistrationToken); }); + + it('shows the runners list', () => { + expect(findRunnerList().props('runners')).toEqual(groupRunnersData.data.group.runners.nodes); + }); + + it('requests the runners with group path and no other filters', () => { + expect(mockGroupRunnersQuery).toHaveBeenLastCalledWith({ + groupFullPath: mockGroupFullPath, + status: undefined, + type: undefined, + sort: DEFAULT_SORT, + first: RUNNER_PAGE_SIZE, + }); + }); + + it('sets tokens in the filtered search', () => { + createComponent({ mountFn: mount }); + + expect(findFilteredSearch().props('tokens')).toEqual([ + expect.objectContaining({ + type: PARAM_KEY_STATUS, + options: expect.any(Array), + }), + expect.objectContaining({ + type: PARAM_KEY_RUNNER_TYPE, + options: expect.any(Array), + }), + ]); + }); + + describe('shows the active runner count', () => { + it('with a regular value', () => { + createComponent({ mountFn: mount }); + + expect(findRunnerFilteredSearchBar().text()).toMatch( + `Runners in this group: ${mockGroupRunnersLimitedCount}`, + ); + }); + + it('at the limit', () => { + createComponent({ props: { groupRunnersLimitedCount: 1000 }, mountFn: mount }); + + expect(findRunnerFilteredSearchBar().text()).toMatch(`Runners in this group: 1,000`); + }); + + it('over the limit', () => { + createComponent({ props: { groupRunnersLimitedCount: 1001 }, mountFn: mount }); + + expect(findRunnerFilteredSearchBar().text()).toMatch(`Runners in this group: 1,000+`); + }); + }); + + describe('when a filter is preselected', () => { + beforeEach(async () => { + setWindowLocation(`?status[]=${STATUS_ACTIVE}&runner_type[]=${INSTANCE_TYPE}`); + + createComponent(); + await waitForPromises(); + }); + + it('sets the filters in the search bar', () => { + expect(findRunnerFilteredSearchBar().props('value')).toEqual({ + filters: [ + { type: 'status', value: { data: STATUS_ACTIVE, operator: '=' } }, + { type: 'runner_type', value: { data: INSTANCE_TYPE, operator: '=' } }, + ], + sort: 'CREATED_DESC', + pagination: { page: 1 }, + }); + }); + + it('requests the runners with filter parameters', () => { + expect(mockGroupRunnersQuery).toHaveBeenLastCalledWith({ + groupFullPath: mockGroupFullPath, + status: STATUS_ACTIVE, + type: INSTANCE_TYPE, + sort: DEFAULT_SORT, + first: RUNNER_PAGE_SIZE, + }); + }); + }); + + describe('when a filter is selected by the user', () => { + beforeEach(() => { + findRunnerFilteredSearchBar().vm.$emit('input', { + filters: [{ type: PARAM_KEY_STATUS, value: { data: STATUS_ACTIVE, operator: '=' } }], + sort: CREATED_ASC, + }); + }); + + it('updates the browser url', () => { + expect(updateHistory).toHaveBeenLastCalledWith({ + title: expect.any(String), + url: 'http://test.host/groups/group1/-/runners?status[]=ACTIVE&sort=CREATED_ASC', + }); + }); + + it('requests the runners with filters', () => { + expect(mockGroupRunnersQuery).toHaveBeenLastCalledWith({ + groupFullPath: mockGroupFullPath, + status: STATUS_ACTIVE, + sort: CREATED_ASC, + first: RUNNER_PAGE_SIZE, + }); + }); + }); + + it('when runners have not loaded, shows a loading state', () => { + createComponent(); + expect(findRunnerList().props('loading')).toBe(true); + }); + + describe('when no runners are found', () => { + beforeEach(async () => { + mockGroupRunnersQuery = jest.fn().mockResolvedValue({ + data: { + group: { + runners: { nodes: [] }, + }, + }, + }); + createComponent(); + }); + + it('shows a message for no results', async () => { + expect(wrapper.text()).toContain('No runners found'); + }); + }); + + describe('when runners query fails', () => { + beforeEach(() => { + mockGroupRunnersQuery = jest.fn().mockRejectedValue(new Error('Error!')); + createComponent(); + }); + + it('error is shown to the user', async () => { + expect(createFlash).toHaveBeenCalledTimes(1); + }); + + it('error is reported to sentry', async () => { + expect(captureException).toHaveBeenCalledWith({ + error: new Error('Network error: Error!'), + component: 'GroupRunnersApp', + }); + }); + }); + + describe('Pagination', () => { + beforeEach(() => { + mockGroupRunnersQuery = jest.fn().mockResolvedValue(groupRunnersDataPaginated); + + createComponent({ mountFn: mount }); + }); + + it('more pages can be selected', () => { + expect(findRunnerPagination().text()).toMatchInterpolatedText('Prev Next'); + }); + + it('cannot navigate to the previous page', () => { + expect(findRunnerPaginationPrev().attributes('aria-disabled')).toBe('true'); + }); + + it('navigates to the next page', async () => { + await findRunnerPaginationNext().trigger('click'); + + expect(mockGroupRunnersQuery).toHaveBeenLastCalledWith({ + groupFullPath: mockGroupFullPath, + sort: CREATED_DESC, + first: RUNNER_PAGE_SIZE, + after: groupRunnersDataPaginated.data.group.runners.pageInfo.endCursor, + }); + }); + }); }); diff --git a/spec/frontend/runner/mock_data.js b/spec/frontend/runner/mock_data.js index 8f551feca6e..c90b9a4c426 100644 --- a/spec/frontend/runner/mock_data.js +++ b/spec/frontend/runner/mock_data.js @@ -1,6 +1,14 @@ +const runnerFixture = (filename) => getJSONFixture(`graphql/runner/${filename}`); + // Fixtures generated by: spec/frontend/fixtures/runner.rb -export const runnersData = getJSONFixture('graphql/runner/get_runners.query.graphql.json'); -export const runnersDataPaginated = getJSONFixture( - 'graphql/runner/get_runners.query.graphql.paginated.json', + +// Admin queries +export const runnersData = runnerFixture('get_runners.query.graphql.json'); +export const runnersDataPaginated = runnerFixture('get_runners.query.graphql.paginated.json'); +export const runnerData = runnerFixture('get_runner.query.graphql.json'); + +// Group queries +export const groupRunnersData = runnerFixture('get_group_runners.query.graphql.json'); +export const groupRunnersDataPaginated = runnerFixture( + 'get_group_runners.query.graphql.paginated.json', ); -export const runnerData = getJSONFixture('graphql/runner/get_runner.query.graphql.json'); |