Commit 5a08ff83 authored by Martin Wortschack's avatar Martin Wortschack

Merge branch 'tz-preload-mr-list' into 'master'

Prefetches Issue, MR and ToDo Lists

See merge request gitlab-org/gitlab!76540
parents b60c838b 949bf033
......@@ -13,3 +13,42 @@ export default function findAndFollowLink(selector) {
visitUrl(link);
}
}
export function prefetchDocument(url) {
const newPrefetchLink = document.createElement('link');
newPrefetchLink.rel = 'prefetch';
newPrefetchLink.href = url;
newPrefetchLink.setAttribute('as', 'document');
document.head.appendChild(newPrefetchLink);
}
export function initPrefetchLinks(selector) {
document.querySelectorAll(selector).forEach((el) => {
let mouseOverTimer;
const mouseOutHandler = () => {
if (mouseOverTimer) {
clearTimeout(mouseOverTimer);
mouseOverTimer = undefined;
}
};
const mouseOverHandler = () => {
el.addEventListener('mouseout', mouseOutHandler, { once: true, passive: true });
mouseOverTimer = setTimeout(() => {
if (el.href) prefetchDocument(el.href);
// Only execute once
el.removeEventListener('mouseover', mouseOverHandler, true);
mouseOverTimer = undefined;
}, 100);
};
el.addEventListener('mouseover', mouseOverHandler, {
capture: true,
passive: true,
});
});
}
......@@ -14,6 +14,7 @@ import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { initRails } from '~/lib/utils/rails_ujs';
import * as popovers from '~/popovers';
import * as tooltips from '~/tooltips';
import { initPrefetchLinks } from '~/lib/utils/navigation_utility';
import initAlertHandler from './alert_handler';
import { addDismissFlashClickListener } from './flash';
import initTodoToggle from './header';
......@@ -90,6 +91,7 @@ function deferredInitialisation() {
initTopNav();
initBreadcrumbs();
initTodoToggle();
initPrefetchLinks('.js-prefetch-document');
initLogoAnimation();
initServicePingConsent();
initUserPopovers();
......
......@@ -46,7 +46,7 @@
= sprite_icon(search_menu_item.fetch(:icon))
- if header_link?(:issues)
= nav_link(path: 'dashboard#issues', html_options: { class: "user-counter" }) do
= link_to assigned_issues_dashboard_path, title: _('Issues'), class: 'dashboard-shortcuts-issues', aria: { label: _('Issues') },
= link_to assigned_issues_dashboard_path, title: _('Issues'), class: 'dashboard-shortcuts-issues js-prefetch-document', aria: { label: _('Issues') },
data: { qa_selector: 'issues_shortcut_button', toggle: 'tooltip', placement: 'bottom',
track_label: 'main_navigation',
track_action: 'click_issues_link',
......@@ -75,18 +75,18 @@
%li.dropdown-header
= _('Merge requests')
%li
= link_to assigned_mrs_dashboard_path, class: 'gl-display-flex! gl-align-items-center' do
= link_to assigned_mrs_dashboard_path, class: 'gl-display-flex! gl-align-items-center js-prefetch-document' do
= _('Assigned to you')
%span.badge.gl-badge.badge-pill.badge-muted.merge-request-badge.gl-ml-auto.js-assigned-mr-count{ class: "" }
= user_merge_requests_counts[:assigned]
%li
= link_to reviewer_mrs_dashboard_path, class: 'gl-display-flex! gl-align-items-center' do
= link_to reviewer_mrs_dashboard_path, class: 'gl-display-flex! gl-align-items-center js-prefetch-document' do
= _('Review requests for you')
%span.badge.gl-badge.badge-pill.badge-muted.merge-request-badge.gl-ml-auto.js-reviewer-mr-count{ class: "" }
= user_merge_requests_counts[:review_requested]
- if header_link?(:todos)
= nav_link(controller: 'dashboard/todos', html_options: { class: "user-counter" }) do
= link_to dashboard_todos_path, title: _('To-Do List'), aria: { label: _('To-Do List') }, class: 'shortcuts-todos',
= link_to dashboard_todos_path, title: _('To-Do List'), aria: { label: _('To-Do List') }, class: 'shortcuts-todos js-prefetch-document',
data: { qa_selector: 'todos_shortcut_button', toggle: 'tooltip', placement: 'bottom',
track_label: 'main_navigation',
track_action: 'click_to_do_link',
......
......@@ -9,7 +9,7 @@
.issuable-main-info
.merge-request-title.title
%span.merge-request-title-text.js-onboarding-mr-item
= link_to merge_request.title, merge_request_path(merge_request)
= link_to merge_request.title, merge_request_path(merge_request), class: 'js-prefetch-document'
- if merge_request.tasks?
%span.task-status.d-none.d-sm-inline-block
 
......
import findAndFollowLink from '~/lib/utils/navigation_utility';
import * as navigationUtils from '~/lib/utils/navigation_utility';
import { visitUrl } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility');
......@@ -21,3 +22,91 @@ describe('findAndFollowLink', () => {
expect(visitUrl).not.toHaveBeenCalled();
});
});
describe('prefetchDocument', () => {
it('creates a prefetch link tag', () => {
const linkElement = document.createElement('link');
jest.spyOn(document, 'createElement').mockImplementation(() => linkElement);
jest.spyOn(document.head, 'appendChild');
navigationUtils.prefetchDocument('index.htm');
expect(document.head.appendChild).toHaveBeenCalledWith(linkElement);
expect(linkElement.href).toEqual('http://test.host/index.htm');
expect(linkElement.rel).toEqual('prefetch');
expect(linkElement.getAttribute('as')).toEqual('document');
});
});
describe('initPrefetchLinks', () => {
let newLink;
beforeEach(() => {
newLink = document.createElement('a');
newLink.href = 'index_prefetch.htm';
newLink.classList.add('js-test-prefetch-link');
document.body.appendChild(newLink);
});
it('adds to all links mouse out handlers when hovered', () => {
const mouseOverEvent = new Event('mouseover');
jest.spyOn(newLink, 'addEventListener');
navigationUtils.initPrefetchLinks('.js-test-prefetch-link');
newLink.dispatchEvent(mouseOverEvent);
expect(newLink.addEventListener).toHaveBeenCalled();
});
it('it is not fired when less then 100ms over link', () => {
const mouseOverEvent = new Event('mouseover');
const mouseOutEvent = new Event('mouseout');
jest.spyOn(newLink, 'addEventListener');
jest.spyOn(navigationUtils, 'prefetchDocument').mockImplementation(() => true);
navigationUtils.initPrefetchLinks('.js-test-prefetch-link');
newLink.dispatchEvent(mouseOverEvent);
newLink.dispatchEvent(mouseOutEvent);
expect(navigationUtils.prefetchDocument).not.toHaveBeenCalled();
});
describe('executes correctly when hovering long enough', () => {
const mouseOverEvent = new Event('mouseover');
beforeEach(() => {
jest.useFakeTimers();
jest.spyOn(global, 'setTimeout');
jest.spyOn(newLink, 'removeEventListener');
});
it('calls prefetchDocument which adds to document', () => {
jest.spyOn(document.head, 'appendChild');
navigationUtils.initPrefetchLinks('.js-test-prefetch-link');
newLink.dispatchEvent(mouseOverEvent);
jest.runAllTimers();
expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 100);
expect(document.head.appendChild).toHaveBeenCalled();
});
it('removes Event Listener when fired so only done once', () => {
navigationUtils.initPrefetchLinks('.js-test-prefetch-link');
newLink.dispatchEvent(mouseOverEvent);
jest.runAllTimers();
expect(newLink.removeEventListener).toHaveBeenCalledWith(
'mouseover',
expect.any(Function),
true,
);
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment