summaryrefslogtreecommitdiff
path: root/spec/frontend/jira_connect/subscriptions/components/groups_list_spec.js
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/jira_connect/subscriptions/components/groups_list_spec.js')
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/groups_list_spec.js303
1 files changed, 303 insertions, 0 deletions
diff --git a/spec/frontend/jira_connect/subscriptions/components/groups_list_spec.js b/spec/frontend/jira_connect/subscriptions/components/groups_list_spec.js
new file mode 100644
index 00000000000..d3a9a3bfd41
--- /dev/null
+++ b/spec/frontend/jira_connect/subscriptions/components/groups_list_spec.js
@@ -0,0 +1,303 @@
+import { GlAlert, GlLoadingIcon, GlSearchBoxByType, GlPagination } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { fetchGroups } from '~/jira_connect/subscriptions/api';
+import GroupsList from '~/jira_connect/subscriptions/components/groups_list.vue';
+import GroupsListItem from '~/jira_connect/subscriptions/components/groups_list_item.vue';
+import { DEFAULT_GROUPS_PER_PAGE } from '~/jira_connect/subscriptions/constants';
+import { mockGroup1, mockGroup2 } from '../mock_data';
+
+const createMockGroup = (groupId) => {
+ return {
+ ...mockGroup1,
+ id: groupId,
+ };
+};
+
+const createMockGroups = (count) => {
+ return [...new Array(count)].map((_, idx) => createMockGroup(idx));
+};
+
+jest.mock('~/jira_connect/subscriptions/api', () => {
+ return {
+ fetchGroups: jest.fn(),
+ };
+});
+
+const mockGroupsPath = '/groups';
+
+describe('GroupsList', () => {
+ let wrapper;
+
+ const mockEmptyResponse = { data: [] };
+
+ const createComponent = (options = {}) => {
+ wrapper = extendedWrapper(
+ shallowMount(GroupsList, {
+ provide: {
+ groupsPath: mockGroupsPath,
+ },
+ ...options,
+ }),
+ );
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findGlAlert = () => wrapper.findComponent(GlAlert);
+ const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findAllItems = () => wrapper.findAll(GroupsListItem);
+ const findFirstItem = () => findAllItems().at(0);
+ const findSecondItem = () => findAllItems().at(1);
+ const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+ const findGroupsList = () => wrapper.findByTestId('groups-list');
+ const findPagination = () => wrapper.findComponent(GlPagination);
+
+ describe('when groups are loading', () => {
+ it('renders loading icon', async () => {
+ fetchGroups.mockReturnValue(new Promise(() => {}));
+ createComponent();
+
+ await wrapper.vm.$nextTick();
+
+ expect(findGlLoadingIcon().exists()).toBe(true);
+ });
+ });
+
+ describe('when groups fetch fails', () => {
+ it('renders error message', async () => {
+ fetchGroups.mockRejectedValue();
+ createComponent();
+
+ await waitForPromises();
+
+ expect(findGlLoadingIcon().exists()).toBe(false);
+ expect(findGlAlert().exists()).toBe(true);
+ expect(findGlAlert().text()).toBe('Failed to load namespaces. Please try again.');
+ });
+ });
+
+ describe('with no groups returned', () => {
+ it('renders empty state', async () => {
+ fetchGroups.mockResolvedValue(mockEmptyResponse);
+ createComponent();
+
+ await waitForPromises();
+
+ expect(findGlLoadingIcon().exists()).toBe(false);
+ expect(wrapper.text()).toContain('No available namespaces');
+ });
+ });
+
+ describe('with groups returned', () => {
+ beforeEach(async () => {
+ fetchGroups.mockResolvedValue({
+ headers: { 'X-PAGE': 1, 'X-TOTAL': 2 },
+ data: [mockGroup1, mockGroup2],
+ });
+ createComponent();
+
+ await waitForPromises();
+ });
+
+ it('renders groups list', () => {
+ expect(findAllItems()).toHaveLength(2);
+ expect(findFirstItem().props('group')).toBe(mockGroup1);
+ expect(findSecondItem().props('group')).toBe(mockGroup2);
+ });
+
+ it('sets GroupListItem `disabled` prop to `false`', () => {
+ findAllItems().wrappers.forEach((groupListItem) => {
+ expect(groupListItem.props('disabled')).toBe(false);
+ });
+ });
+
+ it('does not set opacity of the groups list', () => {
+ expect(findGroupsList().classes()).not.toContain('gl-opacity-5');
+ });
+
+ it('shows error message on $emit from item', async () => {
+ const errorMessage = 'error message';
+
+ findFirstItem().vm.$emit('error', errorMessage);
+
+ await wrapper.vm.$nextTick();
+
+ expect(findGlAlert().exists()).toBe(true);
+ expect(findGlAlert().text()).toContain(errorMessage);
+ });
+
+ describe('when searching groups', () => {
+ const mockSearchTeam = 'mock search term';
+
+ describe('while groups are loading', () => {
+ beforeEach(async () => {
+ fetchGroups.mockClear();
+ fetchGroups.mockReturnValue(new Promise(() => {}));
+
+ findSearchBox().vm.$emit('input', mockSearchTeam);
+ await wrapper.vm.$nextTick();
+ });
+
+ it('calls `fetchGroups` with search term', () => {
+ expect(fetchGroups).toHaveBeenLastCalledWith(mockGroupsPath, {
+ page: 1,
+ perPage: DEFAULT_GROUPS_PER_PAGE,
+ search: mockSearchTeam,
+ });
+ });
+
+ it('disables GroupListItems', () => {
+ findAllItems().wrappers.forEach((groupListItem) => {
+ expect(groupListItem.props('disabled')).toBe(true);
+ });
+ });
+
+ it('sets opacity of the groups list', () => {
+ expect(findGroupsList().classes()).toContain('gl-opacity-5');
+ });
+
+ it('sets loading prop of the search box', () => {
+ expect(findSearchBox().props('isLoading')).toBe(true);
+ });
+
+ it('sets value prop of the search box to the search term', () => {
+ expect(findSearchBox().props('value')).toBe(mockSearchTeam);
+ });
+ });
+
+ describe('when group search finishes loading', () => {
+ beforeEach(async () => {
+ fetchGroups.mockResolvedValue({ data: [mockGroup1] });
+ findSearchBox().vm.$emit('input');
+
+ await waitForPromises();
+ });
+
+ it('renders new groups list', () => {
+ expect(findAllItems()).toHaveLength(1);
+ expect(findFirstItem().props('group')).toBe(mockGroup1);
+ });
+ });
+
+ it.each`
+ userSearchTerm | finalSearchTerm
+ ${'gitl'} | ${'gitl'}
+ ${'git'} | ${'git'}
+ ${'gi'} | ${''}
+ ${'g'} | ${''}
+ ${''} | ${''}
+ ${undefined} | ${undefined}
+ `(
+ 'searches for "$finalSearchTerm" when user enters "$userSearchTerm"',
+ async ({ userSearchTerm, finalSearchTerm }) => {
+ fetchGroups.mockResolvedValue({
+ data: [mockGroup1],
+ headers: { 'X-PAGE': 1, 'X-TOTAL': 1 },
+ });
+
+ createComponent();
+ await waitForPromises();
+
+ const searchBox = findSearchBox();
+ searchBox.vm.$emit('input', userSearchTerm);
+
+ expect(fetchGroups).toHaveBeenLastCalledWith(mockGroupsPath, {
+ page: 1,
+ perPage: DEFAULT_GROUPS_PER_PAGE,
+ search: finalSearchTerm,
+ });
+ },
+ );
+ });
+
+ describe('when page=2', () => {
+ beforeEach(async () => {
+ const totalItems = DEFAULT_GROUPS_PER_PAGE + 1;
+ const mockGroups = createMockGroups(totalItems);
+ fetchGroups.mockResolvedValue({
+ headers: { 'X-TOTAL': totalItems, 'X-PAGE': 1 },
+ data: mockGroups,
+ });
+ createComponent();
+ await waitForPromises();
+
+ const paginationEl = findPagination();
+ paginationEl.vm.$emit('input', 2);
+ });
+
+ it('should load results for page 2', () => {
+ expect(fetchGroups).toHaveBeenLastCalledWith(mockGroupsPath, {
+ page: 2,
+ perPage: DEFAULT_GROUPS_PER_PAGE,
+ search: '',
+ });
+ });
+
+ it('resets page to 1 on search `input` event', () => {
+ const mockSearchTerm = 'gitlab';
+ const searchBox = findSearchBox();
+
+ searchBox.vm.$emit('input', mockSearchTerm);
+
+ expect(fetchGroups).toHaveBeenLastCalledWith(mockGroupsPath, {
+ page: 1,
+ perPage: DEFAULT_GROUPS_PER_PAGE,
+ search: mockSearchTerm,
+ });
+ });
+ });
+ });
+
+ describe('pagination', () => {
+ it.each`
+ scenario | totalItems | shouldShowPagination
+ ${'renders pagination'} | ${DEFAULT_GROUPS_PER_PAGE + 1} | ${true}
+ ${'does not render pagination'} | ${DEFAULT_GROUPS_PER_PAGE} | ${false}
+ ${'does not render pagination'} | ${2} | ${false}
+ ${'does not render pagination'} | ${0} | ${false}
+ `('$scenario with $totalItems groups', async ({ totalItems, shouldShowPagination }) => {
+ const mockGroups = createMockGroups(totalItems);
+ fetchGroups.mockResolvedValue({
+ headers: { 'X-TOTAL': totalItems, 'X-PAGE': 1 },
+ data: mockGroups,
+ });
+ createComponent();
+ await waitForPromises();
+
+ const paginationEl = findPagination();
+
+ expect(paginationEl.exists()).toBe(shouldShowPagination);
+ if (shouldShowPagination) {
+ expect(paginationEl.props('totalItems')).toBe(totalItems);
+ }
+ });
+
+ describe('when `input` event triggered', () => {
+ beforeEach(async () => {
+ const MOCK_TOTAL_ITEMS = DEFAULT_GROUPS_PER_PAGE + 1;
+ fetchGroups.mockResolvedValue({
+ headers: { 'X-TOTAL': MOCK_TOTAL_ITEMS, 'X-PAGE': 1 },
+ data: createMockGroups(MOCK_TOTAL_ITEMS),
+ });
+
+ createComponent();
+ await waitForPromises();
+ });
+
+ it('executes `fetchGroups` with correct arguments', () => {
+ const paginationEl = findPagination();
+ paginationEl.vm.$emit('input', 2);
+
+ expect(fetchGroups).toHaveBeenLastCalledWith(mockGroupsPath, {
+ page: 2,
+ perPage: DEFAULT_GROUPS_PER_PAGE,
+ search: '',
+ });
+ });
+ });
+ });
+});