251 lines
7.7 KiB
JavaScript
251 lines
7.7 KiB
JavaScript
import MockAdapter from 'axios-mock-adapter';
|
|
import axios from '~/lib/utils/axios_utils';
|
|
import Vue from 'vue';
|
|
import appComponent from '~/frequent_items/components/app.vue';
|
|
import eventHub from '~/frequent_items/event_hub';
|
|
import store from '~/frequent_items/store';
|
|
import { FREQUENT_ITEMS, HOUR_IN_MS } from '~/frequent_items/constants';
|
|
import { getTopFrequentItems } from '~/frequent_items/utils';
|
|
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
|
import { currentSession, mockFrequentProjects, mockSearchedProjects } from '../mock_data';
|
|
|
|
let session;
|
|
const createComponentWithStore = (namespace = 'projects') => {
|
|
session = currentSession[namespace];
|
|
gon.api_version = session.apiVersion;
|
|
const Component = Vue.extend(appComponent);
|
|
|
|
return mountComponentWithStore(Component, {
|
|
store,
|
|
props: {
|
|
namespace,
|
|
currentUserName: session.username,
|
|
currentItem: session.project || session.group,
|
|
},
|
|
});
|
|
};
|
|
|
|
describe('Frequent Items App Component', () => {
|
|
let vm;
|
|
let mock;
|
|
|
|
beforeEach(() => {
|
|
mock = new MockAdapter(axios);
|
|
vm = createComponentWithStore();
|
|
});
|
|
|
|
afterEach(() => {
|
|
mock.restore();
|
|
vm.$destroy();
|
|
});
|
|
|
|
describe('methods', () => {
|
|
describe('dropdownOpenHandler', () => {
|
|
it('should fetch frequent items when no search has been previously made on desktop', () => {
|
|
spyOn(vm, 'fetchFrequentItems');
|
|
|
|
vm.dropdownOpenHandler();
|
|
|
|
expect(vm.fetchFrequentItems).toHaveBeenCalledWith();
|
|
});
|
|
});
|
|
|
|
describe('logItemAccess', () => {
|
|
let storage;
|
|
|
|
beforeEach(() => {
|
|
storage = {};
|
|
|
|
spyOn(window.localStorage, 'setItem').and.callFake((storageKey, value) => {
|
|
storage[storageKey] = value;
|
|
});
|
|
|
|
spyOn(window.localStorage, 'getItem').and.callFake(storageKey => {
|
|
if (storage[storageKey]) {
|
|
return storage[storageKey];
|
|
}
|
|
|
|
return null;
|
|
});
|
|
});
|
|
|
|
it('should create a project store if it does not exist and adds a project', () => {
|
|
vm.logItemAccess(session.storageKey, session.project);
|
|
|
|
const projects = JSON.parse(storage[session.storageKey]);
|
|
|
|
expect(projects.length).toBe(1);
|
|
expect(projects[0].frequency).toBe(1);
|
|
expect(projects[0].lastAccessedOn).toBeDefined();
|
|
});
|
|
|
|
it('should prevent inserting same report multiple times into store', () => {
|
|
vm.logItemAccess(session.storageKey, session.project);
|
|
vm.logItemAccess(session.storageKey, session.project);
|
|
|
|
const projects = JSON.parse(storage[session.storageKey]);
|
|
|
|
expect(projects.length).toBe(1);
|
|
});
|
|
|
|
it('should increase frequency of report if it was logged multiple times over the course of an hour', () => {
|
|
let projects;
|
|
const newTimestamp = Date.now() + HOUR_IN_MS + 1;
|
|
|
|
vm.logItemAccess(session.storageKey, session.project);
|
|
projects = JSON.parse(storage[session.storageKey]);
|
|
|
|
expect(projects[0].frequency).toBe(1);
|
|
|
|
vm.logItemAccess(session.storageKey, {
|
|
...session.project,
|
|
lastAccessedOn: newTimestamp,
|
|
});
|
|
projects = JSON.parse(storage[session.storageKey]);
|
|
|
|
expect(projects[0].frequency).toBe(2);
|
|
expect(projects[0].lastAccessedOn).not.toBe(session.project.lastAccessedOn);
|
|
});
|
|
|
|
it('should always update project metadata', () => {
|
|
let projects;
|
|
const oldProject = {
|
|
...session.project,
|
|
};
|
|
|
|
const newProject = {
|
|
...session.project,
|
|
name: 'New Name',
|
|
avatarUrl: 'new/avatar.png',
|
|
namespace: 'New / Namespace',
|
|
webUrl: 'http://localhost/new/web/url',
|
|
};
|
|
|
|
vm.logItemAccess(session.storageKey, oldProject);
|
|
projects = JSON.parse(storage[session.storageKey]);
|
|
|
|
expect(projects[0].name).toBe(oldProject.name);
|
|
expect(projects[0].avatarUrl).toBe(oldProject.avatarUrl);
|
|
expect(projects[0].namespace).toBe(oldProject.namespace);
|
|
expect(projects[0].webUrl).toBe(oldProject.webUrl);
|
|
|
|
vm.logItemAccess(session.storageKey, newProject);
|
|
projects = JSON.parse(storage[session.storageKey]);
|
|
|
|
expect(projects[0].name).toBe(newProject.name);
|
|
expect(projects[0].avatarUrl).toBe(newProject.avatarUrl);
|
|
expect(projects[0].namespace).toBe(newProject.namespace);
|
|
expect(projects[0].webUrl).toBe(newProject.webUrl);
|
|
});
|
|
|
|
it('should not add more than 20 projects in store', () => {
|
|
for (let id = 0; id < FREQUENT_ITEMS.MAX_COUNT; id += 1) {
|
|
const project = {
|
|
...session.project,
|
|
id,
|
|
};
|
|
vm.logItemAccess(session.storageKey, project);
|
|
}
|
|
|
|
const projects = JSON.parse(storage[session.storageKey]);
|
|
|
|
expect(projects.length).toBe(FREQUENT_ITEMS.MAX_COUNT);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('created', () => {
|
|
it('should bind event listeners on eventHub', done => {
|
|
spyOn(eventHub, '$on');
|
|
|
|
createComponentWithStore().$mount();
|
|
|
|
Vue.nextTick(() => {
|
|
expect(eventHub.$on).toHaveBeenCalledWith('projects-dropdownOpen', jasmine.any(Function));
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('beforeDestroy', () => {
|
|
it('should unbind event listeners on eventHub', done => {
|
|
spyOn(eventHub, '$off');
|
|
|
|
vm.$mount();
|
|
vm.$destroy();
|
|
|
|
Vue.nextTick(() => {
|
|
expect(eventHub.$off).toHaveBeenCalledWith('projects-dropdownOpen', jasmine.any(Function));
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('template', () => {
|
|
it('should render search input', () => {
|
|
expect(vm.$el.querySelector('.search-input-container')).toBeDefined();
|
|
});
|
|
|
|
it('should render loading animation', done => {
|
|
vm.$store.dispatch('fetchSearchedItems');
|
|
|
|
Vue.nextTick(() => {
|
|
const loadingEl = vm.$el.querySelector('.loading-animation');
|
|
|
|
expect(loadingEl).toBeDefined();
|
|
expect(loadingEl.classList.contains('prepend-top-20')).toBe(true);
|
|
expect(loadingEl.querySelector('i').getAttribute('aria-label')).toBe('Loading projects');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should render frequent projects list header', done => {
|
|
Vue.nextTick(() => {
|
|
const sectionHeaderEl = vm.$el.querySelector('.section-header');
|
|
|
|
expect(sectionHeaderEl).toBeDefined();
|
|
expect(sectionHeaderEl.innerText.trim()).toBe('Frequently visited');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should render frequent projects list', done => {
|
|
const expectedResult = getTopFrequentItems(mockFrequentProjects);
|
|
spyOn(window.localStorage, 'getItem').and.callFake(() =>
|
|
JSON.stringify(mockFrequentProjects),
|
|
);
|
|
|
|
expect(vm.$el.querySelectorAll('.frequent-items-list-container li').length).toBe(1);
|
|
|
|
vm.fetchFrequentItems();
|
|
Vue.nextTick(() => {
|
|
expect(vm.$el.querySelectorAll('.frequent-items-list-container li').length).toBe(
|
|
expectedResult.length,
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should render searched projects list', done => {
|
|
mock.onGet(/\/api\/v4\/projects.json(.*)$/).replyOnce(200, mockSearchedProjects);
|
|
|
|
expect(vm.$el.querySelectorAll('.frequent-items-list-container li').length).toBe(1);
|
|
|
|
vm.$store.dispatch('setSearchQuery', 'gitlab');
|
|
vm
|
|
.$nextTick()
|
|
.then(() => {
|
|
expect(vm.$el.querySelector('.loading-animation')).toBeDefined();
|
|
})
|
|
.then(vm.$nextTick)
|
|
.then(vm.$nextTick)
|
|
.then(() => {
|
|
expect(vm.$el.querySelectorAll('.frequent-items-list-container li').length).toBe(
|
|
mockSearchedProjects.length,
|
|
);
|
|
})
|
|
.then(done)
|
|
.catch(done.fail);
|
|
});
|
|
});
|
|
});
|