diff options
Diffstat (limited to 'spec/frontend/clusters_list')
11 files changed, 643 insertions, 61 deletions
diff --git a/spec/frontend/clusters_list/components/agent_empty_state_spec.js b/spec/frontend/clusters_list/components/agent_empty_state_spec.js index a548721588e..38f0e0ba2c4 100644 --- a/spec/frontend/clusters_list/components/agent_empty_state_spec.js +++ b/spec/frontend/clusters_list/components/agent_empty_state_spec.js @@ -1,13 +1,12 @@ import { GlAlert, GlEmptyState, GlSprintf } from '@gitlab/ui'; import AgentEmptyState from '~/clusters_list/components/agent_empty_state.vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { helpPagePath } from '~/helpers/help_page_helper'; const emptyStateImage = '/path/to/image'; const projectPath = 'path/to/project'; -const agentDocsUrl = 'path/to/agentDocs'; -const installDocsUrl = 'path/to/installDocs'; -const getStartedDocsUrl = 'path/to/getStartedDocs'; -const integrationDocsUrl = 'path/to/integrationDocs'; +const multipleClustersDocsUrl = helpPagePath('user/project/clusters/multiple_kubernetes_clusters'); +const installDocsUrl = helpPagePath('administration/clusters/kas'); describe('AgentEmptyStateComponent', () => { let wrapper; @@ -18,14 +17,10 @@ describe('AgentEmptyStateComponent', () => { const provideData = { emptyStateImage, projectPath, - agentDocsUrl, - installDocsUrl, - getStartedDocsUrl, - integrationDocsUrl, }; const findConfigurationsAlert = () => wrapper.findComponent(GlAlert); - const findAgentDocsLink = () => wrapper.findByTestId('agent-docs-link'); + const findMultipleClustersDocsLink = () => wrapper.findByTestId('multiple-clusters-docs-link'); const findInstallDocsLink = () => wrapper.findByTestId('install-docs-link'); const findIntegrationButton = () => wrapper.findByTestId('integration-primary-button'); const findEmptyState = () => wrapper.findComponent(GlEmptyState); @@ -41,12 +36,11 @@ describe('AgentEmptyStateComponent', () => { afterEach(() => { if (wrapper) { wrapper.destroy(); - wrapper = null; } }); it('renders correct href attributes for the links', () => { - expect(findAgentDocsLink().attributes('href')).toBe(agentDocsUrl); + expect(findMultipleClustersDocsLink().attributes('href')).toBe(multipleClustersDocsUrl); expect(findInstallDocsLink().attributes('href')).toBe(installDocsUrl); }); diff --git a/spec/frontend/clusters_list/components/agent_table_spec.js b/spec/frontend/clusters_list/components/agent_table_spec.js index e3b90584f29..a6d76b069cf 100644 --- a/spec/frontend/clusters_list/components/agent_table_spec.js +++ b/spec/frontend/clusters_list/components/agent_table_spec.js @@ -1,4 +1,4 @@ -import { GlButton, GlLink, GlIcon } from '@gitlab/ui'; +import { GlLink, GlIcon } from '@gitlab/ui'; import AgentTable from '~/clusters_list/components/agent_table.vue'; import { ACTIVE_CONNECTION_TIME } from '~/clusters_list/constants'; import { mountExtended } from 'helpers/vue_test_utils_helper'; @@ -47,7 +47,6 @@ const propsData = { }, ], }; -const provideData = { integrationDocsUrl: 'path/to/integrationDocs' }; describe('AgentTable', () => { let wrapper; @@ -60,7 +59,7 @@ describe('AgentTable', () => { wrapper.findAllByTestId('cluster-agent-configuration-link').at(at); beforeEach(() => { - wrapper = mountExtended(AgentTable, { propsData, provide: provideData }); + wrapper = mountExtended(AgentTable, { propsData }); }); afterEach(() => { @@ -70,10 +69,6 @@ describe('AgentTable', () => { } }); - it('displays header button', () => { - expect(wrapper.find(GlButton).text()).toBe('Install a new GitLab Agent'); - }); - describe('agent table', () => { it.each` agentName | link | lineNumber diff --git a/spec/frontend/clusters_list/components/agents_spec.js b/spec/frontend/clusters_list/components/agents_spec.js index 54d5ae94172..2dec7cdc973 100644 --- a/spec/frontend/clusters_list/components/agents_spec.js +++ b/spec/frontend/clusters_list/components/agents_spec.js @@ -14,7 +14,7 @@ localVue.use(VueApollo); describe('Agents', () => { let wrapper; - const propsData = { + const defaultProps = { defaultBranchName: 'default', }; const provideData = { @@ -22,12 +22,12 @@ describe('Agents', () => { kasAddress: 'kas.example.com', }; - const createWrapper = ({ agents = [], pageInfo = null, trees = [] }) => { + const createWrapper = ({ props = {}, agents = [], pageInfo = null, trees = [], count = 0 }) => { const provide = provideData; const apolloQueryResponse = { data: { project: { - clusterAgents: { nodes: agents, pageInfo, tokens: { nodes: [] } }, + clusterAgents: { nodes: agents, pageInfo, tokens: { nodes: [] }, count }, repository: { tree: { trees: { nodes: trees, pageInfo } } }, }, }, @@ -40,7 +40,10 @@ describe('Agents', () => { wrapper = shallowMount(Agents, { localVue, apolloProvider, - propsData, + propsData: { + ...defaultProps, + ...props, + }, provide: provideData, }); @@ -54,7 +57,6 @@ describe('Agents', () => { afterEach(() => { if (wrapper) { wrapper.destroy(); - wrapper = null; } }); @@ -81,6 +83,8 @@ describe('Agents', () => { }, ]; + const count = 2; + const trees = [ { name: 'agent-2', @@ -121,7 +125,7 @@ describe('Agents', () => { ]; beforeEach(() => { - return createWrapper({ agents, trees }); + return createWrapper({ agents, count, trees }); }); it('should render agent table', () => { @@ -133,6 +137,10 @@ describe('Agents', () => { expect(findAgentTable().props('agents')).toMatchObject(expectedAgentsList); }); + it('should emit agents count to the parent component', () => { + expect(wrapper.emitted().onAgentsLoad).toEqual([[count]]); + }); + describe('when the agent has recently connected tokens', () => { it('should set agent status to active', () => { expect(findAgentTable().props('agents')).toMatchObject(expectedAgentsList); @@ -180,6 +188,20 @@ describe('Agents', () => { it('should pass pageInfo to the pagination component', () => { expect(findPaginationButtons().props()).toMatchObject(pageInfo); }); + + describe('when limit is passed from the parent component', () => { + beforeEach(() => { + return createWrapper({ + props: { limit: 6 }, + agents, + pageInfo, + }); + }); + + it('should not render pagination buttons', () => { + expect(findPaginationButtons().exists()).toBe(false); + }); + }); }); }); @@ -234,7 +256,11 @@ describe('Agents', () => { }; beforeEach(() => { - wrapper = shallowMount(Agents, { mocks, propsData, provide: provideData }); + wrapper = shallowMount(Agents, { + mocks, + propsData: defaultProps, + provide: provideData, + }); return wrapper.vm.$nextTick(); }); diff --git a/spec/frontend/clusters_list/components/clusters_actions_spec.js b/spec/frontend/clusters_list/components/clusters_actions_spec.js new file mode 100644 index 00000000000..cb8303ca4b2 --- /dev/null +++ b/spec/frontend/clusters_list/components/clusters_actions_spec.js @@ -0,0 +1,55 @@ +import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ClustersActions from '~/clusters_list/components/clusters_actions.vue'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import { INSTALL_AGENT_MODAL_ID, CLUSTERS_ACTIONS } from '~/clusters_list/constants'; + +describe('ClustersActionsComponent', () => { + let wrapper; + + const newClusterPath = 'path/to/create/cluster'; + const addClusterPath = 'path/to/connect/existing/cluster'; + + const provideData = { + newClusterPath, + addClusterPath, + }; + + const findDropdown = () => wrapper.findComponent(GlDropdown); + const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); + const findNewClusterLink = () => wrapper.findByTestId('new-cluster-link'); + const findConnectClusterLink = () => wrapper.findByTestId('connect-cluster-link'); + const findConnectNewAgentLink = () => wrapper.findByTestId('connect-new-agent-link'); + + beforeEach(() => { + wrapper = shallowMountExtended(ClustersActions, { + provide: provideData, + directives: { + GlModalDirective: createMockDirective(), + }, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders actions menu', () => { + expect(findDropdown().props('text')).toBe(CLUSTERS_ACTIONS.actionsButton); + }); + + it('renders a dropdown with 3 actions items', () => { + expect(findDropdownItems()).toHaveLength(3); + }); + + it('renders correct href attributes for the links', () => { + expect(findNewClusterLink().attributes('href')).toBe(newClusterPath); + expect(findConnectClusterLink().attributes('href')).toBe(addClusterPath); + }); + + it('renders correct modal id for the agent link', () => { + const binding = getBinding(findConnectNewAgentLink().element, 'gl-modal-directive'); + + expect(binding.value).toBe(INSTALL_AGENT_MODAL_ID); + }); +}); diff --git a/spec/frontend/clusters_list/components/clusters_empty_state_spec.js b/spec/frontend/clusters_list/components/clusters_empty_state_spec.js new file mode 100644 index 00000000000..f7e1791d0f7 --- /dev/null +++ b/spec/frontend/clusters_list/components/clusters_empty_state_spec.js @@ -0,0 +1,104 @@ +import { GlEmptyState, GlButton } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ClustersEmptyState from '~/clusters_list/components/clusters_empty_state.vue'; +import ClusterStore from '~/clusters_list/store'; + +const clustersEmptyStateImage = 'path/to/svg'; +const newClusterPath = '/path/to/connect/cluster'; +const emptyStateHelpText = 'empty state text'; +const canAddCluster = true; + +describe('ClustersEmptyStateComponent', () => { + let wrapper; + + const propsData = { + isChildComponent: false, + }; + + const provideData = { + clustersEmptyStateImage, + emptyStateHelpText: null, + newClusterPath, + }; + + const entryData = { + canAddCluster, + }; + + const findButton = () => wrapper.findComponent(GlButton); + const findEmptyStateText = () => wrapper.findByTestId('clusters-empty-state-text'); + + beforeEach(() => { + wrapper = shallowMountExtended(ClustersEmptyState, { + store: ClusterStore(entryData), + propsData, + provide: provideData, + stubs: { GlEmptyState }, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('when the component is loaded independently', () => { + it('should render the action button', () => { + expect(findButton().exists()).toBe(true); + }); + }); + + describe('when the help text is not provided', () => { + it('should not render the empty state text', () => { + expect(findEmptyStateText().exists()).toBe(false); + }); + }); + + describe('when the component is loaded as a child component', () => { + beforeEach(() => { + propsData.isChildComponent = true; + wrapper = shallowMountExtended(ClustersEmptyState, { + store: ClusterStore(entryData), + propsData, + provide: provideData, + }); + }); + + afterEach(() => { + propsData.isChildComponent = false; + }); + + it('should not render the action button', () => { + expect(findButton().exists()).toBe(false); + }); + }); + + describe('when the help text is provided', () => { + beforeEach(() => { + provideData.emptyStateHelpText = emptyStateHelpText; + wrapper = shallowMountExtended(ClustersEmptyState, { + store: ClusterStore(entryData), + propsData, + provide: provideData, + }); + }); + + it('should show the empty state text', () => { + expect(findEmptyStateText().text()).toBe(emptyStateHelpText); + }); + }); + + describe('when the user cannot add clusters', () => { + entryData.canAddCluster = false; + beforeEach(() => { + wrapper = shallowMountExtended(ClustersEmptyState, { + store: ClusterStore(entryData), + propsData, + provide: provideData, + stubs: { GlEmptyState }, + }); + }); + it('should disable the button', () => { + expect(findButton().props('disabled')).toBe(true); + }); + }); +}); diff --git a/spec/frontend/clusters_list/components/clusters_main_view_spec.js b/spec/frontend/clusters_list/components/clusters_main_view_spec.js new file mode 100644 index 00000000000..c2233e5d39c --- /dev/null +++ b/spec/frontend/clusters_list/components/clusters_main_view_spec.js @@ -0,0 +1,82 @@ +import { GlTabs, GlTab } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ClustersMainView from '~/clusters_list/components/clusters_main_view.vue'; +import InstallAgentModal from '~/clusters_list/components/install_agent_modal.vue'; +import { + AGENT, + CERTIFICATE_BASED, + CLUSTERS_TABS, + MAX_CLUSTERS_LIST, + MAX_LIST_COUNT, +} from '~/clusters_list/constants'; + +const defaultBranchName = 'default-branch'; + +describe('ClustersMainViewComponent', () => { + let wrapper; + + const propsData = { + defaultBranchName, + }; + + beforeEach(() => { + wrapper = shallowMountExtended(ClustersMainView, { + propsData, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + const findTabs = () => wrapper.findComponent(GlTabs); + const findAllTabs = () => wrapper.findAllComponents(GlTab); + const findGlTabAtIndex = (index) => findAllTabs().at(index); + const findComponent = () => wrapper.findByTestId('clusters-tab-component'); + const findModal = () => wrapper.findComponent(InstallAgentModal); + + it('renders `GlTabs` with `syncActiveTabWithQueryParams` and `queryParamName` props set', () => { + expect(findTabs().exists()).toBe(true); + expect(findTabs().props('syncActiveTabWithQueryParams')).toBe(true); + }); + + it('renders correct number of tabs', () => { + expect(findAllTabs()).toHaveLength(CLUSTERS_TABS.length); + }); + + it('passes child-component param to the component', () => { + expect(findComponent().props('defaultBranchName')).toBe(defaultBranchName); + }); + + it('passes correct max-agents param to the modal', () => { + expect(findModal().props('maxAgents')).toBe(MAX_CLUSTERS_LIST); + }); + + describe('tabs', () => { + it.each` + tabTitle | queryParamValue | lineNumber + ${'All'} | ${'all'} | ${0} + ${'Agent'} | ${AGENT} | ${1} + ${'Certificate based'} | ${CERTIFICATE_BASED} | ${2} + `( + 'renders correct tab title and query param value', + ({ tabTitle, queryParamValue, lineNumber }) => { + expect(findGlTabAtIndex(lineNumber).attributes('title')).toBe(tabTitle); + expect(findGlTabAtIndex(lineNumber).props('queryParamValue')).toBe(queryParamValue); + }, + ); + }); + + describe('when the child component emits the tab change event', () => { + beforeEach(() => { + findComponent().vm.$emit('changeTab', AGENT); + }); + it('changes the tab', () => { + expect(findTabs().attributes('value')).toBe('1'); + }); + + it('passes correct max-agents param to the modal', () => { + expect(findModal().props('maxAgents')).toBe(MAX_LIST_COUNT); + }); + }); +}); diff --git a/spec/frontend/clusters_list/components/clusters_spec.js b/spec/frontend/clusters_list/components/clusters_spec.js index 941a3adb625..a34202c789d 100644 --- a/spec/frontend/clusters_list/components/clusters_spec.js +++ b/spec/frontend/clusters_list/components/clusters_spec.js @@ -8,6 +8,7 @@ import * as Sentry from '@sentry/browser'; import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import Clusters from '~/clusters_list/components/clusters.vue'; +import ClustersEmptyState from '~/clusters_list/components/clusters_empty_state.vue'; import ClusterStore from '~/clusters_list/store'; import axios from '~/lib/utils/axios_utils'; import { apiData } from '../mock_data'; @@ -18,26 +19,38 @@ describe('Clusters', () => { let wrapper; const endpoint = 'some/endpoint'; + const totalClustersNumber = 6; + const clustersEmptyStateImage = 'path/to/svg'; + const emptyStateHelpText = null; + const newClusterPath = '/path/to/new/cluster'; const entryData = { endpoint, imgTagsAwsText: 'AWS Icon', imgTagsDefaultText: 'Default Icon', imgTagsGcpText: 'GCP Icon', + totalClusters: totalClustersNumber, }; - const findLoader = () => wrapper.find(GlLoadingIcon); - const findPaginatedButtons = () => wrapper.find(GlPagination); - const findTable = () => wrapper.find(GlTable); + const provideData = { + clustersEmptyStateImage, + emptyStateHelpText, + newClusterPath, + }; + + const findLoader = () => wrapper.findComponent(GlLoadingIcon); + const findPaginatedButtons = () => wrapper.findComponent(GlPagination); + const findTable = () => wrapper.findComponent(GlTable); const findStatuses = () => findTable().findAll('.js-status'); + const findEmptyState = () => wrapper.findComponent(ClustersEmptyState); const mockPollingApi = (response, body, header) => { mock.onGet(`${endpoint}?page=${header['x-page']}`).reply(response, body, header); }; - const mountWrapper = () => { + const createWrapper = ({ propsData = {} }) => { store = ClusterStore(entryData); - wrapper = mount(Clusters, { store }); + wrapper = mount(Clusters, { propsData, provide: provideData, store, stubs: { GlTable } }); return axios.waitForAll(); }; @@ -57,7 +70,7 @@ describe('Clusters', () => { mock = new MockAdapter(axios); mockPollingApi(200, apiData, paginationHeader()); - return mountWrapper(); + return createWrapper({}); }); afterEach(() => { @@ -70,7 +83,6 @@ describe('Clusters', () => { describe('when data is loading', () => { beforeEach(() => { wrapper.vm.$store.state.loadingClusters = true; - return wrapper.vm.$nextTick(); }); it('displays a loader instead of the table while loading', () => { @@ -79,23 +91,29 @@ describe('Clusters', () => { }); }); - it('displays a table component', () => { - expect(findTable().exists()).toBe(true); + describe('when clusters are present', () => { + it('displays a table component', () => { + expect(findTable().exists()).toBe(true); + }); }); - it('renders the correct table headers', () => { - const tableHeaders = wrapper.vm.fields; - const headers = findTable().findAll('th'); - - expect(headers.length).toBe(tableHeaders.length); - - tableHeaders.forEach((headerText, i) => - expect(headers.at(i).text()).toEqual(headerText.label), - ); + describe('when there are no clusters', () => { + beforeEach(() => { + wrapper.vm.$store.state.totalClusters = 0; + }); + it('should render empty state', () => { + expect(findEmptyState().exists()).toBe(true); + }); }); - it('should stack on smaller devices', () => { - expect(findTable().classes()).toContain('b-table-stacked-md'); + describe('when is loaded as a child component', () => { + beforeEach(() => { + createWrapper({ limit: 6 }); + }); + + it("shouldn't render pagination buttons", () => { + expect(findPaginatedButtons().exists()).toBe(false); + }); }); }); @@ -240,7 +258,7 @@ describe('Clusters', () => { beforeEach(() => { mockPollingApi(200, apiData, paginationHeader(totalFirstPage, perPage, 1)); - return mountWrapper(); + return createWrapper({}); }); it('should load to page 1 with header values', () => { diff --git a/spec/frontend/clusters_list/components/clusters_view_all_spec.js b/spec/frontend/clusters_list/components/clusters_view_all_spec.js new file mode 100644 index 00000000000..6ef56beddee --- /dev/null +++ b/spec/frontend/clusters_list/components/clusters_view_all_spec.js @@ -0,0 +1,243 @@ +import { GlCard, GlLoadingIcon, GlButton, GlSprintf, GlBadge } from '@gitlab/ui'; +import { createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ClustersViewAll from '~/clusters_list/components/clusters_view_all.vue'; +import Agents from '~/clusters_list/components/agents.vue'; +import Clusters from '~/clusters_list/components/clusters.vue'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import { + AGENT, + CERTIFICATE_BASED, + AGENT_CARD_INFO, + CERTIFICATE_BASED_CARD_INFO, + MAX_CLUSTERS_LIST, + INSTALL_AGENT_MODAL_ID, +} from '~/clusters_list/constants'; +import { sprintf } from '~/locale'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +const addClusterPath = '/path/to/add/cluster'; +const defaultBranchName = 'default-branch'; + +describe('ClustersViewAllComponent', () => { + let wrapper; + + const event = { + preventDefault: jest.fn(), + }; + + const propsData = { + defaultBranchName, + }; + + const provideData = { + addClusterPath, + }; + + const entryData = { + loadingClusters: false, + totalClusters: 0, + }; + + const findCards = () => wrapper.findAllComponents(GlCard); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findAgentsComponent = () => wrapper.findComponent(Agents); + const findClustersComponent = () => wrapper.findComponent(Clusters); + const findCardsContainer = () => wrapper.findByTestId('clusters-cards-container'); + const findAgentCardTitle = () => wrapper.findByTestId('agent-card-title'); + const findRecommendedBadge = () => wrapper.findComponent(GlBadge); + const findClustersCardTitle = () => wrapper.findByTestId('clusters-card-title'); + const findFooterButton = (line) => findCards().at(line).findComponent(GlButton); + + const createStore = (initialState) => + new Vuex.Store({ + state: initialState, + }); + + const createWrapper = ({ initialState }) => { + wrapper = shallowMountExtended(ClustersViewAll, { + localVue, + store: createStore(initialState), + propsData, + provide: provideData, + directives: { + GlModalDirective: createMockDirective(), + }, + stubs: { GlCard, GlSprintf }, + }); + }; + + beforeEach(() => { + createWrapper({ initialState: entryData }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('when agents and clusters are not loaded', () => { + const initialState = { + loadingClusters: true, + totalClusters: 0, + }; + beforeEach(() => { + createWrapper({ initialState }); + }); + + it('should show the loading icon', () => { + expect(findLoadingIcon().exists()).toBe(true); + }); + }); + + describe('when both agents and clusters are loaded', () => { + beforeEach(() => { + findAgentsComponent().vm.$emit('onAgentsLoad', 6); + }); + + it("shouldn't show the loading icon", () => { + expect(findLoadingIcon().exists()).toBe(false); + }); + + it('should make content visible', () => { + expect(findCardsContainer().isVisible()).toBe(true); + }); + + it('should render 2 cards', () => { + expect(findCards().length).toBe(2); + }); + }); + + describe('agents card', () => { + it('should show recommended badge', () => { + expect(findRecommendedBadge().exists()).toBe(true); + }); + + it('should render Agents component', () => { + expect(findAgentsComponent().exists()).toBe(true); + }); + + it('should pass the limit prop', () => { + expect(findAgentsComponent().props('limit')).toBe(MAX_CLUSTERS_LIST); + }); + + it('should pass the default-branch-name prop', () => { + expect(findAgentsComponent().props('defaultBranchName')).toBe(defaultBranchName); + }); + + describe('when there are no agents', () => { + it('should show the empty title', () => { + expect(findAgentCardTitle().text()).toBe(AGENT_CARD_INFO.emptyTitle); + }); + + it('should show install new Agent button in the footer', () => { + expect(findFooterButton(0).exists()).toBe(true); + }); + + it('should render correct modal id for the agent link', () => { + const binding = getBinding(findFooterButton(0).element, 'gl-modal-directive'); + + expect(binding.value).toBe(INSTALL_AGENT_MODAL_ID); + }); + }); + + describe('when the agents are present', () => { + const findFooterLink = () => wrapper.findByTestId('agents-tab-footer-link'); + const agentsNumber = 7; + + beforeEach(() => { + findAgentsComponent().vm.$emit('onAgentsLoad', agentsNumber); + }); + + it('should show the correct title', () => { + expect(findAgentCardTitle().text()).toBe( + sprintf(AGENT_CARD_INFO.title, { number: MAX_CLUSTERS_LIST, total: agentsNumber }), + ); + }); + + it('should show the link to the Agents tab in the footer', () => { + expect(findFooterLink().exists()).toBe(true); + expect(findFooterLink().text()).toBe( + sprintf(AGENT_CARD_INFO.footerText, { number: agentsNumber }), + ); + expect(findFooterLink().attributes('href')).toBe(`?tab=${AGENT}`); + }); + + describe('when clicking on the footer link', () => { + beforeEach(() => { + findFooterLink().vm.$emit('click', event); + }); + + it('should trigger tab change', () => { + expect(wrapper.emitted('changeTab')).toEqual([[AGENT]]); + }); + }); + }); + }); + + describe('clusters tab', () => { + it('should pass the limit prop', () => { + expect(findClustersComponent().props('limit')).toBe(MAX_CLUSTERS_LIST); + }); + + it('should pass the is-child-component prop', () => { + expect(findClustersComponent().props('isChildComponent')).toBe(true); + }); + + describe('when there are no clusters', () => { + it('should show the empty title', () => { + expect(findClustersCardTitle().text()).toBe(CERTIFICATE_BASED_CARD_INFO.emptyTitle); + }); + + it('should show install new Agent button in the footer', () => { + expect(findFooterButton(1).exists()).toBe(true); + }); + + it('should render correct href for the button in the footer', () => { + expect(findFooterButton(1).attributes('href')).toBe(addClusterPath); + }); + }); + + describe('when the clusters are present', () => { + const findFooterLink = () => wrapper.findByTestId('clusters-tab-footer-link'); + + const clustersNumber = 7; + const initialState = { + loadingClusters: false, + totalClusters: clustersNumber, + }; + + beforeEach(() => { + createWrapper({ initialState }); + }); + + it('should show the correct title', () => { + expect(findClustersCardTitle().text()).toBe( + sprintf(CERTIFICATE_BASED_CARD_INFO.title, { + number: MAX_CLUSTERS_LIST, + total: clustersNumber, + }), + ); + }); + + it('should show the link to the Clusters tab in the footer', () => { + expect(findFooterLink().exists()).toBe(true); + expect(findFooterLink().text()).toBe( + sprintf(CERTIFICATE_BASED_CARD_INFO.footerText, { number: clustersNumber }), + ); + }); + + describe('when clicking on the footer link', () => { + beforeEach(() => { + findFooterLink().vm.$emit('click', event); + }); + + it('should trigger tab change', () => { + expect(wrapper.emitted('changeTab')).toEqual([[CERTIFICATE_BASED]]); + }); + }); + }); + }); +}); diff --git a/spec/frontend/clusters_list/components/install_agent_modal_spec.js b/spec/frontend/clusters_list/components/install_agent_modal_spec.js index 98ca5e05b3f..6c2ea45b99b 100644 --- a/spec/frontend/clusters_list/components/install_agent_modal_spec.js +++ b/spec/frontend/clusters_list/components/install_agent_modal_spec.js @@ -3,7 +3,8 @@ import { createLocalVue, shallowMount } from '@vue/test-utils'; import VueApollo from 'vue-apollo'; import AvailableAgentsDropdown from '~/clusters_list/components/available_agents_dropdown.vue'; import InstallAgentModal from '~/clusters_list/components/install_agent_modal.vue'; -import { I18N_INSTALL_AGENT_MODAL } from '~/clusters_list/constants'; +import { I18N_INSTALL_AGENT_MODAL, MAX_LIST_COUNT } from '~/clusters_list/constants'; +import getAgentsQuery from '~/clusters_list/graphql/queries/get_agents.query.graphql'; import createAgentMutation from '~/clusters_list/graphql/mutations/create_agent.mutation.graphql'; import createAgentTokenMutation from '~/clusters_list/graphql/mutations/create_agent_token.mutation.graphql'; import createMockApollo from 'helpers/mock_apollo_helper'; @@ -14,12 +15,17 @@ import { createAgentErrorResponse, createAgentTokenResponse, createAgentTokenErrorResponse, + getAgentResponse, } from '../mocks/apollo'; import ModalStub from '../stubs'; const localVue = createLocalVue(); localVue.use(VueApollo); +const projectPath = 'path/to/project'; +const defaultBranchName = 'default'; +const maxAgents = MAX_LIST_COUNT; + describe('InstallAgentModal', () => { let wrapper; let apolloProvider; @@ -45,10 +51,15 @@ describe('InstallAgentModal', () => { const createWrapper = () => { const provide = { - projectPath: 'path/to/project', + projectPath, kasAddress: 'kas.example.com', }; + const propsData = { + defaultBranchName, + maxAgents, + }; + wrapper = shallowMount(InstallAgentModal, { attachTo: document.body, stubs: { @@ -57,11 +68,26 @@ describe('InstallAgentModal', () => { localVue, apolloProvider, provide, + propsData, + }); + }; + + const writeQuery = () => { + apolloProvider.clients.defaultClient.cache.writeQuery({ + query: getAgentsQuery, + variables: { + projectPath, + defaultBranchName, + first: MAX_LIST_COUNT, + last: null, + }, + data: getAgentResponse.data, }); }; const mockSelectedAgentResponse = () => { createWrapper(); + writeQuery(); wrapper.vm.setAgentName('agent-name'); findActionButton().vm.$emit('click'); @@ -95,7 +121,7 @@ describe('InstallAgentModal', () => { it('renders a disabled next button', () => { expect(findActionButton().isVisible()).toBe(true); - expect(findActionButton().text()).toBe(i18n.next); + expect(findActionButton().text()).toBe(i18n.registerAgentButton); expectDisabledAttribute(findActionButton(), true); }); }); @@ -126,7 +152,7 @@ describe('InstallAgentModal', () => { it('creates an agent and token', () => { expect(createAgentHandler).toHaveBeenCalledWith({ - input: { name: 'agent-name', projectPath: 'path/to/project' }, + input: { name: 'agent-name', projectPath }, }); expect(createAgentTokenHandler).toHaveBeenCalledWith({ @@ -134,9 +160,9 @@ describe('InstallAgentModal', () => { }); }); - it('renders a done button', () => { + it('renders a close button', () => { expect(findActionButton().isVisible()).toBe(true); - expect(findActionButton().text()).toBe(i18n.done); + expect(findActionButton().text()).toBe(i18n.close); expectDisabledAttribute(findActionButton(), false); }); diff --git a/spec/frontend/clusters_list/mocks/apollo.js b/spec/frontend/clusters_list/mocks/apollo.js index 27b71a0d4b5..1a7ef84a6d9 100644 --- a/spec/frontend/clusters_list/mocks/apollo.js +++ b/spec/frontend/clusters_list/mocks/apollo.js @@ -1,8 +1,29 @@ +const agent = { + id: 'agent-id', + name: 'agent-name', + webPath: 'agent-webPath', +}; +const token = { + id: 'token-id', + lastUsedAt: null, +}; +const tokens = { + nodes: [token], +}; +const pageInfo = { + endCursor: '', + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', +}; +const count = 1; + export const createAgentResponse = { data: { createClusterAgent: { clusterAgent: { - id: 'agent-id', + ...agent, + tokens, }, errors: [], }, @@ -13,7 +34,8 @@ export const createAgentErrorResponse = { data: { createClusterAgent: { clusterAgent: { - id: 'agent-id', + ...agent, + tokens, }, errors: ['could not create agent'], }, @@ -23,9 +45,7 @@ export const createAgentErrorResponse = { export const createAgentTokenResponse = { data: { clusterAgentTokenCreate: { - token: { - id: 'token-id', - }, + token, secret: 'mock-agent-token', errors: [], }, @@ -35,11 +55,22 @@ export const createAgentTokenResponse = { export const createAgentTokenErrorResponse = { data: { clusterAgentTokenCreate: { - token: { - id: 'token-id', - }, + token, secret: 'mock-agent-token', errors: ['could not create agent token'], }, }, }; + +export const getAgentResponse = { + data: { + project: { + clusterAgents: { nodes: [{ ...agent, tokens }], pageInfo, count }, + repository: { + tree: { + trees: { nodes: [{ ...agent, path: null }], pageInfo }, + }, + }, + }, + }, +}; diff --git a/spec/frontend/clusters_list/store/mutations_spec.js b/spec/frontend/clusters_list/store/mutations_spec.js index c0fe634a703..ae264eee449 100644 --- a/spec/frontend/clusters_list/store/mutations_spec.js +++ b/spec/frontend/clusters_list/store/mutations_spec.js @@ -26,7 +26,7 @@ describe('Admin statistics panel mutations', () => { expect(state.clusters).toBe(apiData.clusters); expect(state.clustersPerPage).toBe(paginationInformation.perPage); expect(state.hasAncestorClusters).toBe(apiData.has_ancestor_clusters); - expect(state.totalCulsters).toBe(paginationInformation.total); + expect(state.totalClusters).toBe(paginationInformation.total); }); }); @@ -57,4 +57,12 @@ describe('Admin statistics panel mutations', () => { expect(state.page).toBe(123); }); }); + + describe(`${types.SET_CLUSTERS_PER_PAGE}`, () => { + it('changes clustersPerPage value', () => { + mutations[types.SET_CLUSTERS_PER_PAGE](state, 123); + + expect(state.clustersPerPage).toBe(123); + }); + }); }); |