Commit 8c3210bc authored by Luke Bennett's avatar Luke Bennett

merge ce

parent eddaeba1
import $ from 'jquery'; import $ from 'jquery';
import _ from 'underscore';
import { import {
getSelector, getSelector,
togglePopover,
inserted, inserted,
mouseenter,
mouseleave,
} from './feature_highlight_helper'; } from './feature_highlight_helper';
import {
togglePopover,
mouseenter,
debouncedMouseleave,
} from '../shared/popover';
export function setupFeatureHighlightPopover(id, debounceTimeout = 300) { export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
const $selector = $(getSelector(id)); const $selector = $(getSelector(id));
const $parent = $selector.parent(); const $parent = $selector.parent();
const $popoverContent = $parent.siblings('.feature-highlight-popover-content'); const $popoverContent = $parent.siblings('.feature-highlight-popover-content');
const hideOnScroll = togglePopover.bind($selector, false); const hideOnScroll = togglePopover.bind($selector, false);
const debouncedMouseleave = _.debounce(mouseleave, debounceTimeout);
$selector $selector
// Setup popover // Setup popover
...@@ -29,7 +29,7 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) { ...@@ -29,7 +29,7 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
`, `,
}) })
.on('mouseenter', mouseenter) .on('mouseenter', mouseenter)
.on('mouseleave', debouncedMouseleave) .on('mouseleave', debouncedMouseleave(debounceTimeout))
.on('inserted.bs.popover', inserted) .on('inserted.bs.popover', inserted)
.on('show.bs.popover', () => { .on('show.bs.popover', () => {
window.addEventListener('scroll', hideOnScroll); window.addEventListener('scroll', hideOnScroll);
......
...@@ -3,20 +3,10 @@ import axios from '../lib/utils/axios_utils'; ...@@ -3,20 +3,10 @@ import axios from '../lib/utils/axios_utils';
import { __ } from '../locale'; import { __ } from '../locale';
import Flash from '../flash'; import Flash from '../flash';
import LazyLoader from '../lazy_loader'; import LazyLoader from '../lazy_loader';
import { togglePopover } from '../shared/popover';
export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`; 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) { export function dismiss(highlightId) {
axios.post(this.attr('data-dismiss-endpoint'), { axios.post(this.attr('data-dismiss-endpoint'), {
feature_name: highlightId, feature_name: highlightId,
...@@ -27,23 +17,6 @@ export function dismiss(highlightId) { ...@@ -27,23 +17,6 @@ export function dismiss(highlightId) {
this.hide(); 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() { export function inserted() {
const popoverId = this.getAttribute('aria-describedby'); const popoverId = this.getAttribute('aria-describedby');
const highlightId = this.dataset.highlight; const highlightId = this.dataset.highlight;
......
import $ from 'jquery'; import $ from 'jquery';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import flash from './flash'; import flash from './flash';
import { mouseenter, debouncedMouseleave, togglePopover } from './shared/popover';
export default class Milestone { export default class Milestone {
constructor() { constructor() {
...@@ -43,4 +44,25 @@ export default class Milestone { ...@@ -43,4 +44,25 @@ export default class Milestone {
.catch(() => flash('Error loading milestone tab')); .catch(() => flash('Error loading milestone tab'));
} }
} }
static initDeprecationMessage() {
const deprecationMesssageContainer = document.querySelector('.js-milestone-deprecation-message');
if (!deprecationMesssageContainer) return;
const deprecationMessage = deprecationMesssageContainer.querySelector('.js-milestone-deprecation-message-template').innerHTML;
const $popover = $('.js-popover-link', deprecationMesssageContainer);
const hideOnScroll = togglePopover.bind($popover, false);
$popover.popover({
content: deprecationMessage,
html: true,
placement: 'bottom',
})
.on('mouseenter', mouseenter)
.on('mouseleave', debouncedMouseleave())
.one('show.bs.popover', () => {
window.addEventListener('scroll', hideOnScroll);
});
}
} }
...@@ -6,4 +6,6 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -6,4 +6,6 @@ document.addEventListener('DOMContentLoaded', () => {
new Milestone(); // eslint-disable-line no-new new Milestone(); // eslint-disable-line no-new
new Sidebar(); // eslint-disable-line no-new new Sidebar(); // eslint-disable-line no-new
new MountMilestoneSidebar(); // eslint-disable-line no-new new MountMilestoneSidebar(); // eslint-disable-line no-new
Milestone.initDeprecationMessage();
}); });
import initMilestonesShow from '~/pages/milestones/shared/init_milestones_show'; import initMilestonesShow from '~/pages/milestones/shared/init_milestones_show';
import Milestone from '~/milestone';
document.addEventListener('DOMContentLoaded', initMilestonesShow); document.addEventListener('DOMContentLoaded', () => {
initMilestonesShow();
Milestone.initDeprecationMessage();
});
import $ from 'jquery';
import _ from 'underscore';
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 mouseleave() {
if (!$('.popover:hover').length > 0) {
const $popover = $(this);
togglePopover.call($popover, false);
}
}
export function mouseenter() {
const $popover = $(this);
const showedPopover = togglePopover.call($popover, true);
if (showedPopover) {
$('.popover').on('mouseleave', mouseleave.bind($popover));
}
}
export function debouncedMouseleave(debounceTimeout = 300) {
return _.debounce(mouseleave, debounceTimeout);
}
.banner-callout { .banner-callout {
display: flex; display: flex;
position: relative; position: relative;
flex-wrap: wrap; align-items: start;
.banner-close { .banner-close {
position: absolute; position: absolute;
...@@ -16,10 +16,25 @@ ...@@ -16,10 +16,25 @@
} }
.banner-graphic { .banner-graphic {
margin: 20px auto; margin: 0 $gl-padding $gl-padding 0;
} }
&.banner-non-empty-state { &.banner-non-empty-state {
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
} }
@media (max-width: $screen-xs-max) {
justify-content: center;
flex-direction: column;
align-items: center;
.banner-title,
.banner-buttons {
text-align: center;
}
.banner-graphic {
margin-left: $gl-padding;
}
}
} }
...@@ -422,7 +422,24 @@ ...@@ -422,7 +422,24 @@
} }
} }
.btn-link.btn-secondary-hover-link { .btn-link {
padding: 0;
background-color: transparent;
color: $blue-600;
font-weight: normal;
border-radius: 0;
border-color: transparent;
&:hover,
&:active,
&:focus {
color: $blue-800;
text-decoration: underline;
background-color: transparent;
border-color: transparent;
}
&.btn-secondary-hover-link {
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
&:hover, &:hover,
...@@ -431,9 +448,9 @@ ...@@ -431,9 +448,9 @@
color: $gl-link-color; color: $gl-link-color;
text-decoration: none; text-decoration: none;
} }
} }
.btn-link.btn-primary-hover-link { &.btn-primary-hover-link {
color: inherit; color: inherit;
&:hover, &:hover,
...@@ -442,6 +459,7 @@ ...@@ -442,6 +459,7 @@
color: $gl-link-color; color: $gl-link-color;
text-decoration: none; text-decoration: none;
} }
}
} }
.btn-missing { .btn-missing {
......
...@@ -195,6 +195,42 @@ ...@@ -195,6 +195,42 @@
background-color: $white-light; background-color: $white-light;
} }
.milestone-deprecation-message {
.popover {
padding: 0;
}
.popover-content {
padding: 0;
}
}
.milestone-popover-body {
padding: $gl-padding-8;
background-color: $gray-light;
}
.milestone-popover-footer {
padding: $gl-padding-8 $gl-padding;
border-top: 1px solid $white-dark;
}
.milestone-popover-instructions-list {
padding-left: 2em;
> li {
padding-left: 1em;
}
}
@media (max-width: $screen-xs-max) {
.milestone-banner-text,
.milestone-banner-link {
display: inline;
}
}
// EE-only // EE-only
.burndown-hint.container-fluid { .burndown-hint.container-fluid {
......
.js-autodevops-banner.banner-callout.banner-non-empty-state.append-bottom-20{ data: { uid: 'auto_devops_settings_dismissed', project_path: project_path(@project) } } .js-autodevops-banner.banner-callout.banner-non-empty-state.append-bottom-20.prepend-top-10{ data: { uid: 'auto_devops_settings_dismissed', project_path: project_path(@project) } }
.banner-graphic .banner-graphic
= custom_icon('icon_autodevops') = custom_icon('icon_autodevops')
.prepend-top-10.prepend-left-10.append-bottom-10 .banner-body.prepend-left-10.append-bottom-10
%h5= s_('AutoDevOps|Auto DevOps (Beta)') %h5.banner-title= s_('AutoDevOps|Auto DevOps (Beta)')
%p= s_('AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.') %p= s_('AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.')
%p %p
- link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer') - link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer')
= s_('AutoDevOps|Learn more in the %{link_to_documentation}').html_safe % { link_to_documentation: link } = s_('AutoDevOps|Learn more in the %{link_to_documentation}').html_safe % { link_to_documentation: link }
.prepend-top-10 .banner-buttons
= link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings'), class: 'btn js-close-callout' = link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings'), class: 'btn js-close-callout'
%button.btn-transparent.banner-close.close.js-close-callout{ type: 'button', %button.btn-transparent.banner-close.close.js-close-callout{ type: 'button',
......
.banner-callout.compact.milestone-deprecation-message.js-milestone-deprecation-message.prepend-top-20
.banner-graphic= image_tag 'illustrations/milestone_removing-page.svg'
.banner-body.prepend-left-10.append-right-10
%h5.banner-title.prepend-top-0= _('This page will be removed in a future release.')
%p.milestone-banner-text= _('Use group milestones to manage issues from multiple projects in the same milestone.')
= button_tag _('Promote these project milestones into a group milestone.'), class: 'btn btn-link js-popover-link text-align-left milestone-banner-link'
.milestone-banner-buttons.prepend-top-20= link_to _('Learn more'), help_page_url('user/project/milestones/index', anchor: 'promoting-project-milestones-to-group-milestones'), class: 'btn btn-default', target: '_blank'
%template.js-milestone-deprecation-message-template
.milestone-popover-body
%ol.milestone-popover-instructions-list.append-bottom-0
%li= _('Click any <strong>project name</strong> in the project list below to navigate to the project milestone.').html_safe
%li= _('Click the <strong>Promote</strong> button in the top right corner to promote it to a group milestone.').html_safe
.milestone-popover-footer= link_to _('Learn more'), help_page_url('user/project/milestones/index', anchor: 'promoting-project-milestones-to-group-milestones'), class: 'btn btn-link prepend-left-0', target: '_blank'
- page_title @milestone.title - page_title milestone.title
- @breadcrumb_link = dashboard_milestone_path(milestone.safe_title, title: milestone.title) - @breadcrumb_link = dashboard_milestone_path(milestone.safe_title, title: milestone.title)
- group = local_assigns[:group] - group = local_assigns[:group]
- is_dynamic_milestone = milestone.legacy_group_milestone? || milestone.dashboard_milestone?
.detail-page-header .detail-page-header
%a.btn.btn-default.btn-grouped.pull-right.visible-xs-block.js-sidebar-toggle{ href: "#" } %a.btn.btn-default.btn-grouped.pull-right.visible-xs-block.js-sidebar-toggle{ href: "#" }
...@@ -31,21 +32,23 @@ ...@@ -31,21 +32,23 @@
- else - else
= link_to 'Reopen Milestone', group_milestone_route(milestone, {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen" = link_to 'Reopen Milestone', group_milestone_route(milestone, {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
= render 'shared/milestones/deprecation_message' if is_dynamic_milestone
.detail-page-description.milestone-detail .detail-page-description.milestone-detail
%h2.title %h2.title
= markdown_field(milestone, :title) = markdown_field(milestone, :title)
- if @milestone.group_milestone? && @milestone.description.present? - if milestone.group_milestone? && milestone.description.present?
%div %div
.description .description
.wiki .wiki
= markdown_field(@milestone, :description) = markdown_field(milestone, :description)
- if milestone.complete?(current_user) && milestone.active? - if milestone.complete?(current_user) && milestone.active?
.alert.alert-success.prepend-top-default .alert.alert-success.prepend-top-default
- close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.' - close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.'
%span All issues for this milestone are closed. #{close_msg} %span All issues for this milestone are closed. #{close_msg}
- if @milestone.legacy_group_milestone? || @milestone.dashboard_milestone? - if is_dynamic_milestone
.table-holder .table-holder
%table.table %table.table
%thead %thead
...@@ -68,7 +71,7 @@ ...@@ -68,7 +71,7 @@
Open Open
%td %td
= ms.expires_at = ms.expires_at
- elsif @milestone.group_milestone? - elsif milestone.group_milestone?
%br %br
View View
= link_to 'Issues', issues_group_path(@group, milestone_title: milestone.title) = link_to 'Issues', issues_group_path(@group, milestone_title: milestone.title)
......
---
title: Add deprecation message to dynamic milestone pages
merge_request: 17505
author:
type: added
...@@ -108,4 +108,18 @@ feature 'Milestone' do ...@@ -108,4 +108,18 @@ feature 'Milestone' do
expect(page).to have_selector('.js-delete-milestone-button', count: 0) expect(page).to have_selector('.js-delete-milestone-button', count: 0)
end end
end end
feature 'deprecation popover', :js do
it 'opens deprecation popover' do
milestone = create(:milestone, project: project)
visit group_milestone_path(group, milestone, title: milestone.title)
expect(page).to have_selector('.milestone-deprecation-message')
find('.milestone-deprecation-message .js-popover-link').click
expect(page).to have_selector('.milestone-deprecation-message .popover')
end
end
end end
...@@ -3,12 +3,11 @@ import MockAdapter from 'axios-mock-adapter'; ...@@ -3,12 +3,11 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { import {
getSelector, getSelector,
togglePopover,
dismiss, dismiss,
mouseleave,
mouseenter,
inserted, inserted,
} from '~/feature_highlight/feature_highlight_helper'; } from '~/feature_highlight/feature_highlight_helper';
import { togglePopover } from '~/shared/popover';
import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
describe('feature highlight helper', () => { describe('feature highlight helper', () => {
...@@ -19,110 +18,6 @@ describe('feature highlight helper', () => { ...@@ -19,110 +18,6 @@ describe('feature highlight helper', () => {
}); });
}); });
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', () => { describe('dismiss', () => {
let mock; let mock;
const context = { const context = {
...@@ -163,56 +58,6 @@ describe('feature highlight helper', () => { ...@@ -163,56 +58,6 @@ describe('feature highlight helper', () => {
}); });
}); });
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', () => { describe('inserted', () => {
it('registers click event callback', (done) => { it('registers click event callback', (done) => {
const context = { const context = {
......
import $ from 'jquery'; import $ from 'jquery';
import * as featureHighlightHelper from '~/feature_highlight/feature_highlight_helper';
import * as featureHighlight from '~/feature_highlight/feature_highlight'; import * as featureHighlight from '~/feature_highlight/feature_highlight';
import * as popover from '~/shared/popover';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
...@@ -45,14 +45,14 @@ describe('feature highlight', () => { ...@@ -45,14 +45,14 @@ describe('feature highlight', () => {
}); });
it('setup mouseenter', () => { it('setup mouseenter', () => {
const toggleSpy = spyOn(featureHighlightHelper.togglePopover, 'call'); const toggleSpy = spyOn(popover.togglePopover, 'call');
$(selector).trigger('mouseenter'); $(selector).trigger('mouseenter');
expect(toggleSpy).toHaveBeenCalledWith(jasmine.any(Object), true); expect(toggleSpy).toHaveBeenCalledWith(jasmine.any(Object), true);
}); });
it('setup debounced mouseleave', (done) => { it('setup debounced mouseleave', (done) => {
const toggleSpy = spyOn(featureHighlightHelper.togglePopover, 'call'); const toggleSpy = spyOn(popover.togglePopover, 'call');
$(selector).trigger('mouseleave'); $(selector).trigger('mouseleave');
// Even though we've set the debounce to 0ms, setTimeout is needed for the debounce // Even though we've set the debounce to 0ms, setTimeout is needed for the debounce
...@@ -85,7 +85,7 @@ describe('feature highlight', () => { ...@@ -85,7 +85,7 @@ describe('feature highlight', () => {
it('toggles when clicked', () => { it('toggles when clicked', () => {
$(selector).trigger('mouseenter'); $(selector).trigger('mouseenter');
const popoverId = $(selector).attr('aria-describedby'); const popoverId = $(selector).attr('aria-describedby');
const toggleSpy = spyOn(featureHighlightHelper.togglePopover, 'call'); const toggleSpy = spyOn(popover.togglePopover, 'call');
$(`#${popoverId} .dismiss-feature-highlight`).click(); $(`#${popoverId} .dismiss-feature-highlight`).click();
......
import $ from 'jquery';
import {
togglePopover,
mouseleave,
mouseenter,
} from '~/shared/popover';
describe('popover', () => {
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('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();
});
});
});
require 'spec_helper'
describe 'shared/milestones/_top.html.haml' do
set(:group) { create(:group) }
let(:project) { create(:project, group: group) }
let(:milestone) { create(:milestone, project: project) }
before do
allow(milestone).to receive(:milestones) { [] }
end
it 'renders a deprecation message for a legacy milestone' do
allow(milestone).to receive(:legacy_group_milestone?) { true }
render 'shared/milestones/top', milestone: milestone
expect(rendered).to have_css('.milestone-deprecation-message')
end
it 'renders a deprecation message for a dashboard milestone' do
allow(milestone).to receive(:dashboard_milestone?) { true }
render 'shared/milestones/top', milestone: milestone
expect(rendered).to have_css('.milestone-deprecation-message')
end
it 'does not render a deprecation message for a non-legacy and non-dashboard milestone' do
assign :group, group
render 'shared/milestones/top', milestone: milestone
expect(rendered).not_to have_css('.milestone-deprecation-message')
end
end
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