pulsar/spec/context-menu-manager-spec.coffee

403 lines
12 KiB
CoffeeScript

ContextMenuManager = require '../src/context-menu-manager'
describe "ContextMenuManager", ->
[contextMenu, parent, child, grandchild] = []
beforeEach ->
{resourcePath} = atom.getLoadSettings()
contextMenu = new ContextMenuManager({keymapManager: atom.keymaps})
contextMenu.initialize({resourcePath})
parent = document.createElement("div")
child = document.createElement("div")
grandchild = document.createElement("div")
parent.tabIndex = -1
child.tabIndex = -1
grandchild.tabIndex = -1
parent.classList.add('parent')
child.classList.add('child')
grandchild.classList.add('grandchild')
child.appendChild(grandchild)
parent.appendChild(child)
document.body.appendChild(parent)
afterEach ->
document.body.blur()
document.body.removeChild(parent)
describe "::add(itemsBySelector)", ->
it "can add top-level menu items that can be removed with the returned disposable", ->
disposable = contextMenu.add
'.parent': [{label: 'A', command: 'a'}]
'.child': [{label: 'B', command: 'b'}]
'.grandchild': [{label: 'C', command: 'c'}]
expect(contextMenu.templateForElement(grandchild)).toEqual [
{label: 'C', command: 'c'}
{label: 'B', command: 'b'}
{label: 'A', command: 'a'}
]
disposable.dispose()
expect(contextMenu.templateForElement(grandchild)).toEqual []
it "can add submenu items to existing menus that can be removed with the returned disposable", ->
disposable1 = contextMenu.add
'.grandchild': [{label: 'A', submenu: [{label: 'B', command: 'b'}]}]
disposable2 = contextMenu.add
'.grandchild': [{label: 'A', submenu: [{label: 'C', command: 'c'}]}]
expect(contextMenu.templateForElement(grandchild)).toEqual [{
label: 'A',
submenu: [
{label: 'B', command: 'b'}
{label: 'C', command: 'c'}
]
}]
disposable2.dispose()
expect(contextMenu.templateForElement(grandchild)).toEqual [{
label: 'A',
submenu: [
{label: 'B', command: 'b'}
]
}]
disposable1.dispose()
expect(contextMenu.templateForElement(grandchild)).toEqual []
it "favors the most specific / recently added item in the case of a duplicate label", ->
grandchild.classList.add('foo')
disposable1 = contextMenu.add
'.grandchild': [{label: 'A', command: 'a'}]
disposable2 = contextMenu.add
'.grandchild.foo': [{label: 'A', command: 'b'}]
disposable3 = contextMenu.add
'.grandchild': [{label: 'A', command: 'c'}]
disposable4 = contextMenu.add
'.child': [{label: 'A', command: 'd'}]
expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'A', command: 'b'}]
disposable2.dispose()
expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'A', command: 'c'}]
disposable3.dispose()
expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'A', command: 'a'}]
disposable1.dispose()
expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'A', command: 'd'}]
it "allows multiple separators, but not adjacent to each other", ->
contextMenu.add
'.grandchild': [
{label: 'A', command: 'a'},
{type: 'separator'},
{type: 'separator'},
{label: 'B', command: 'b'},
{type: 'separator'},
{type: 'separator'},
{label: 'C', command: 'c'}
]
expect(contextMenu.templateForElement(grandchild)).toEqual [
{label: 'A', command: 'a'},
{type: 'separator'},
{label: 'B', command: 'b'},
{type: 'separator'},
{label: 'C', command: 'c'}
]
it "excludes items marked for display in devMode unless in dev mode", ->
disposable1 = contextMenu.add
'.grandchild': [{label: 'A', command: 'a', devMode: true}, {label: 'B', command: 'b', devMode: false}]
expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'B', command: 'b'}]
contextMenu.devMode = true
expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'A', command: 'a'}, {label: 'B', command: 'b'}]
it "allows items to be associated with `created` hooks which are invoked on template construction with the item and event", ->
createdEvent = null
item = {
label: 'A',
command: 'a',
created: (event) ->
@command = 'b'
createdEvent = event
}
contextMenu.add('.grandchild': [item])
dispatchedEvent = {target: grandchild}
expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual [{label: 'A', command: 'b'}]
expect(item.command).toBe 'a' # doesn't modify original item template
expect(createdEvent).toBe dispatchedEvent
it "allows items to be associated with `shouldDisplay` hooks which are invoked on construction to determine whether the item should be included", ->
shouldDisplayEvent = null
shouldDisplay = true
item = {
label: 'A',
command: 'a',
shouldDisplay: (event) ->
@foo = 'bar'
shouldDisplayEvent = event
shouldDisplay
}
contextMenu.add('.grandchild': [item])
dispatchedEvent = {target: grandchild}
expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual [{label: 'A', command: 'a'}]
expect(item.foo).toBeUndefined() # doesn't modify original item template
expect(shouldDisplayEvent).toBe dispatchedEvent
shouldDisplay = false
expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual []
it "prunes a trailing separator", ->
contextMenu.add
'.grandchild': [
{label: 'A', command: 'a'},
{type: 'separator'},
{label: 'B', command: 'b'},
{type: 'separator'}
]
expect(contextMenu.templateForEvent({target: grandchild}).length).toBe(3)
it "prunes a leading separator", ->
contextMenu.add
'.grandchild': [
{type: 'separator'},
{label: 'A', command: 'a'},
{type: 'separator'},
{label: 'B', command: 'b'}
]
expect(contextMenu.templateForEvent({target: grandchild}).length).toBe(3)
it "prunes duplicate separators", ->
contextMenu.add
'.grandchild': [
{label: 'A', command: 'a'},
{type: 'separator'},
{type: 'separator'},
{label: 'B', command: 'b'}
]
expect(contextMenu.templateForEvent({target: grandchild}).length).toBe(3)
it "prunes all redundant separators", ->
contextMenu.add
'.grandchild': [
{type: 'separator'},
{type: 'separator'},
{label: 'A', command: 'a'},
{type: 'separator'},
{type: 'separator'},
{label: 'B', command: 'b'}
{label: 'C', command: 'c'}
{type: 'separator'},
{type: 'separator'},
]
expect(contextMenu.templateForEvent({target: grandchild}).length).toBe(4)
it "throws an error when the selector is invalid", ->
addError = null
try
contextMenu.add '<>': [{label: 'A', command: 'a'}]
catch error
addError = error
expect(addError.message).toContain('<>')
it "calls `created` hooks for submenu items", ->
item = {
label: 'A',
command: 'B',
submenu: [
{
label: 'C',
created: (event) -> @label = 'D',
}
]
}
contextMenu.add('.grandchild': [item])
dispatchedEvent = {target: grandchild}
expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual(
[
label: 'A',
command: 'B',
submenu: [
{
label: 'D',
}
]
])
describe "::templateForEvent(target)", ->
[keymaps, item] = []
beforeEach ->
keymaps = atom.keymaps.add('source', {
'.child': {
'ctrl-a': 'test:my-command',
'shift-b': 'test:my-other-command'
}
})
item = {
label: 'My Command',
command: 'test:my-command',
submenu: [
{
label: 'My Other Command',
command: 'test:my-other-command',
}
]
}
contextMenu.add('.parent': [item])
afterEach ->
keymaps.dispose()
it "adds Electron-style accelerators to items that have keybindings", ->
child.focus()
dispatchedEvent = {target: child}
expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual(
[
label: 'My Command',
command: 'test:my-command',
accelerator: 'Ctrl+A',
submenu: [
{
label: 'My Other Command',
command: 'test:my-other-command',
accelerator: 'Shift+B',
}
]
])
it "adds accelerators when a parent node has key bindings for a given command", ->
grandchild.focus()
dispatchedEvent = {target: grandchild}
expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual(
[
label: 'My Command',
command: 'test:my-command',
accelerator: 'Ctrl+A',
submenu: [
{
label: 'My Other Command',
command: 'test:my-other-command',
accelerator: 'Shift+B',
}
]
])
it "does not add accelerators when a child node has key bindings for a given command", ->
parent.focus()
dispatchedEvent = {target: parent}
expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual(
[
label: 'My Command',
command: 'test:my-command',
submenu: [
{
label: 'My Other Command',
command: 'test:my-other-command',
}
]
])
it "adds accelerators based on focus, not context menu target", ->
grandchild.focus()
dispatchedEvent = {target: parent}
expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual(
[
label: 'My Command',
command: 'test:my-command',
accelerator: 'Ctrl+A',
submenu: [
{
label: 'My Other Command',
command: 'test:my-other-command',
accelerator: 'Shift+B',
}
]
])
it "does not add accelerators for multi-keystroke key bindings", ->
atom.keymaps.add('source', {
'.child': {
'ctrl-a ctrl-b': 'test:multi-keystroke-command'
}
})
contextMenu.clear()
contextMenu.add('.parent': [{
label: 'Multi-keystroke command',
command: 'test:multi-keystroke-command',
}])
child.focus()
label =
if process.platform is 'darwin'
'⌃A ⌃B'
else
'Ctrl+A Ctrl+B'
expect(contextMenu.templateForEvent({target: child})).toEqual([{
label: "Multi-keystroke command [#{label}]",
command: 'test:multi-keystroke-command',
}])
describe "::templateForEvent(target) (sorting)", ->
it "applies simple sorting rules", ->
contextMenu.add('.parent': [{
label: 'My Command',
command: "test:my-command",
after: ["test:my-other-command"]
}, {
label: 'My Other Command',
command: "test:my-other-command",
}])
dispatchedEvent = {target: parent}
expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual([{
label: 'My Other Command',
command: 'test:my-other-command',
}, {
label: 'My Command',
command: 'test:my-command',
after: ["test:my-other-command"]
}])
it "applies sorting rules recursively to submenus", ->
contextMenu.add('.parent': [{
submenu: [{
label: 'My Command',
command: "test:my-command",
after: ["test:my-other-command"]
}, {
label: 'My Other Command',
command: "test:my-other-command",
}]
}])
dispatchedEvent = {target: parent}
expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual([{
submenu: [{
label: 'My Other Command',
command: 'test:my-other-command',
}, {
label: 'My Command',
command: 'test:my-command',
after: ["test:my-other-command"]
}]
}])