2023-03-17 16:20:25 +05:30
|
|
|
import { GlButton, GlIcon } from '@gitlab/ui';
|
2018-11-08 19:23:39 +05:30
|
|
|
import MockAdapter from 'axios-mock-adapter';
|
2022-04-04 11:22:00 +05:30
|
|
|
import Vue, { nextTick } from 'vue';
|
2021-06-08 01:23:25 +05:30
|
|
|
import Vuex from 'vuex';
|
2020-10-24 23:57:45 +05:30
|
|
|
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
|
2021-06-08 01:23:25 +05:30
|
|
|
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
2020-10-24 23:57:45 +05:30
|
|
|
import waitForPromises from 'helpers/wait_for_promises';
|
2021-06-08 01:23:25 +05:30
|
|
|
import App from '~/frequent_items/components/app.vue';
|
|
|
|
import FrequentItemsList from '~/frequent_items/components/frequent_items_list.vue';
|
2022-07-16 23:28:13 +05:30
|
|
|
import { FREQUENT_ITEMS, FIFTEEN_MINUTES_IN_MS } from '~/frequent_items/constants';
|
2021-03-11 19:13:27 +05:30
|
|
|
import eventHub from '~/frequent_items/event_hub';
|
|
|
|
import { createStore } from '~/frequent_items/store';
|
2018-11-08 19:23:39 +05:30
|
|
|
import { getTopFrequentItems } from '~/frequent_items/utils';
|
2021-03-11 19:13:27 +05:30
|
|
|
import axios from '~/lib/utils/axios_utils';
|
2023-04-23 21:23:45 +05:30
|
|
|
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
|
2018-11-08 19:23:39 +05:30
|
|
|
import { currentSession, mockFrequentProjects, mockSearchedProjects } from '../mock_data';
|
2020-05-24 23:13:21 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
Vue.use(Vuex);
|
|
|
|
|
2020-05-24 23:13:21 +05:30
|
|
|
useLocalStorageSpy();
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
const TEST_NAMESPACE = 'projects';
|
|
|
|
const TEST_VUEX_MODULE = 'frequentProjects';
|
|
|
|
const TEST_PROJECT = currentSession[TEST_NAMESPACE].project;
|
|
|
|
const TEST_STORAGE_KEY = currentSession[TEST_NAMESPACE].storageKey;
|
2021-09-04 01:27:46 +05:30
|
|
|
const TEST_SEARCH_CLASS = 'test-search-class';
|
2018-11-08 19:23:39 +05:30
|
|
|
|
|
|
|
describe('Frequent Items App Component', () => {
|
2021-06-08 01:23:25 +05:30
|
|
|
let wrapper;
|
2018-11-08 19:23:39 +05:30
|
|
|
let mock;
|
2021-06-08 01:23:25 +05:30
|
|
|
let store;
|
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
const createComponent = (props = {}) => {
|
2021-06-08 01:23:25 +05:30
|
|
|
const session = currentSession[TEST_NAMESPACE];
|
|
|
|
gon.api_version = session.apiVersion;
|
|
|
|
|
|
|
|
wrapper = mountExtended(App, {
|
|
|
|
store,
|
|
|
|
propsData: {
|
|
|
|
namespace: TEST_NAMESPACE,
|
|
|
|
currentUserName: session.username,
|
2021-09-04 01:27:46 +05:30
|
|
|
currentItem: session.project,
|
|
|
|
...props,
|
2021-06-08 01:23:25 +05:30
|
|
|
},
|
|
|
|
provide: {
|
|
|
|
vuexModule: TEST_VUEX_MODULE,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const triggerDropdownOpen = () => eventHub.$emit(`${TEST_NAMESPACE}-dropdownOpen`);
|
|
|
|
const getStoredProjects = () => JSON.parse(localStorage.getItem(TEST_STORAGE_KEY));
|
|
|
|
const findSearchInput = () => wrapper.findByTestId('frequent-items-search-input');
|
|
|
|
const findLoading = () => wrapper.findByTestId('loading');
|
|
|
|
const findSectionHeader = () => wrapper.findByTestId('header');
|
|
|
|
const findFrequentItemsList = () => wrapper.findComponent(FrequentItemsList);
|
|
|
|
const findFrequentItems = () => findFrequentItemsList().findAll('li');
|
|
|
|
const setSearch = (search) => {
|
|
|
|
const searchInput = wrapper.find('input');
|
|
|
|
|
|
|
|
searchInput.setValue(search);
|
|
|
|
};
|
2018-11-08 19:23:39 +05:30
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
mock = new MockAdapter(axios);
|
2021-06-08 01:23:25 +05:30
|
|
|
store = createStore();
|
2018-11-08 19:23:39 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
mock.restore();
|
|
|
|
});
|
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
describe('default', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
jest.spyOn(store, 'dispatch');
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
createComponent();
|
2018-11-08 19:23:39 +05:30
|
|
|
});
|
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
it('should fetch frequent items', () => {
|
|
|
|
triggerDropdownOpen();
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
expect(store.dispatch).toHaveBeenCalledWith(`${TEST_VUEX_MODULE}/fetchFrequentItems`);
|
|
|
|
});
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
it('should not fetch frequent items if detroyed', () => {
|
|
|
|
wrapper.destroy();
|
|
|
|
triggerDropdownOpen();
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
expect(store.dispatch).not.toHaveBeenCalledWith(`${TEST_VUEX_MODULE}/fetchFrequentItems`);
|
|
|
|
});
|
2021-03-11 19:13:27 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
it('should render search input', () => {
|
2021-09-04 01:27:46 +05:30
|
|
|
expect(findSearchInput().classes()).toEqual(['search-input-container']);
|
2021-06-08 01:23:25 +05:30
|
|
|
});
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
it('should render loading animation', async () => {
|
|
|
|
triggerDropdownOpen();
|
|
|
|
store.state[TEST_VUEX_MODULE].isLoadingItems = true;
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
await nextTick();
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
const loading = findLoading();
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
expect(loading.exists()).toBe(true);
|
|
|
|
expect(loading.find('[aria-label="Loading projects"]').exists()).toBe(true);
|
2023-03-17 16:20:25 +05:30
|
|
|
expect(findSectionHeader().exists()).toBe(false);
|
2021-06-08 01:23:25 +05:30
|
|
|
});
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
it('should render frequent projects list header', () => {
|
|
|
|
const sectionHeader = findSectionHeader();
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
expect(sectionHeader.exists()).toBe(true);
|
|
|
|
expect(sectionHeader.text()).toBe('Frequently visited');
|
|
|
|
});
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
it('should render searched projects list', async () => {
|
2023-04-23 21:23:45 +05:30
|
|
|
mock
|
|
|
|
.onGet(/\/api\/v4\/projects.json(.*)$/)
|
|
|
|
.replyOnce(HTTP_STATUS_OK, mockSearchedProjects.data);
|
2021-06-08 01:23:25 +05:30
|
|
|
|
|
|
|
setSearch('gitlab');
|
2022-04-04 11:22:00 +05:30
|
|
|
await nextTick();
|
2021-06-08 01:23:25 +05:30
|
|
|
|
|
|
|
expect(findLoading().exists()).toBe(true);
|
|
|
|
|
|
|
|
await waitForPromises();
|
|
|
|
|
|
|
|
expect(findFrequentItems().length).toBe(mockSearchedProjects.data.length);
|
|
|
|
expect(findFrequentItemsList().props()).toEqual(
|
|
|
|
expect.objectContaining({
|
|
|
|
items: mockSearchedProjects.data.map(
|
2022-08-27 11:52:29 +05:30
|
|
|
({
|
|
|
|
avatar_url: avatarUrl,
|
|
|
|
web_url: webUrl,
|
|
|
|
name_with_namespace: namespace,
|
|
|
|
...item
|
|
|
|
}) => ({
|
2021-06-08 01:23:25 +05:30
|
|
|
...item,
|
2022-08-27 11:52:29 +05:30
|
|
|
avatarUrl,
|
|
|
|
webUrl,
|
|
|
|
namespace,
|
2021-06-08 01:23:25 +05:30
|
|
|
}),
|
|
|
|
),
|
|
|
|
namespace: TEST_NAMESPACE,
|
|
|
|
hasSearchQuery: true,
|
|
|
|
isFetchFailed: false,
|
|
|
|
matcher: 'gitlab',
|
|
|
|
}),
|
|
|
|
);
|
2018-11-08 19:23:39 +05:30
|
|
|
});
|
2023-03-17 16:20:25 +05:30
|
|
|
|
|
|
|
describe('with frequent items list', () => {
|
|
|
|
const expectedResult = getTopFrequentItems(mockFrequentProjects);
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
localStorage.setItem(TEST_STORAGE_KEY, JSON.stringify(mockFrequentProjects));
|
|
|
|
triggerDropdownOpen();
|
|
|
|
await nextTick();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should render edit button within header', () => {
|
|
|
|
const itemEditButton = findSectionHeader().findComponent(GlButton);
|
|
|
|
|
|
|
|
expect(itemEditButton.exists()).toBe(true);
|
|
|
|
expect(itemEditButton.attributes('title')).toBe('Toggle edit mode');
|
|
|
|
expect(itemEditButton.findComponent(GlIcon).props('name')).toBe('pencil');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should render frequent projects list', () => {
|
|
|
|
expect(findFrequentItems().length).toBe(expectedResult.length);
|
|
|
|
expect(findFrequentItemsList().props()).toEqual({
|
|
|
|
items: expectedResult,
|
|
|
|
namespace: TEST_NAMESPACE,
|
|
|
|
hasSearchQuery: false,
|
|
|
|
isFetchFailed: false,
|
|
|
|
isItemRemovalFailed: false,
|
|
|
|
matcher: '',
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('dispatches action `toggleItemsListEditablity` when edit button is clicked', async () => {
|
|
|
|
const itemEditButton = findSectionHeader().findComponent(GlButton);
|
|
|
|
itemEditButton.vm.$emit('click');
|
|
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
|
|
expect(store.dispatch).toHaveBeenCalledWith(
|
|
|
|
`${TEST_VUEX_MODULE}/toggleItemsListEditablity`,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
2018-11-08 19:23:39 +05:30
|
|
|
});
|
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
describe('with searchClass', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
createComponent({ searchClass: TEST_SEARCH_CLASS });
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should render search input with searchClass', () => {
|
|
|
|
expect(findSearchInput().classes()).toEqual(['search-input-container', TEST_SEARCH_CLASS]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
describe('logging', () => {
|
|
|
|
it('when created, it should create a project storage entry and adds a project', () => {
|
|
|
|
createComponent();
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
expect(getStoredProjects()).toEqual([
|
|
|
|
expect.objectContaining({
|
|
|
|
frequency: 1,
|
|
|
|
lastAccessedOn: Date.now(),
|
|
|
|
}),
|
|
|
|
]);
|
|
|
|
});
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
describe('when created multiple times', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
createComponent();
|
|
|
|
wrapper.destroy();
|
|
|
|
createComponent();
|
|
|
|
wrapper.destroy();
|
2018-11-08 19:23:39 +05:30
|
|
|
});
|
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
it('should only log once', () => {
|
|
|
|
expect(getStoredProjects()).toEqual([
|
|
|
|
expect.objectContaining({
|
|
|
|
lastAccessedOn: Date.now(),
|
|
|
|
frequency: 1,
|
|
|
|
}),
|
|
|
|
]);
|
|
|
|
});
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2022-07-16 23:28:13 +05:30
|
|
|
it('should increase frequency, when created 15 minutes later', () => {
|
|
|
|
const fifteenMinutesLater = Date.now() + FIFTEEN_MINUTES_IN_MS + 1;
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2022-07-16 23:28:13 +05:30
|
|
|
jest.spyOn(Date, 'now').mockReturnValue(fifteenMinutesLater);
|
|
|
|
createComponent({ currentItem: { ...TEST_PROJECT, lastAccessedOn: fifteenMinutesLater } });
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
expect(getStoredProjects()).toEqual([
|
|
|
|
expect.objectContaining({
|
2022-07-16 23:28:13 +05:30
|
|
|
lastAccessedOn: fifteenMinutesLater,
|
2021-06-08 01:23:25 +05:30
|
|
|
frequency: 2,
|
|
|
|
}),
|
|
|
|
]);
|
2018-11-08 19:23:39 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
it('should always update project metadata', () => {
|
|
|
|
const oldProject = {
|
|
|
|
...TEST_PROJECT,
|
|
|
|
};
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
const newProject = {
|
|
|
|
...oldProject,
|
|
|
|
name: 'New Name',
|
|
|
|
avatarUrl: 'new/avatar.png',
|
|
|
|
namespace: 'New / Namespace',
|
|
|
|
webUrl: 'http://localhost/new/web/url',
|
|
|
|
};
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
createComponent({ currentItem: oldProject });
|
|
|
|
wrapper.destroy();
|
|
|
|
expect(getStoredProjects()).toEqual([expect.objectContaining(oldProject)]);
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
createComponent({ currentItem: newProject });
|
|
|
|
wrapper.destroy();
|
2018-11-08 19:23:39 +05:30
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
expect(getStoredProjects()).toEqual([expect.objectContaining(newProject)]);
|
2018-11-08 19:23:39 +05:30
|
|
|
});
|
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
it('should not add more than 20 projects in store', () => {
|
|
|
|
for (let id = 0; id < FREQUENT_ITEMS.MAX_COUNT + 10; id += 1) {
|
|
|
|
const project = {
|
|
|
|
...TEST_PROJECT,
|
|
|
|
id,
|
|
|
|
};
|
|
|
|
createComponent({ currentItem: project });
|
|
|
|
wrapper.destroy();
|
|
|
|
}
|
|
|
|
|
|
|
|
expect(getStoredProjects().length).toBe(FREQUENT_ITEMS.MAX_COUNT);
|
2018-11-08 19:23:39 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|