debian-mirror-gitlab/spec/frontend/access_tokens/components/projects_token_selector_spec.js
2021-11-18 22:05:49 +05:30

267 lines
8.1 KiB
JavaScript

import {
GlAvatar,
GlAvatarLabeled,
GlIntersectionObserver,
GlToken,
GlTokenSelector,
GlLoadingIcon,
} from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import produce from 'immer';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import getProjectsQueryResponse from 'test_fixtures/graphql/projects/access_tokens/get_projects.query.graphql.json';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import ProjectsTokenSelector from '~/access_tokens/components/projects_token_selector.vue';
import getProjectsQuery from '~/access_tokens/graphql/queries/get_projects.query.graphql';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
describe('ProjectsTokenSelector', () => {
const getProjectsQueryResponsePage2 = produce(
getProjectsQueryResponse,
(getProjectsQueryResponseDraft) => {
/* eslint-disable no-param-reassign */
getProjectsQueryResponseDraft.data.projects.pageInfo.hasNextPage = false;
getProjectsQueryResponseDraft.data.projects.pageInfo.endCursor = null;
getProjectsQueryResponseDraft.data.projects.nodes.splice(1, 1);
getProjectsQueryResponseDraft.data.projects.nodes[0].id = 'gid://gitlab/Project/100';
/* eslint-enable no-param-reassign */
},
);
const runDebounce = () => jest.runAllTimers();
const { pageInfo, nodes: projects } = getProjectsQueryResponse.data.projects;
const project1 = projects[0];
const project2 = projects[1];
let wrapper;
let resolveGetProjectsQuery;
let resolveGetInitialProjectsQuery;
const getProjectsQueryRequestHandler = jest.fn(
({ ids }) =>
new Promise((resolve) => {
if (ids) {
resolveGetInitialProjectsQuery = resolve;
} else {
resolveGetProjectsQuery = resolve;
}
}),
);
const createComponent = ({
propsData = {},
apolloProvider = createMockApollo([[getProjectsQuery, getProjectsQueryRequestHandler]]),
resolveQueries = true,
} = {}) => {
Vue.use(VueApollo);
wrapper = extendedWrapper(
mount(ProjectsTokenSelector, {
apolloProvider,
propsData: {
selectedProjects: [],
initialProjectIds: [],
...propsData,
},
stubs: ['gl-intersection-observer'],
}),
);
runDebounce();
if (resolveQueries) {
resolveGetProjectsQuery(getProjectsQueryResponse);
return waitForPromises();
}
return Promise.resolve();
};
const findTokenSelector = () => wrapper.findComponent(GlTokenSelector);
const findTokenSelectorInput = () => findTokenSelector().find('input[type="text"]');
const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver);
it('renders dropdown items with project avatars', async () => {
await createComponent();
wrapper.findAllComponents(GlAvatarLabeled).wrappers.forEach((avatarLabeledWrapper, index) => {
const project = projects[index];
expect(avatarLabeledWrapper.attributes()).toEqual(
expect.objectContaining({
'entity-id': `${getIdFromGraphQLId(project.id)}`,
'entity-name': project.name,
...(project.avatarUrl && { src: project.avatarUrl }),
}),
);
expect(avatarLabeledWrapper.props()).toEqual(
expect.objectContaining({
label: project.name,
subLabel: project.nameWithNamespace,
}),
);
});
});
it('renders tokens with project avatars', () => {
createComponent({
propsData: {
selectedProjects: [{ ...project2, id: getIdFromGraphQLId(project2.id) }],
},
});
const token = wrapper.findComponent(GlToken);
const avatar = token.findComponent(GlAvatar);
expect(token.text()).toContain(project2.nameWithNamespace);
expect(avatar.attributes('src')).toBe(project2.avatarUrl);
expect(avatar.props()).toEqual(
expect.objectContaining({
entityId: getIdFromGraphQLId(project2.id),
entityName: project2.name,
}),
);
});
describe('when `enter` key is pressed', () => {
it('calls `preventDefault` so form is not submitted when user selects a project from the dropdown', () => {
createComponent();
const event = {
preventDefault: jest.fn(),
};
findTokenSelectorInput().trigger('keydown.enter', event);
expect(event.preventDefault).toHaveBeenCalled();
});
});
describe('when text input is typed in', () => {
const searchTerm = 'foo bar';
beforeEach(async () => {
await createComponent();
await findTokenSelectorInput().setValue(searchTerm);
runDebounce();
});
it('makes GraphQL request with `search` variable set', async () => {
expect(getProjectsQueryRequestHandler).toHaveBeenLastCalledWith({
search: searchTerm,
after: null,
first: 20,
ids: null,
});
});
it('sets loading state while waiting for GraphQL request to resolve', async () => {
expect(findTokenSelector().props('loading')).toBe(true);
resolveGetProjectsQuery(getProjectsQueryResponse);
await waitForPromises();
expect(findTokenSelector().props('loading')).toBe(false);
});
});
describe('when there is a next page of projects and user scrolls to the bottom of the dropdown', () => {
beforeEach(async () => {
await createComponent();
findIntersectionObserver().vm.$emit('appear');
});
it('makes GraphQL request with `after` variable set', async () => {
expect(getProjectsQueryRequestHandler).toHaveBeenLastCalledWith({
after: pageInfo.endCursor,
first: 20,
search: '',
ids: null,
});
});
it('displays loading icon while waiting for GraphQL request to resolve', async () => {
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
resolveGetProjectsQuery(getProjectsQueryResponsePage2);
await waitForPromises();
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
});
});
describe('when there is not a next page of projects', () => {
it('does not render `GlIntersectionObserver`', async () => {
createComponent({ resolveQueries: false });
resolveGetProjectsQuery(getProjectsQueryResponsePage2);
await waitForPromises();
expect(findIntersectionObserver().exists()).toBe(false);
});
});
describe('when `GlTokenSelector` emits `input` event', () => {
it('emits `input` event used by `v-model`', () => {
findTokenSelector().vm.$emit('input', project1);
expect(wrapper.emitted('input')[0]).toEqual([project1]);
});
});
describe('when `GlTokenSelector` emits `focus` event', () => {
it('emits `focus` event', () => {
const event = { fakeEvent: 'foo' };
findTokenSelector().vm.$emit('focus', event);
expect(wrapper.emitted('focus')[0]).toEqual([event]);
});
});
describe('when `initialProjectIds` is an empty array', () => {
it('does not request initial projects', async () => {
await createComponent();
expect(getProjectsQueryRequestHandler).toHaveBeenCalledTimes(1);
expect(getProjectsQueryRequestHandler).toHaveBeenCalledWith(
expect.objectContaining({
ids: null,
}),
);
});
});
describe('when `initialProjectIds` is an array of project IDs', () => {
it('requests those projects and emits `input` event with result', async () => {
await createComponent({
propsData: {
initialProjectIds: [getIdFromGraphQLId(project1.id), getIdFromGraphQLId(project2.id)],
},
});
resolveGetInitialProjectsQuery(getProjectsQueryResponse);
await waitForPromises();
expect(getProjectsQueryRequestHandler).toHaveBeenCalledWith({
after: '',
first: null,
search: '',
ids: [project1.id, project2.id],
});
expect(wrapper.emitted('input')[0][0]).toEqual([
{ ...project1, id: getIdFromGraphQLId(project1.id) },
{ ...project2, id: getIdFromGraphQLId(project2.id) },
]);
});
});
});