2013-09-21 01:14:17 +04:00
|
|
|
ContextMenuManager = require '../src/context-menu-manager'
|
2013-09-20 01:21:31 +04:00
|
|
|
|
|
|
|
describe "ContextMenuManager", ->
|
2014-09-29 21:40:04 +04:00
|
|
|
[contextMenu, parent, child, grandchild] = []
|
2013-09-20 01:21:31 +04:00
|
|
|
|
|
|
|
beforeEach ->
|
2014-08-28 21:42:59 +04:00
|
|
|
{resourcePath} = atom.getLoadSettings()
|
2017-03-11 21:05:28 +03:00
|
|
|
contextMenu = new ContextMenuManager({keymapManager: atom.keymaps})
|
|
|
|
contextMenu.initialize({resourcePath})
|
2013-09-20 01:21:31 +04:00
|
|
|
|
2014-09-29 21:40:04 +04:00
|
|
|
parent = document.createElement("div")
|
|
|
|
child = document.createElement("div")
|
|
|
|
grandchild = document.createElement("div")
|
2017-08-16 05:40:43 +03:00
|
|
|
parent.tabIndex = -1
|
|
|
|
child.tabIndex = -1
|
|
|
|
grandchild.tabIndex = -1
|
2014-09-29 21:40:04 +04:00
|
|
|
parent.classList.add('parent')
|
|
|
|
child.classList.add('child')
|
|
|
|
grandchild.classList.add('grandchild')
|
|
|
|
child.appendChild(grandchild)
|
|
|
|
parent.appendChild(child)
|
|
|
|
|
2017-08-16 05:40:43 +03:00
|
|
|
document.body.appendChild(parent)
|
|
|
|
|
|
|
|
afterEach ->
|
|
|
|
document.body.blur()
|
|
|
|
document.body.removeChild(parent)
|
|
|
|
|
|
|
|
|
2014-09-29 21:40:04 +04:00
|
|
|
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'}
|
|
|
|
]
|
2013-09-20 01:21:31 +04:00
|
|
|
|
2014-09-29 21:40:04 +04:00
|
|
|
disposable.dispose()
|
|
|
|
expect(contextMenu.templateForElement(grandchild)).toEqual []
|
2013-09-20 01:21:31 +04:00
|
|
|
|
2014-09-29 21:40:04 +04:00
|
|
|
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'}]}]
|
2013-09-20 01:21:31 +04:00
|
|
|
|
2014-09-29 21:40:04 +04:00
|
|
|
expect(contextMenu.templateForElement(grandchild)).toEqual [{
|
|
|
|
label: 'A',
|
|
|
|
submenu: [
|
|
|
|
{label: 'B', command: 'b'}
|
2014-09-30 20:49:52 +04:00
|
|
|
{label: 'C', command: 'c'}
|
2014-09-29 21:40:04 +04:00
|
|
|
]
|
|
|
|
}]
|
2013-09-20 01:21:31 +04:00
|
|
|
|
2014-09-29 21:40:04 +04:00
|
|
|
disposable2.dispose()
|
|
|
|
expect(contextMenu.templateForElement(grandchild)).toEqual [{
|
|
|
|
label: 'A',
|
|
|
|
submenu: [
|
|
|
|
{label: 'B', command: 'b'}
|
|
|
|
]
|
|
|
|
}]
|
2013-09-20 01:21:31 +04:00
|
|
|
|
2014-09-29 21:40:04 +04:00
|
|
|
disposable1.dispose()
|
|
|
|
expect(contextMenu.templateForElement(grandchild)).toEqual []
|
2013-09-20 01:21:31 +04:00
|
|
|
|
2014-09-29 21:40:04 +04:00
|
|
|
it "favors the most specific / recently added item in the case of a duplicate label", ->
|
|
|
|
grandchild.classList.add('foo')
|
2013-09-20 01:21:31 +04:00
|
|
|
|
2014-09-29 21:40:04 +04:00
|
|
|
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'}]
|
2014-09-30 21:44:48 +04:00
|
|
|
disposable4 = contextMenu.add
|
|
|
|
'.child': [{label: 'A', command: 'd'}]
|
2013-09-20 01:21:31 +04:00
|
|
|
|
2014-09-29 21:40:04 +04:00
|
|
|
expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'A', command: 'b'}]
|
2013-09-20 01:21:31 +04:00
|
|
|
|
2014-09-29 21:40:04 +04:00
|
|
|
disposable2.dispose()
|
|
|
|
expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'A', command: 'c'}]
|
2013-09-20 01:21:31 +04:00
|
|
|
|
2014-09-29 21:40:04 +04:00
|
|
|
disposable3.dispose()
|
|
|
|
expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'A', command: 'a'}]
|
2013-09-20 01:21:31 +04:00
|
|
|
|
2014-09-30 21:44:48 +04:00
|
|
|
disposable1.dispose()
|
|
|
|
expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'A', command: 'd'}]
|
|
|
|
|
2014-09-30 21:51:49 +04:00
|
|
|
it "allows multiple separators, but not adjacent to each other", ->
|
2014-09-29 21:40:04 +04:00
|
|
|
contextMenu.add
|
|
|
|
'.grandchild': [
|
|
|
|
{label: 'A', command: 'a'},
|
|
|
|
{type: 'separator'},
|
2014-09-30 21:51:49 +04:00
|
|
|
{type: 'separator'},
|
2014-09-29 21:40:04 +04:00
|
|
|
{label: 'B', command: 'b'},
|
|
|
|
{type: 'separator'},
|
2014-09-30 21:51:49 +04:00
|
|
|
{type: 'separator'},
|
2014-09-29 21:40:04 +04:00
|
|
|
{label: 'C', command: 'c'}
|
|
|
|
]
|
2013-09-20 01:21:31 +04:00
|
|
|
|
2014-09-29 21:40:04 +04:00
|
|
|
expect(contextMenu.templateForElement(grandchild)).toEqual [
|
|
|
|
{label: 'A', command: 'a'},
|
|
|
|
{type: 'separator'},
|
|
|
|
{label: 'B', command: 'b'},
|
|
|
|
{type: 'separator'},
|
|
|
|
{label: 'C', command: 'c'}
|
|
|
|
]
|
2013-09-20 01:21:31 +04:00
|
|
|
|
2014-09-30 01:24:30 +04:00
|
|
|
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'}]
|
|
|
|
|
2014-09-30 01:51:15 +04:00
|
|
|
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
|
|
|
|
|
2014-09-30 02:06:09 +04:00
|
|
|
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])
|
2013-09-20 01:21:31 +04:00
|
|
|
|
2014-09-30 02:06:09 +04:00
|
|
|
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
|
2013-09-20 01:21:31 +04:00
|
|
|
|
2014-09-30 02:06:09 +04:00
|
|
|
shouldDisplay = false
|
|
|
|
expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual []
|
2014-10-02 20:47:03 +04:00
|
|
|
|
2016-08-26 05:44:57 +03:00
|
|
|
it "prunes a trailing separator", ->
|
2016-08-25 07:45:21 +03:00
|
|
|
contextMenu.add
|
|
|
|
'.grandchild': [
|
|
|
|
{label: 'A', command: 'a'},
|
|
|
|
{type: 'separator'},
|
|
|
|
{label: 'B', command: 'b'},
|
|
|
|
{type: 'separator'}
|
|
|
|
]
|
|
|
|
|
|
|
|
expect(contextMenu.templateForEvent({target: grandchild}).length).toBe(3)
|
|
|
|
|
2016-08-26 05:44:57 +03:00
|
|
|
it "prunes a leading separator", ->
|
2016-08-25 22:49:29 +03:00
|
|
|
contextMenu.add
|
|
|
|
'.grandchild': [
|
|
|
|
{type: 'separator'},
|
|
|
|
{label: 'A', command: 'a'},
|
|
|
|
{type: 'separator'},
|
|
|
|
{label: 'B', command: 'b'}
|
|
|
|
]
|
|
|
|
|
|
|
|
expect(contextMenu.templateForEvent({target: grandchild}).length).toBe(3)
|
|
|
|
|
2016-08-26 05:44:57 +03:00
|
|
|
it "prunes duplicate separators", ->
|
2016-08-25 22:49:29 +03:00
|
|
|
contextMenu.add
|
|
|
|
'.grandchild': [
|
|
|
|
{label: 'A', command: 'a'},
|
|
|
|
{type: 'separator'},
|
|
|
|
{type: 'separator'},
|
|
|
|
{label: 'B', command: 'b'}
|
|
|
|
]
|
|
|
|
|
|
|
|
expect(contextMenu.templateForEvent({target: grandchild}).length).toBe(3)
|
|
|
|
|
2016-08-26 05:44:57 +03:00
|
|
|
it "prunes all redundant separators", ->
|
2016-08-25 22:49:29 +03:00
|
|
|
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)
|
|
|
|
|
2015-03-04 01:52:18 +03:00
|
|
|
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('<>')
|
2016-01-19 03:50:16 +03:00
|
|
|
|
2016-01-26 00:19:58 +03:00
|
|
|
it "calls `created` hooks for submenu items", ->
|
2016-01-19 03:50:16 +03:00
|
|
|
item = {
|
|
|
|
label: 'A',
|
|
|
|
command: 'B',
|
|
|
|
submenu: [
|
|
|
|
{
|
|
|
|
label: 'C',
|
2016-01-21 21:18:04 +03:00
|
|
|
created: (event) -> @label = 'D',
|
2016-01-19 03:50:16 +03:00
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
contextMenu.add('.grandchild': [item])
|
|
|
|
|
|
|
|
dispatchedEvent = {target: grandchild}
|
|
|
|
expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual(
|
|
|
|
[
|
|
|
|
label: 'A',
|
|
|
|
command: 'B',
|
|
|
|
submenu: [
|
|
|
|
{
|
|
|
|
label: 'D',
|
|
|
|
}
|
|
|
|
]
|
|
|
|
])
|
2017-08-12 07:42:41 +03:00
|
|
|
|
|
|
|
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", ->
|
2017-08-16 05:40:43 +03:00
|
|
|
child.focus()
|
2017-08-12 07:42:41 +03:00
|
|
|
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", ->
|
2017-08-16 05:40:43 +03:00
|
|
|
grandchild.focus()
|
2017-08-12 07:42:41 +03:00
|
|
|
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", ->
|
2017-08-16 05:40:43 +03:00
|
|
|
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()
|
2017-08-12 07:42:41 +03:00
|
|
|
dispatchedEvent = {target: parent}
|
|
|
|
expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual(
|
|
|
|
[
|
|
|
|
label: 'My Command',
|
|
|
|
command: 'test:my-command',
|
2017-08-16 05:40:43 +03:00
|
|
|
accelerator: 'Ctrl+A',
|
2017-08-12 07:42:41 +03:00
|
|
|
submenu: [
|
|
|
|
{
|
|
|
|
label: 'My Other Command',
|
|
|
|
command: 'test:my-other-command',
|
2017-08-16 05:40:43 +03:00
|
|
|
accelerator: 'Shift+B',
|
2017-08-12 07:42:41 +03:00
|
|
|
}
|
|
|
|
]
|
|
|
|
])
|