Skip to content
Snippets Groups Projects
Commit e7bc0d7c authored by Clement Ho's avatar Clement Ho Committed by Phil Hughes
Browse files

Add feature highlight to Issue Boards in new navigation sidebar

parent 970af996
No related branches found
No related tags found
No related merge requests found
Showing
with 735 additions and 7 deletions
import Cookies from 'js-cookie';
import _ from 'underscore';
import {
getCookieName,
getSelector,
hidePopover,
setupDismissButton,
mouseenter,
mouseleave,
} from './feature_highlight_helper';
export const setupFeatureHighlightPopover = (id, debounceTimeout = 300) => {
const $selector = $(getSelector(id));
const $parent = $selector.parent();
const $popoverContent = $parent.siblings('.feature-highlight-popover-content');
const hideOnScroll = hidePopover.bind($selector);
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', setupDismissButton)
.on('show.bs.popover', () => {
window.addEventListener('scroll', hideOnScroll);
})
.on('hide.bs.popover', () => {
window.removeEventListener('scroll', hideOnScroll);
})
// Display feature highlight
.removeAttr('disabled');
};
export const shouldHighlightFeature = (id) => {
const element = document.querySelector(getSelector(id));
const previouslyDismissed = Cookies.get(getCookieName(id)) === 'true';
return element && !previouslyDismissed;
};
export const highlightFeatures = (highlightOrder) => {
const featureId = highlightOrder.find(shouldHighlightFeature);
if (featureId) {
setupFeatureHighlightPopover(featureId);
return true;
}
return false;
};
import Cookies from 'js-cookie';
export const getCookieName = cookieId => `feature-highlighted-${cookieId}`;
export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`;
export const showPopover = function showPopover() {
if (this.hasClass('js-popover-show')) {
return false;
}
this.popover('show');
this.addClass('disable-animation js-popover-show');
return true;
};
export const hidePopover = function hidePopover() {
if (!this.hasClass('js-popover-show')) {
return false;
}
this.popover('hide');
this.removeClass('disable-animation js-popover-show');
return true;
};
export const dismiss = function dismiss(cookieId) {
Cookies.set(getCookieName(cookieId), true);
hidePopover.call(this);
this.hide();
};
export const mouseleave = function mouseleave() {
if (!$('.popover:hover').length > 0) {
const $featureHighlight = $(this);
hidePopover.call($featureHighlight);
}
};
export const mouseenter = function mouseenter() {
const $featureHighlight = $(this);
const showedPopover = showPopover.call($featureHighlight);
if (showedPopover) {
$('.popover')
.on('mouseleave', mouseleave.bind($featureHighlight));
}
};
export const setupDismissButton = function setupDismissButton() {
const popoverId = this.getAttribute('aria-describedby');
const cookieId = this.dataset.highlight;
const $popover = $(this);
const dismissWrapper = dismiss.bind($popover, cookieId);
$(`#${popoverId} .dismiss-feature-highlight`)
.on('click', dismissWrapper);
};
import { highlightFeatures } from './feature_highlight';
import bp from '../breakpoints';
const highlightOrder = ['issue-boards'];
export default function domContentLoaded(order) {
if (bp.getBreakpointSize() === 'lg') {
highlightFeatures(order);
}
}
document.addEventListener('DOMContentLoaded', domContentLoaded.bind(this, highlightOrder));
Loading
Loading
@@ -102,6 +102,7 @@ import './label_manager';
import './labels';
import './labels_select';
import './layout_nav';
import './feature_highlight/feature_highlight_options';
import LazyLoader from './lazy_loader';
import './line_highlighter';
import './logo';
Loading
Loading
Loading
Loading
@@ -51,3 +51,4 @@
@import "framework/snippets";
@import "framework/memory_graph";
@import "framework/responsive-tables";
@import "framework/feature_highlight";
Loading
Loading
@@ -46,6 +46,15 @@
}
}
 
@mixin btn-svg {
svg {
height: 15px;
width: 15px;
position: relative;
top: 2px;
}
}
@mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color) {
background-color: $light;
border-color: $border-light;
Loading
Loading
@@ -123,6 +132,7 @@
.btn {
@include btn-default;
@include btn-white;
@include btn-svg;
 
color: $gl-text-color;
 
Loading
Loading
@@ -222,13 +232,6 @@
}
}
 
svg {
height: 15px;
width: 15px;
position: relative;
top: 2px;
}
svg,
.fa {
&:not(:last-child) {
Loading
Loading
.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 {
@include btn-svg;
svg path {
fill: currentColor;
}
}
.dismiss-feature-highlight {
padding: 0;
}
svg:first-child {
width: 100%;
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 {
padding: 0;
.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;
}
}
<svg xmlns="http://www.w3.org/2000/svg" width="214" height="102" viewBox="0 0 214 102" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<path id="b" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,27 C48,28.1045695 47.1045695,29 46,29 L2,29 C0.8954305,29 1.3527075e-16,28.1045695 0,27 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="a" width="102.1%" height="106.9%" x="-1%" y="-1.7%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
<path id="d" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="c" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
<path id="e" d="M5,0 L53,0 C55.7614237,-5.07265313e-16 58,2.23857625 58,5 L58,91 C58,93.7614237 55.7614237,96 53,96 L5,96 C2.23857625,96 3.38176876e-16,93.7614237 0,91 L0,5 C-3.38176876e-16,2.23857625 2.23857625,5.07265313e-16 5,0 Z"/>
<path id="h" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="g" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
<path id="j" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="i" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
<path id="l" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="k" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
<path id="n" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="m" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
<path id="p" d="M2,0 L46,0 C47.1045695,-2.02906125e-16 48,0.8954305 48,2 L48,26 C48,27.1045695 47.1045695,28 46,28 L2,28 C0.8954305,28 1.3527075e-16,27.1045695 0,26 L0,2 C-1.3527075e-16,0.8954305 0.8954305,2.02906125e-16 2,0 Z"/>
<filter id="o" width="102.1%" height="107.1%" x="-1%" y="-1.8%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowOffsetOuter1"/>
</filter>
</defs>
<g fill="none" fill-rule="evenodd">
<path fill="#D6D4DE" d="M14,21 L62,21 C64.7614237,21 67,23.2385763 67,26 L67,112 C67,114.761424 64.7614237,117 62,117 L14,117 C11.2385763,117 9,114.761424 9,112 L9,26 C9,23.2385763 11.2385763,21 14,21 Z"/>
<g transform="translate(11 23)">
<path fill="#FFFFFF" d="M5,0 L53,0 C55.7614237,-5.07265313e-16 58,2.23857625 58,5 L58,91 C58,93.7614237 55.7614237,96 53,96 L5,96 C2.23857625,96 3.38176876e-16,93.7614237 0,91 L0,5 C-3.38176876e-16,2.23857625 2.23857625,5.07265313e-16 5,0 Z"/>
<path fill="#FC6D26" d="M4,0 L54,0 C56.209139,-4.05812251e-16 58,1.790861 58,4 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z"/>
<g transform="translate(5 10)">
<use fill="black" filter="url(#a)" xlink:href="#b"/>
<use fill="#F9F9F9" xlink:href="#b"/>
</g>
<g transform="translate(5 42)">
<use fill="black" filter="url(#c)" xlink:href="#d"/>
<use fill="#FEF0E8" xlink:href="#d"/>
<path fill="#FEE1D3" d="M9,8 L33,8 C34.1045695,8 35,8.8954305 35,10 C35,11.1045695 34.1045695,12 33,12 L9,12 C7.8954305,12 7,11.1045695 7,10 C7,8.8954305 7.8954305,8 9,8 Z"/>
<path fill="#FDC4A8" d="M9,17 L17,17 C18.1045695,17 19,17.8954305 19,19 C19,20.1045695 18.1045695,21 17,21 L9,21 C7.8954305,21 7,20.1045695 7,19 C7,17.8954305 7.8954305,17 9,17 Z"/>
<path fill="#FC6D26" d="M24,17 L32,17 C33.1045695,17 34,17.8954305 34,19 C34,20.1045695 33.1045695,21 32,21 L24,21 C22.8954305,21 22,20.1045695 22,19 C22,17.8954305 22.8954305,17 24,17 Z"/>
</g>
</g>
<path fill="#D6D4DE" d="M148,26 L196,26 C198.761424,26 201,28.2385763 201,31 L201,117 C201,119.761424 198.761424,122 196,122 L148,122 C145.238576,122 143,119.761424 143,117 L143,31 C143,28.2385763 145.238576,26 148,26 Z"/>
<g transform="translate(145 28)">
<mask id="f" fill="white">
<use xlink:href="#e"/>
</mask>
<use fill="#FFFFFF" xlink:href="#e"/>
<path fill="#FC6D26" d="M4,0 L54,0 C56.209139,-4.05812251e-16 58,1.790861 58,4 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z" mask="url(#f)"/>
<g transform="translate(5 10)">
<use fill="black" filter="url(#g)" xlink:href="#h"/>
<use fill="#F9F9F9" xlink:href="#h"/>
</g>
<g transform="translate(5 42)">
<use fill="black" filter="url(#i)" xlink:href="#j"/>
<use fill="#FEF0E8" xlink:href="#j"/>
<path fill="#FEE1D3" d="M9 8L33 8C34.1045695 8 35 8.8954305 35 10 35 11.1045695 34.1045695 12 33 12L9 12C7.8954305 12 7 11.1045695 7 10 7 8.8954305 7.8954305 8 9 8zM9 17L13 17C14.1045695 17 15 17.8954305 15 19 15 20.1045695 14.1045695 21 13 21L9 21C7.8954305 21 7 20.1045695 7 19 7 17.8954305 7.8954305 17 9 17z"/>
<path fill="#FC6D26" d="M20,17 L24,17 C25.1045695,17 26,17.8954305 26,19 C26,20.1045695 25.1045695,21 24,21 L20,21 C18.8954305,21 18,20.1045695 18,19 C18,17.8954305 18.8954305,17 20,17 Z"/>
<path fill="#FDC4A8" d="M31,17 L35,17 C36.1045695,17 37,17.8954305 37,19 C37,20.1045695 36.1045695,21 35,21 L31,21 C29.8954305,21 29,20.1045695 29,19 C29,17.8954305 29.8954305,17 31,17 Z"/>
</g>
</g>
<path fill="#D6D4DE" d="M81,14 L129,14 C131.761424,14 134,16.2385763 134,19 L134,105 C134,107.761424 131.761424,110 129,110 L81,110 C78.2385763,110 76,107.761424 76,105 L76,19 C76,16.2385763 78.2385763,14 81,14 Z"/>
<g transform="translate(78 16)">
<path fill="#FFFFFF" d="M5,0 L53,0 C55.7614237,-5.07265313e-16 58,2.23857625 58,5 L58,91 C58,93.7614237 55.7614237,96 53,96 L5,96 C2.23857625,96 3.38176876e-16,93.7614237 0,91 L0,5 C-3.38176876e-16,2.23857625 2.23857625,5.07265313e-16 5,0 Z"/>
<g transform="translate(5 10)">
<use fill="black" filter="url(#k)" xlink:href="#l"/>
<use fill="#EFEDF8" xlink:href="#l"/>
<path fill="#E1DBF1" d="M9,8 L33,8 C34.1045695,8 35,8.8954305 35,10 C35,11.1045695 34.1045695,12 33,12 L9,12 C7.8954305,12 7,11.1045695 7,10 C7,8.8954305 7.8954305,8 9,8 Z"/>
<path fill="#6B4FBB" d="M9,17 L13,17 C14.1045695,17 15,17.8954305 15,19 C15,20.1045695 14.1045695,21 13,21 L9,21 C7.8954305,21 7,20.1045695 7,19 C7,17.8954305 7.8954305,17 9,17 Z"/>
<path fill="#C3B8E3" d="M20,17 L28,17 C29.1045695,17 30,17.8954305 30,19 C30,20.1045695 29.1045695,21 28,21 L20,21 C18.8954305,21 18,20.1045695 18,19 C18,17.8954305 18.8954305,17 20,17 Z"/>
</g>
<g transform="translate(5 42)">
<use fill="black" filter="url(#m)" xlink:href="#n"/>
<use fill="#F9F9F9" xlink:href="#n"/>
</g>
<g transform="translate(5 74)">
<rect width="34" height="4" x="7" y="7" fill="#E1DBF1" rx="2"/>
<use fill="black" filter="url(#o)" xlink:href="#p"/>
<use fill="#F9F9F9" xlink:href="#p"/>
</g>
<path fill="#6B4FBB" d="M4,0 L54,0 C56.209139,-4.05812251e-16 58,1.790861 58,4 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z"/>
</g>
</g>
</svg>
Loading
Loading
@@ -99,6 +99,20 @@
= link_to project_boards_path(@project), title: 'Board' do
%span
Board
.feature-highlight.js-feature-highlight{ disabled: true, data: { trigger: 'manual', container: 'body', toggle: 'popover', placement: 'right', highlight: 'issue-boards' } }
.feature-highlight-popover-content
= render 'feature_highlight/issue_boards.svg'
.feature-highlight-popover-sub-content
%span= _('Use')
= link_to 'Issue Boards', project_boards_path(@project)
%span= _('to create customized software development workflows like')
%strong= _('Scrum')
%span= _('or')
%strong= _('Kanban')
%hr
%button.btn-link.dismiss-feature-highlight{ type: 'button' }
%span= _("Got it! Don't show this again")
= custom_icon('thumbs_up')
 
= nav_link(controller: :labels) do
= link_to project_labels_path(@project), title: 'Labels' do
Loading
Loading
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.33 5h5.282a2 2 0 0 1 1.963 2.38l-.563 2.905a3 3 0 0 1-.243.732l-1.104 2.286A3 3 0 0 1 10.964 15H7a3 3 0 0 1-3-3V5.7a2 2 0 0 1 .436-1.247l3.11-3.9A.632.632 0 0 1 8.486.5l.138.137a1 1 0 0 1 .28.87L8.33 5zM1 6h2v7H1a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"/></svg>
import Cookies from 'js-cookie';
import {
getCookieName,
getSelector,
showPopover,
hidePopover,
dismiss,
mouseleave,
mouseenter,
setupDismissButton,
} from '~/feature_highlight/feature_highlight_helper';
describe('feature highlight helper', () => {
describe('getCookieName', () => {
it('returns `feature-highlighted-` prefix', () => {
const cookieId = 'cookieId';
expect(getCookieName(cookieId)).toEqual(`feature-highlighted-${cookieId}`);
});
});
describe('getSelector', () => {
it('returns js-feature-highlight selector', () => {
const highlightId = 'highlightId';
expect(getSelector(highlightId)).toEqual(`.js-feature-highlight[data-highlight=${highlightId}]`);
});
});
describe('showPopover', () => {
it('returns true when popover is shown', () => {
const context = {
hasClass: () => false,
popover: () => {},
addClass: () => {},
};
expect(showPopover.call(context)).toEqual(true);
});
it('returns false when popover is already shown', () => {
const context = {
hasClass: () => true,
};
expect(showPopover.call(context)).toEqual(false);
});
it('shows popover', (done) => {
const context = {
hasClass: () => false,
popover: () => {},
addClass: () => {},
};
spyOn(context, 'popover').and.callFake((method) => {
expect(method).toEqual('show');
done();
});
showPopover.call(context);
});
it('adds disable-animation and js-popover-show class', (done) => {
const context = {
hasClass: () => false,
popover: () => {},
addClass: () => {},
};
spyOn(context, 'addClass').and.callFake((classNames) => {
expect(classNames).toEqual('disable-animation js-popover-show');
done();
});
showPopover.call(context);
});
});
describe('hidePopover', () => {
it('returns true when popover is hidden', () => {
const context = {
hasClass: () => true,
popover: () => {},
removeClass: () => {},
};
expect(hidePopover.call(context)).toEqual(true);
});
it('returns false when popover is already hidden', () => {
const context = {
hasClass: () => false,
};
expect(hidePopover.call(context)).toEqual(false);
});
it('hides popover', (done) => {
const context = {
hasClass: () => true,
popover: () => {},
removeClass: () => {},
};
spyOn(context, 'popover').and.callFake((method) => {
expect(method).toEqual('hide');
done();
});
hidePopover.call(context);
});
it('removes disable-animation and js-popover-show class', (done) => {
const context = {
hasClass: () => true,
popover: () => {},
removeClass: () => {},
};
spyOn(context, 'removeClass').and.callFake((classNames) => {
expect(classNames).toEqual('disable-animation js-popover-show');
done();
});
hidePopover.call(context);
});
});
describe('dismiss', () => {
const context = {
hide: () => {},
};
beforeEach(() => {
spyOn(Cookies, 'set').and.callFake(() => {});
spyOn(hidePopover, 'call').and.callFake(() => {});
spyOn(context, 'hide').and.callFake(() => {});
dismiss.call(context);
});
it('sets cookie to true', () => {
expect(Cookies.set).toHaveBeenCalled();
});
it('calls hide popover', () => {
expect(hidePopover.call).toHaveBeenCalled();
});
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(hidePopover, 'call');
mouseleave();
expect(hidePopover.call).toHaveBeenCalled();
});
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(hidePopover, 'call');
mouseleave();
expect(hidePopover.call).not.toHaveBeenCalled();
});
});
describe('mouseenter', () => {
const context = {};
it('shows popover', () => {
spyOn(showPopover, 'call').and.returnValue(false);
mouseenter.call(context);
expect(showPopover.call).toHaveBeenCalled();
});
it('registers mouseleave event if popover is showed', (done) => {
spyOn(showPopover, '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(showPopover, 'call').and.returnValue(false);
const spy = spyOn($.fn, 'on').and.callFake(() => {});
mouseenter.call(context);
expect(spy).not.toHaveBeenCalled();
});
});
describe('setupDismissButton', () => {
it('registers click event callback', (done) => {
const context = {
getAttribute: () => 'popoverId',
dataset: {
highlight: 'cookieId',
},
};
spyOn($.fn, 'on').and.callFake((event) => {
expect(event).toEqual('click');
done();
});
setupDismissButton.call(context);
});
});
});
import domContentLoaded from '~/feature_highlight/feature_highlight_options';
import bp from '~/breakpoints';
describe('feature highlight options', () => {
describe('domContentLoaded', () => {
const highlightOrder = [];
beforeEach(() => {
// Check for when highlightFeatures is called
spyOn(highlightOrder, 'find').and.callFake(() => {});
});
it('should not call highlightFeatures when breakpoint is xs', () => {
spyOn(bp, 'getBreakpointSize').and.returnValue('xs');
domContentLoaded(highlightOrder);
expect(bp.getBreakpointSize).toHaveBeenCalled();
expect(highlightOrder.find).not.toHaveBeenCalled();
});
it('should not call highlightFeatures when breakpoint is sm', () => {
spyOn(bp, 'getBreakpointSize').and.returnValue('sm');
domContentLoaded(highlightOrder);
expect(bp.getBreakpointSize).toHaveBeenCalled();
expect(highlightOrder.find).not.toHaveBeenCalled();
});
it('should not call highlightFeatures when breakpoint is md', () => {
spyOn(bp, 'getBreakpointSize').and.returnValue('md');
domContentLoaded(highlightOrder);
expect(bp.getBreakpointSize).toHaveBeenCalled();
expect(highlightOrder.find).not.toHaveBeenCalled();
});
it('should call highlightFeatures when breakpoint is lg', () => {
spyOn(bp, 'getBreakpointSize').and.returnValue('lg');
domContentLoaded(highlightOrder);
expect(bp.getBreakpointSize).toHaveBeenCalled();
expect(highlightOrder.find).toHaveBeenCalled();
});
});
});
import Cookies from 'js-cookie';
import * as featureHighlightHelper from '~/feature_highlight/feature_highlight_helper';
import * as featureHighlight from '~/feature_highlight/feature_highlight';
describe('feature highlight', () => {
describe('setupFeatureHighlightPopover', () => {
const selector = '.js-feature-highlight[data-highlight=test]';
beforeEach(() => {
setFixtures(`
<div>
<div class="js-feature-highlight" data-highlight="test" disabled>
Trigger
</div>
</div>
<div class="feature-highlight-popover-content">
Content
<div class="dismiss-feature-highlight">
Dismiss
</div>
</div>
`);
spyOn(window, 'addEventListener');
spyOn(window, 'removeEventListener');
featureHighlight.setupFeatureHighlightPopover('test', 0);
});
it('setups popover content', () => {
const $popoverContent = $('.feature-highlight-popover-content');
const outerHTML = $popoverContent.prop('outerHTML');
expect($(selector).data('content')).toEqual(outerHTML);
});
it('setups mouseenter', () => {
const showSpy = spyOn(featureHighlightHelper.showPopover, 'call');
$(selector).trigger('mouseenter');
expect(showSpy).toHaveBeenCalled();
});
it('setups debounced mouseleave', (done) => {
const hideSpy = spyOn(featureHighlightHelper.hidePopover, 'call');
$(selector).trigger('mouseleave');
// Even though we've set the debounce to 0ms, setTimeout is needed for the debounce
setTimeout(() => {
expect(hideSpy).toHaveBeenCalled();
done();
}, 0);
});
it('setups 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('setups show.bs.popover', () => {
$(selector).trigger('show.bs.popover');
expect(window.addEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function));
});
it('setups 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('shouldHighlightFeature', () => {
it('should return false if element is not found', () => {
spyOn(document, 'querySelector').and.returnValue(null);
spyOn(Cookies, 'get').and.returnValue(null);
expect(featureHighlight.shouldHighlightFeature()).toBeFalsy();
});
it('should return false if previouslyDismissed', () => {
spyOn(document, 'querySelector').and.returnValue(document.createElement('div'));
spyOn(Cookies, 'get').and.returnValue('true');
expect(featureHighlight.shouldHighlightFeature()).toBeFalsy();
});
it('should return true if element is found and not previouslyDismissed', () => {
spyOn(document, 'querySelector').and.returnValue(document.createElement('div'));
spyOn(Cookies, 'get').and.returnValue(null);
expect(featureHighlight.shouldHighlightFeature()).toBeTruthy();
});
});
describe('highlightFeatures', () => {
it('calls setupFeatureHighlightPopover if shouldHighlightFeature returns true', () => {
// Mimic shouldHighlightFeature set to true
const highlightOrder = ['issue-boards'];
spyOn(highlightOrder, 'find').and.returnValue(highlightOrder[0]);
expect(featureHighlight.highlightFeatures(highlightOrder)).toEqual(true);
});
it('does not call setupFeatureHighlightPopover if shouldHighlightFeature returns false', () => {
// Mimic shouldHighlightFeature set to false
const highlightOrder = ['issue-boards'];
spyOn(highlightOrder, 'find').and.returnValue(null);
expect(featureHighlight.highlightFeatures(highlightOrder)).toEqual(false);
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment