Merge branch '41672-emphasize-gke-cluster-to-new-users' into 'master'

Add feature highlight blue dot to GKE "Clusters" sidebar item

Closes #41672

See merge request gitlab-org/gitlab-ce!16379
This commit is contained in:
Clement Ho 2018-02-05 21:11:07 +00:00
commit 7c8e7a8d1f
20 changed files with 687 additions and 7 deletions

View file

@ -1 +1 @@
{"iconCount":189,"spriteSize":85900,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","bookmark","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder-o","folder-open","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-external","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","podcast","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","staged","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_notfound","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","unstaged","user","users","volume-up","warning","work"]}
{"iconCount":191,"spriteSize":86607,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","bookmark","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder-o","folder-open","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-external","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","podcast","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","soft-unwrap","soft-wrap","spam","spinner","staged","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_notfound","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","unstaged","user","users","volume-up","warning","work"]}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="142" height="104" viewBox="0 0 142 104"><g fill="none" fill-rule="evenodd"><g transform="translate(112 4)"><path fill="#FFF" d="M8 4a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h14a4 4 0 0 0 4-4V8a4 4 0 0 0-4-4H8z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M8 4a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h14a4 4 0 0 0 4-4V8a4 4 0 0 0-4-4H8zm0-4h14a8 8 0 0 1 8 8v14a8 8 0 0 1-8 8H8a8 8 0 0 1-8-8V8a8 8 0 0 1 8-8z"/><rect width="10" height="10" x="10" y="10" fill="#FC6D26" rx="5"/></g><g transform="translate(5 74)"><rect width="30" height="30" fill="#FFF" rx="8"/><path fill="#E1DBF1" fill-rule="nonzero" d="M8 4a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h14a4 4 0 0 0 4-4V8a4 4 0 0 0-4-4H8zm0-4h14a8 8 0 0 1 8 8v14a8 8 0 0 1-8 8H8a8 8 0 0 1-8-8V8a8 8 0 0 1 8-8z"/><rect width="10" height="10" x="10" y="10" fill="#6B4FBB" rx="5"/></g><path fill="#FFF" d="M6 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H6z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M6 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H6zm0-4h12a6 6 0 0 1 6 6v12a6 6 0 0 1-6 6H6a6 6 0 0 1-6-6V6a6 6 0 0 1 6-6z"/><rect width="8" height="8" x="8" y="8" fill="#FC6D26" rx="4"/><g transform="translate(112 77)"><rect width="24" height="24" fill="#FFF" rx="6"/><path fill="#E1DBF1" fill-rule="nonzero" d="M6 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H6zm0-4h12a6 6 0 0 1 6 6v12a6 6 0 0 1-6 6H6a6 6 0 0 1-6-6V6a6 6 0 0 1 6-6z"/><rect width="8" height="8" x="8" y="8" fill="#6B4FBB" rx="4"/></g><g transform="translate(46 29)"><rect width="46" height="46" y="2" fill="#E1DBF1" rx="10"/><rect width="46" height="46" fill="#E1DBF1" rx="10"/><path fill="#C3B8E3" fill-rule="nonzero" d="M10 4a6 6 0 0 0-6 6v26a6 6 0 0 0 6 6h26a6 6 0 0 0 6-6V10a6 6 0 0 0-6-6H10zm0-4h26c5.523 0 10 4.477 10 10v26c0 5.523-4.477 10-10 10H10C4.477 46 0 41.523 0 36V10C0 4.477 4.477 0 10 0z"/><rect width="14" height="14" x="16" y="16" fill="#6B4FBB" rx="2"/></g><path fill="#E1DBF1" fill-rule="nonzero" d="M98.413 35.682a2 2 0 1 1-2.826-2.83l2.122-2.12a2 2 0 1 1 2.827 2.83l-2.123 2.12z"/><path fill="#C3B8E3" d="M104.78 29.32a2 2 0 0 1-2.826-2.829l2.122-2.12a2 2 0 0 1 2.827 2.83l-2.122 2.12z"/><path fill="#C3B8E3" fill-rule="nonzero" d="M42.413 89.682a2 2 0 1 1-2.826-2.83l2.122-2.12a2 2 0 1 1 2.827 2.83l-2.123 2.12z"/><path fill="#E1DBF1" d="M48.78 83.32a2 2 0 1 1-2.826-2.829l2.122-2.12a2 2 0 1 1 2.827 2.83l-2.122 2.12z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M27.713 26.531a2 2 0 1 1 2.574-3.062l2.296 1.93a2 2 0 1 1-2.573 3.062l-2.297-1.93z"/><path fill="#C3B8E3" d="M34.604 32.321a2 2 0 1 1 2.573-3.062l2.297 1.93A2 2 0 0 1 36.9 34.25l-2.297-1.93z"/><path fill="#C3B8E3" fill-rule="nonzero" d="M93.74 74.553a2 2 0 0 1 2.52-3.106l2.33 1.891a2 2 0 1 1-2.521 3.106l-2.33-1.891z"/><path fill="#E1DBF1" d="M100.727 80.225a2 2 0 1 1 2.521-3.105l2.33 1.89a2 2 0 1 1-2.522 3.106l-2.33-1.89z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,65 @@
import _ from 'underscore';
import {
getSelector,
togglePopover,
inserted,
mouseenter,
mouseleave,
} from './feature_highlight_helper';
export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
const $selector = $(getSelector(id));
const $parent = $selector.parent();
const $popoverContent = $parent.siblings('.feature-highlight-popover-content');
const hideOnScroll = togglePopover.bind($selector, false);
const debouncedMouseleave = _.debounce(mouseleave, debounceTimeout);
$selector
// Setup popover
.data('content', $popoverContent.prop('outerHTML'))
.popover({
html: true,
// Override the existing template to add custom CSS classes
template: `
<div class="popover feature-highlight-popover" role="tooltip">
<div class="arrow"></div>
<div class="popover-content"></div>
</div>
`,
})
.on('mouseenter', mouseenter)
.on('mouseleave', debouncedMouseleave)
.on('inserted.bs.popover', inserted)
.on('show.bs.popover', () => {
window.addEventListener('scroll', hideOnScroll);
})
.on('hide.bs.popover', () => {
window.removeEventListener('scroll', hideOnScroll);
})
// Display feature highlight
.removeAttr('disabled');
}
export function findHighestPriorityFeature() {
let priorityFeature;
const sortedFeatureEls = [].slice.call(document.querySelectorAll('.js-feature-highlight')).sort((a, b) =>
(a.dataset.highlightPriority || 0) < (b.dataset.highlightPriority || 0));
const [priorityFeatureEl] = sortedFeatureEls;
if (priorityFeatureEl) {
priorityFeature = priorityFeatureEl.dataset.highlight;
}
return priorityFeature;
}
export function highlightFeatures() {
const priorityFeature = findHighestPriorityFeature();
if (priorityFeature) {
setupFeatureHighlightPopover(priorityFeature);
}
return priorityFeature;
}

View file

@ -0,0 +1,59 @@
import axios from '../lib/utils/axios_utils';
import { __ } from '../locale';
import Flash from '../flash';
import LazyLoader from '../lazy_loader';
export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`;
export function togglePopover(show) {
const isAlreadyShown = this.hasClass('js-popover-show');
if ((show && isAlreadyShown) || (!show && !isAlreadyShown)) {
return false;
}
this.popover(show ? 'show' : 'hide');
this.toggleClass('disable-animation js-popover-show', show);
return true;
}
export function dismiss(highlightId) {
axios.post(this.attr('data-dismiss-endpoint'), {
feature_name: highlightId,
})
.catch(() => Flash(__('An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again.')));
togglePopover.call(this, false);
this.hide();
}
export function mouseleave() {
if (!$('.popover:hover').length > 0) {
const $featureHighlight = $(this);
togglePopover.call($featureHighlight, false);
}
}
export function mouseenter() {
const $featureHighlight = $(this);
const showedPopover = togglePopover.call($featureHighlight, true);
if (showedPopover) {
$('.popover')
.on('mouseleave', mouseleave.bind($featureHighlight));
}
}
export function inserted() {
const popoverId = this.getAttribute('aria-describedby');
const highlightId = this.dataset.highlight;
const $popover = $(this);
const dismissWrapper = dismiss.bind($popover, highlightId);
$(`#${popoverId} .dismiss-feature-highlight`)
.on('click', dismissWrapper);
const lazyImg = $(`#${popoverId} .feature-highlight-illustration`)[0];
if (lazyImg) {
LazyLoader.loadImage(lazyImg);
}
}

View file

@ -0,0 +1,12 @@
import { highlightFeatures } from './feature_highlight';
import bp from '../breakpoints';
export default function domContentLoaded() {
if (bp.getBreakpointSize() === 'lg') {
highlightFeatures();
return true;
}
return false;
}
document.addEventListener('DOMContentLoaded', domContentLoaded);

View file

@ -26,6 +26,7 @@ import './gl_dropdown';
import initTodoToggle from './header';
import initImporterStatus from './importer_status';
import initLayoutNav from './layout_nav';
import './feature_highlight/feature_highlight_options';
import LazyLoader from './lazy_loader';
import initLogoAnimation from './logo';
import './milestone_select';

View file

@ -61,3 +61,4 @@
@import "framework/responsive_tables";
@import "framework/stacked-progress-bar";
@import "framework/ci_variable_list";
@import "framework/feature_highlight";

View file

@ -0,0 +1,103 @@
.feature-highlight {
position: relative;
margin-left: $gl-padding;
width: 20px;
height: 20px;
cursor: pointer;
&::before {
content: '';
display: block;
position: absolute;
top: 6px;
left: 6px;
width: 8px;
height: 8px;
background-color: $blue-500;
border-radius: 50%;
box-shadow: 0 0 0 rgba($blue-500, 0.4);
animation: pulse-highlight 2s infinite;
}
&:hover::before,
&.disable-animation::before {
animation: none;
}
&[disabled]::before {
display: none;
}
}
.is-showing-fly-out {
.feature-highlight {
display: none;
}
}
.feature-highlight-popover-content {
display: none;
hr {
margin: $gl-padding * 0.5 0;
}
.btn-link {
svg {
@include btn-svg;
path {
fill: currentColor;
}
}
}
.feature-highlight-illustration {
width: 100%;
height: 100px;
padding-top: 12px;
padding-bottom: 12px;
background-color: $indigo-50;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
border-bottom: 1px solid darken($gray-normal, 8%);
}
}
.popover .feature-highlight-popover-content {
display: block;
}
.feature-highlight-popover {
width: 240px;
padding: 0;
border: 1px solid $dropdown-border-color;
box-shadow: 0 2px 4px $dropdown-shadow-color;
&.right > .arrow {
border-right-color: $dropdown-border-color;
}
.popover-content {
padding: 0;
}
}
.feature-highlight-popover-sub-content {
padding: 9px 14px;
}
@include keyframes(pulse-highlight) {
0% {
box-shadow: 0 0 0 0 rgba($blue-200, 0.4);
}
70% {
box-shadow: 0 0 0 10px transparent;
}
100% {
box-shadow: 0 0 0 0 transparent;
}
}

View file

@ -9,6 +9,6 @@ module UserCalloutsHelper
private
def user_dismissed?(feature_name)
current_user&.callouts&.find_by(feature_name: feature_name)
current_user&.callouts&.find_by(feature_name: UserCallout.feature_names[feature_name])
end
end

View file

@ -184,10 +184,34 @@
Environments
- if project_nav_tab? :clusters
- show_cluster_hint = show_gke_cluster_integration_callout?(@project)
= nav_link(controller: [:clusters, :user, :gcp]) do
= link_to project_clusters_path(@project), title: 'Cluster', class: 'shortcuts-cluster' do
%span
Clusters
- if show_cluster_hint
.feature-highlight.js-feature-highlight{ disabled: true,
data: { trigger: 'manual',
container: 'body',
toggle: 'popover',
placement: 'right',
highlight: UserCalloutsHelper::GKE_CLUSTER_INTEGRATION,
highlight_priority: UserCallout.feature_names[:GKE_CLUSTER_INTEGRATION],
dismiss_endpoint: user_callouts_path } }
- if show_cluster_hint
.feature-highlight-popover-content
= image_tag 'illustrations/cluster_popover.svg', class: 'feature-highlight-illustration'
.feature-highlight-popover-sub-content
%p= _('Allows you to add and manage Kubernetes clusters.')
%p
= _('Protip:')
= link_to 'Auto DevOps', help_page_path('topics/autodevops/index.md')
%span= _('uses clusters to deploy your code!')
%hr
%button.btn.btn-create.btn-xs.dismiss-feature-highlight{ type: 'button' }
%span= _("Got it!")
= sprite_icon('thumb-up')
- if @project.feature_available?(:builds, current_user) && !@project.empty_repo?
= nav_link(path: 'pipelines#charts') do

View file

@ -0,0 +1,5 @@
---
title: Add blue dot feature highlight to make GKE Clusters more visible to users
merge_request: 16379
author:
type: added

View file

@ -0,0 +1,15 @@
# Feature highlight
> [Introduced][ce-16379] in GitLab 10.5
Feature highlights are represented by a pulsing blue dot. Hovering over the dot
will open up callout with more information.
They are used to emphasize a certain feature and make something more visible to the user.
You can dismiss any feature highlight permanently by clicking the "Got it" link
at the bottom of the callout. There isn't a way to restore the feature highlight
after it has been dismissed.
![Clusters feature highlight](img/feature_highlight_example.png)
[ce-16379]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/16379

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -107,6 +107,8 @@ personal access tokens, authorized applications, etc.
methods available in GitLab.
- [Permissions](permissions.md): Learn the different set of permissions levels for each
user type (guest, reporter, developer, master, owner).
- [Feature highlight](feature_highlight.md): Learn more about the little blue dots
around the app that explain certain features
## Groups

View file

@ -88,7 +88,7 @@
"worker-loader": "^1.1.0"
},
"devDependencies": {
"@gitlab-org/gitlab-svgs": "^1.7.0",
"@gitlab-org/gitlab-svgs": "^1.8.0",
"axios-mock-adapter": "^1.10.0",
"babel-plugin-istanbul": "^4.1.5",
"eslint": "^3.18.0",

View file

@ -0,0 +1,231 @@
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import {
getSelector,
togglePopover,
dismiss,
mouseleave,
mouseenter,
inserted,
} from '~/feature_highlight/feature_highlight_helper';
import getSetTimeoutPromise from '../helpers/set_timeout_promise_helper';
describe('feature highlight helper', () => {
describe('getSelector', () => {
it('returns js-feature-highlight selector', () => {
const highlightId = 'highlightId';
expect(getSelector(highlightId)).toEqual(`.js-feature-highlight[data-highlight=${highlightId}]`);
});
});
describe('togglePopover', () => {
describe('togglePopover(true)', () => {
it('returns true when popover is shown', () => {
const context = {
hasClass: () => false,
popover: () => {},
toggleClass: () => {},
};
expect(togglePopover.call(context, true)).toEqual(true);
});
it('returns false when popover is already shown', () => {
const context = {
hasClass: () => true,
};
expect(togglePopover.call(context, true)).toEqual(false);
});
it('shows popover', (done) => {
const context = {
hasClass: () => false,
popover: () => {},
toggleClass: () => {},
};
spyOn(context, 'popover').and.callFake((method) => {
expect(method).toEqual('show');
done();
});
togglePopover.call(context, true);
});
it('adds disable-animation and js-popover-show class', (done) => {
const context = {
hasClass: () => false,
popover: () => {},
toggleClass: () => {},
};
spyOn(context, 'toggleClass').and.callFake((classNames, show) => {
expect(classNames).toEqual('disable-animation js-popover-show');
expect(show).toEqual(true);
done();
});
togglePopover.call(context, true);
});
});
describe('togglePopover(false)', () => {
it('returns true when popover is hidden', () => {
const context = {
hasClass: () => true,
popover: () => {},
toggleClass: () => {},
};
expect(togglePopover.call(context, false)).toEqual(true);
});
it('returns false when popover is already hidden', () => {
const context = {
hasClass: () => false,
};
expect(togglePopover.call(context, false)).toEqual(false);
});
it('hides popover', (done) => {
const context = {
hasClass: () => true,
popover: () => {},
toggleClass: () => {},
};
spyOn(context, 'popover').and.callFake((method) => {
expect(method).toEqual('hide');
done();
});
togglePopover.call(context, false);
});
it('removes disable-animation and js-popover-show class', (done) => {
const context = {
hasClass: () => true,
popover: () => {},
toggleClass: () => {},
};
spyOn(context, 'toggleClass').and.callFake((classNames, show) => {
expect(classNames).toEqual('disable-animation js-popover-show');
expect(show).toEqual(false);
done();
});
togglePopover.call(context, false);
});
});
});
describe('dismiss', () => {
let mock;
const context = {
hide: () => {},
attr: () => '/-/callouts/dismiss',
};
beforeEach(() => {
mock = new MockAdapter(axios);
spyOn(togglePopover, 'call').and.callFake(() => {});
spyOn(context, 'hide').and.callFake(() => {});
dismiss.call(context);
});
afterEach(() => {
mock.restore();
});
it('calls persistent dismissal endpoint', (done) => {
const spy = jasmine.createSpy('dismiss-endpoint-hit');
mock.onPost('/-/callouts/dismiss').reply(spy);
getSetTimeoutPromise()
.then(() => {
expect(spy).toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
});
it('calls hide popover', () => {
expect(togglePopover.call).toHaveBeenCalledWith(context, false);
});
it('calls hide', () => {
expect(context.hide).toHaveBeenCalled();
});
});
describe('mouseleave', () => {
it('calls hide popover if .popover:hover is false', () => {
const fakeJquery = {
length: 0,
};
spyOn($.fn, 'init').and.callFake(selector => (selector === '.popover:hover' ? fakeJquery : $.fn));
spyOn(togglePopover, 'call');
mouseleave();
expect(togglePopover.call).toHaveBeenCalledWith(jasmine.any(Object), false);
});
it('does not call hide popover if .popover:hover is true', () => {
const fakeJquery = {
length: 1,
};
spyOn($.fn, 'init').and.callFake(selector => (selector === '.popover:hover' ? fakeJquery : $.fn));
spyOn(togglePopover, 'call');
mouseleave();
expect(togglePopover.call).not.toHaveBeenCalledWith(false);
});
});
describe('mouseenter', () => {
const context = {};
it('shows popover', () => {
spyOn(togglePopover, 'call').and.returnValue(false);
mouseenter.call(context);
expect(togglePopover.call).toHaveBeenCalledWith(jasmine.any(Object), true);
});
it('registers mouseleave event if popover is showed', (done) => {
spyOn(togglePopover, 'call').and.returnValue(true);
spyOn($.fn, 'on').and.callFake((eventName) => {
expect(eventName).toEqual('mouseleave');
done();
});
mouseenter.call(context);
});
it('does not register mouseleave event if popover is not showed', () => {
spyOn(togglePopover, 'call').and.returnValue(false);
const spy = spyOn($.fn, 'on').and.callFake(() => {});
mouseenter.call(context);
expect(spy).not.toHaveBeenCalled();
});
});
describe('inserted', () => {
it('registers click event callback', (done) => {
const context = {
getAttribute: () => 'popoverId',
dataset: {
highlight: 'some-feature',
},
};
spyOn($.fn, 'on').and.callFake((event) => {
expect(event).toEqual('click');
done();
});
inserted.call(context);
});
});
});

View file

@ -0,0 +1,30 @@
import domContentLoaded from '~/feature_highlight/feature_highlight_options';
import bp from '~/breakpoints';
describe('feature highlight options', () => {
describe('domContentLoaded', () => {
it('should not call highlightFeatures when breakpoint is xs', () => {
spyOn(bp, 'getBreakpointSize').and.returnValue('xs');
expect(domContentLoaded()).toBe(false);
});
it('should not call highlightFeatures when breakpoint is sm', () => {
spyOn(bp, 'getBreakpointSize').and.returnValue('sm');
expect(domContentLoaded()).toBe(false);
});
it('should not call highlightFeatures when breakpoint is md', () => {
spyOn(bp, 'getBreakpointSize').and.returnValue('md');
expect(domContentLoaded()).toBe(false);
});
it('should call highlightFeatures when breakpoint is lg', () => {
spyOn(bp, 'getBreakpointSize').and.returnValue('lg');
expect(domContentLoaded()).toBe(true);
});
});
});

View file

@ -0,0 +1,131 @@
import * as featureHighlightHelper from '~/feature_highlight/feature_highlight_helper';
import * as featureHighlight from '~/feature_highlight/feature_highlight';
describe('feature highlight', () => {
beforeEach(() => {
setFixtures(`
<div>
<div class="js-feature-highlight" data-highlight="test" data-highlight-priority="10" disabled>
Trigger
</div>
</div>
<div class="feature-highlight-popover-content">
Content
<div class="dismiss-feature-highlight">
Dismiss
</div>
</div>
`);
});
describe('setupFeatureHighlightPopover', () => {
const selector = '.js-feature-highlight[data-highlight=test]';
beforeEach(() => {
spyOn(window, 'addEventListener');
spyOn(window, 'removeEventListener');
featureHighlight.setupFeatureHighlightPopover('test', 0);
});
it('setup popover content', () => {
const $popoverContent = $('.feature-highlight-popover-content');
const outerHTML = $popoverContent.prop('outerHTML');
expect($(selector).data('content')).toEqual(outerHTML);
});
it('setup mouseenter', () => {
const toggleSpy = spyOn(featureHighlightHelper.togglePopover, 'call');
$(selector).trigger('mouseenter');
expect(toggleSpy).toHaveBeenCalledWith(jasmine.any(Object), true);
});
it('setup debounced mouseleave', (done) => {
const toggleSpy = spyOn(featureHighlightHelper.togglePopover, 'call');
$(selector).trigger('mouseleave');
// Even though we've set the debounce to 0ms, setTimeout is needed for the debounce
setTimeout(() => {
expect(toggleSpy).toHaveBeenCalledWith(jasmine.any(Object), false);
done();
}, 0);
});
it('setup inserted.bs.popover', () => {
$(selector).trigger('mouseenter');
const popoverId = $(selector).attr('aria-describedby');
const spyEvent = spyOnEvent(`#${popoverId} .dismiss-feature-highlight`, 'click');
$(`#${popoverId} .dismiss-feature-highlight`).click();
expect(spyEvent).toHaveBeenTriggered();
});
it('setup show.bs.popover', () => {
$(selector).trigger('show.bs.popover');
expect(window.addEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function));
});
it('setup hide.bs.popover', () => {
$(selector).trigger('hide.bs.popover');
expect(window.removeEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function));
});
it('removes disabled attribute', () => {
expect($('.js-feature-highlight').is(':disabled')).toEqual(false);
});
it('displays popover', () => {
expect($(selector).attr('aria-describedby')).toBeFalsy();
$(selector).trigger('mouseenter');
expect($(selector).attr('aria-describedby')).toBeTruthy();
});
});
describe('findHighestPriorityFeature', () => {
beforeEach(() => {
setFixtures(`
<div class="js-feature-highlight" data-highlight="test" data-highlight-priority="10" disabled></div>
<div class="js-feature-highlight" data-highlight="test-high-priority" data-highlight-priority="20" disabled></div>
<div class="js-feature-highlight" data-highlight="test-low-priority" data-highlight-priority="0" disabled></div>
`);
});
it('should pick the highest priority feature highlight', () => {
setFixtures(`
<div class="js-feature-highlight" data-highlight="test" data-highlight-priority="10" disabled></div>
<div class="js-feature-highlight" data-highlight="test-high-priority" data-highlight-priority="20" disabled></div>
<div class="js-feature-highlight" data-highlight="test-low-priority" data-highlight-priority="0" disabled></div>
`);
expect($('.js-feature-highlight').length).toBeGreaterThan(1);
expect(featureHighlight.findHighestPriorityFeature()).toEqual('test-high-priority');
});
it('should work when no priority is set', () => {
setFixtures(`
<div class="js-feature-highlight" data-highlight="test" disabled></div>
`);
expect(featureHighlight.findHighestPriorityFeature()).toEqual('test');
});
it('should pick the highest priority feature highlight when some have no priority set', () => {
setFixtures(`
<div class="js-feature-highlight" data-highlight="test-no-priority1" disabled></div>
<div class="js-feature-highlight" data-highlight="test" data-highlight-priority="10" disabled></div>
<div class="js-feature-highlight" data-highlight="test-no-priority2" disabled></div>
<div class="js-feature-highlight" data-highlight="test-high-priority" data-highlight-priority="20" disabled></div>
<div class="js-feature-highlight" data-highlight="test-low-priority" data-highlight-priority="0" disabled></div>
`);
expect($('.js-feature-highlight').length).toBeGreaterThan(1);
expect(featureHighlight.findHighestPriorityFeature()).toEqual('test-high-priority');
});
});
describe('highlightFeatures', () => {
it('calls setupFeatureHighlightPopover', () => {
expect(featureHighlight.highlightFeatures()).toEqual('test');
});
});
});

View file

@ -54,9 +54,9 @@
lodash "^4.2.0"
to-fast-properties "^2.0.0"
"@gitlab-org/gitlab-svgs@^1.7.0":
version "1.7.0"
resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.7.0.tgz#dbb1330a1b1ee478378dddab53fe1a881e810f5d"
"@gitlab-org/gitlab-svgs@^1.8.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.8.0.tgz#95d6afa94395860699ddad60a82bd1bbbc2ba89f"
"@types/jquery@^2.0.40":
version "2.0.48"