pulsar/spec/tooltip-manager-spec.js
2019-02-28 19:30:03 +01:00

318 lines
11 KiB
JavaScript

const { CompositeDisposable } = require('atom')
const TooltipManager = require('../src/tooltip-manager')
const Tooltip = require('../src/tooltip')
const _ = require('underscore-plus')
describe('TooltipManager', () => {
let manager, element
const ctrlX = _.humanizeKeystroke('ctrl-x')
const ctrlY = _.humanizeKeystroke('ctrl-y')
const hover = function (element, fn) {
mouseEnter(element)
advanceClock(manager.hoverDefaults.delay.show)
fn()
mouseLeave(element)
advanceClock(manager.hoverDefaults.delay.hide)
}
beforeEach(function () {
manager = new TooltipManager({
keymapManager: atom.keymaps,
viewRegistry: atom.views
})
element = createElement('foo')
})
describe('::add(target, options)', () => {
describe("when the trigger is 'hover' (the default)", () => {
it('creates a tooltip when hovering over the target element', () => {
manager.add(element, { title: 'Title' })
hover(element, () =>
expect(document.body.querySelector('.tooltip')).toHaveText('Title')
)
})
it('displays tooltips immediately when hovering over new elements once a tooltip has been displayed once', () => {
const disposables = new CompositeDisposable()
const element1 = createElement('foo')
disposables.add(manager.add(element1, { title: 'Title' }))
const element2 = createElement('bar')
disposables.add(manager.add(element2, { title: 'Title' }))
const element3 = createElement('baz')
disposables.add(manager.add(element3, { title: 'Title' }))
hover(element1, () => {})
expect(document.body.querySelector('.tooltip')).toBeNull()
mouseEnter(element2)
expect(document.body.querySelector('.tooltip')).not.toBeNull()
mouseLeave(element2)
advanceClock(manager.hoverDefaults.delay.hide)
expect(document.body.querySelector('.tooltip')).toBeNull()
advanceClock(Tooltip.FOLLOW_THROUGH_DURATION)
mouseEnter(element3)
expect(document.body.querySelector('.tooltip')).toBeNull()
advanceClock(manager.hoverDefaults.delay.show)
expect(document.body.querySelector('.tooltip')).not.toBeNull()
disposables.dispose()
})
it('hides the tooltip on keydown events', () => {
const disposable = manager.add(element, {
title: 'Title',
trigger: 'hover'
})
hover(element, function () {
expect(document.body.querySelector('.tooltip')).not.toBeNull()
window.dispatchEvent(
new CustomEvent('keydown', {
bubbles: true
})
)
expect(document.body.querySelector('.tooltip')).toBeNull()
disposable.dispose()
})
})
})
describe("when the trigger is 'manual'", () =>
it('creates a tooltip immediately and only hides it on dispose', () => {
const disposable = manager.add(element, {
title: 'Title',
trigger: 'manual'
})
expect(document.body.querySelector('.tooltip')).toHaveText('Title')
disposable.dispose()
expect(document.body.querySelector('.tooltip')).toBeNull()
}))
describe("when the trigger is 'click'", () =>
it('shows and hides the tooltip when the target element is clicked', () => {
manager.add(element, { title: 'Title', trigger: 'click' })
expect(document.body.querySelector('.tooltip')).toBeNull()
element.click()
expect(document.body.querySelector('.tooltip')).not.toBeNull()
element.click()
expect(document.body.querySelector('.tooltip')).toBeNull()
// Hide the tooltip when clicking anywhere but inside the tooltip element
element.click()
expect(document.body.querySelector('.tooltip')).not.toBeNull()
document.body.querySelector('.tooltip').click()
expect(document.body.querySelector('.tooltip')).not.toBeNull()
document.body.querySelector('.tooltip').firstChild.click()
expect(document.body.querySelector('.tooltip')).not.toBeNull()
document.body.click()
expect(document.body.querySelector('.tooltip')).toBeNull()
// Tooltip can show again after hiding due to clicking outside of the tooltip
element.click()
expect(document.body.querySelector('.tooltip')).not.toBeNull()
element.click()
expect(document.body.querySelector('.tooltip')).toBeNull()
}))
it('does not hide the tooltip on keyboard input', () => {
manager.add(element, { title: 'Title', trigger: 'click' })
element.click()
expect(document.body.querySelector('.tooltip')).not.toBeNull()
window.dispatchEvent(
new CustomEvent('keydown', {
bubbles: true
})
)
expect(document.body.querySelector('.tooltip')).not.toBeNull()
// click again to hide the tooltip because otherwise state leaks
// into other tests.
element.click()
})
it('allows a custom item to be specified for the content of the tooltip', () => {
const tooltipElement = document.createElement('div')
manager.add(element, { item: { element: tooltipElement } })
hover(element, () =>
expect(tooltipElement.closest('.tooltip')).not.toBeNull()
)
})
it('allows a custom class to be specified for the tooltip', () => {
manager.add(element, { title: 'Title', class: 'custom-tooltip-class' })
hover(element, () =>
expect(
document.body
.querySelector('.tooltip')
.classList.contains('custom-tooltip-class')
).toBe(true)
)
})
it('allows jQuery elements to be passed as the target', () => {
const element2 = document.createElement('div')
jasmine.attachToDOM(element2)
const fakeJqueryWrapper = {
0: element,
1: element2,
length: 2,
jquery: 'any-version'
}
const disposable = manager.add(fakeJqueryWrapper, { title: 'Title' })
hover(element, () =>
expect(document.body.querySelector('.tooltip')).toHaveText('Title')
)
expect(document.body.querySelector('.tooltip')).toBeNull()
hover(element2, () =>
expect(document.body.querySelector('.tooltip')).toHaveText('Title')
)
expect(document.body.querySelector('.tooltip')).toBeNull()
disposable.dispose()
hover(element, () =>
expect(document.body.querySelector('.tooltip')).toBeNull()
)
hover(element2, () =>
expect(document.body.querySelector('.tooltip')).toBeNull()
)
})
describe('when a keyBindingCommand is specified', () => {
describe('when a title is specified', () =>
it('appends the key binding corresponding to the command to the title', () => {
atom.keymaps.add('test', {
'.foo': { 'ctrl-x ctrl-y': 'test-command' },
'.bar': { 'ctrl-x ctrl-z': 'test-command' }
})
manager.add(element, {
title: 'Title',
keyBindingCommand: 'test-command'
})
hover(element, function () {
const tooltipElement = document.body.querySelector('.tooltip')
expect(tooltipElement).toHaveText(`Title ${ctrlX} ${ctrlY}`)
})
}))
describe('when no title is specified', () =>
it('shows the key binding corresponding to the command alone', () => {
atom.keymaps.add('test', {
'.foo': { 'ctrl-x ctrl-y': 'test-command' }
})
manager.add(element, { keyBindingCommand: 'test-command' })
hover(element, function () {
const tooltipElement = document.body.querySelector('.tooltip')
expect(tooltipElement).toHaveText(`${ctrlX} ${ctrlY}`)
})
}))
describe('when a keyBindingTarget is specified', () => {
it('looks up the key binding relative to the target', () => {
atom.keymaps.add('test', {
'.bar': { 'ctrl-x ctrl-z': 'test-command' },
'.foo': { 'ctrl-x ctrl-y': 'test-command' }
})
manager.add(element, {
keyBindingCommand: 'test-command',
keyBindingTarget: element
})
hover(element, function () {
const tooltipElement = document.body.querySelector('.tooltip')
expect(tooltipElement).toHaveText(`${ctrlX} ${ctrlY}`)
})
})
it('does not display the keybinding if there is nothing mapped to the specified keyBindingCommand', () => {
manager.add(element, {
title: 'A Title',
keyBindingCommand: 'test-command',
keyBindingTarget: element
})
hover(element, function () {
const tooltipElement = document.body.querySelector('.tooltip')
expect(tooltipElement.textContent).toBe('A Title')
})
})
})
})
describe('when .dispose() is called on the returned disposable', () =>
it('no longer displays the tooltip on hover', () => {
const disposable = manager.add(element, { title: 'Title' })
hover(element, () =>
expect(document.body.querySelector('.tooltip')).toHaveText('Title')
)
disposable.dispose()
hover(element, () =>
expect(document.body.querySelector('.tooltip')).toBeNull()
)
}))
describe('when the window is resized', () =>
it('hides the tooltips', () => {
const disposable = manager.add(element, { title: 'Title' })
hover(element, function () {
expect(document.body.querySelector('.tooltip')).not.toBeNull()
window.dispatchEvent(new CustomEvent('resize'))
expect(document.body.querySelector('.tooltip')).toBeNull()
disposable.dispose()
})
}))
describe('findTooltips', () => {
it('adds and remove tooltips correctly', () => {
expect(manager.findTooltips(element).length).toBe(0)
const disposable1 = manager.add(element, { title: 'elem1' })
expect(manager.findTooltips(element).length).toBe(1)
const disposable2 = manager.add(element, { title: 'elem2' })
expect(manager.findTooltips(element).length).toBe(2)
disposable1.dispose()
expect(manager.findTooltips(element).length).toBe(1)
disposable2.dispose()
expect(manager.findTooltips(element).length).toBe(0)
})
it('lets us hide tooltips programmatically', () => {
const disposable = manager.add(element, { title: 'Title' })
hover(element, function () {
expect(document.body.querySelector('.tooltip')).not.toBeNull()
manager.findTooltips(element)[0].hide()
expect(document.body.querySelector('.tooltip')).toBeNull()
disposable.dispose()
})
})
})
})
})
function createElement (className) {
const el = document.createElement('div')
el.classList.add(className)
jasmine.attachToDOM(el)
return el
}
function mouseEnter (element) {
element.dispatchEvent(new CustomEvent('mouseenter', { bubbles: false }))
element.dispatchEvent(new CustomEvent('mouseover', { bubbles: true }))
}
function mouseLeave (element) {
element.dispatchEvent(new CustomEvent('mouseleave', { bubbles: false }))
element.dispatchEvent(new CustomEvent('mouseout', { bubbles: true }))
}