123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- const ContextMenuManager = require('../src/context-menu-manager');
- describe('ContextMenuManager', function() {
- let [contextMenu, parent, child, grandchild] = [];
- beforeEach(function() {
- const { 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(function() {
- document.body.blur();
- document.body.removeChild(parent);
- });
- describe('::add(itemsBySelector)', function() {
- it('can add top-level menu items that can be removed with the returned disposable', function() {
- const 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', id: 'C', command: 'c' },
- { label: 'B', id: 'B', command: 'b' },
- { label: 'A', id: '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', function() {
- const disposable1 = contextMenu.add({
- '.grandchild': [{ label: 'A', submenu: [{ label: 'B', command: 'b' }] }]
- });
- const disposable2 = contextMenu.add({
- '.grandchild': [{ label: 'A', submenu: [{ label: 'C', command: 'c' }] }]
- });
- expect(contextMenu.templateForElement(grandchild)).toEqual([
- {
- label: 'A',
- id: 'A',
- submenu: [
- { label: 'B', id: 'B', command: 'b' },
- { label: 'C', id: 'C', command: 'c' }
- ]
- }
- ]);
- disposable2.dispose();
- expect(contextMenu.templateForElement(grandchild)).toEqual([
- {
- label: 'A',
- id: 'A',
- submenu: [{ label: 'B', id: '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', function() {
- grandchild.classList.add('foo');
- const disposable1 = contextMenu.add({
- '.grandchild': [{ label: 'A', command: 'a' }]
- });
- const disposable2 = contextMenu.add({
- '.grandchild.foo': [{ label: 'A', command: 'b' }]
- });
- const disposable3 = contextMenu.add({
- '.grandchild': [{ label: 'A', command: 'c' }]
- });
- contextMenu.add({
- '.child': [{ label: 'A', command: 'd' }]
- });
- expect(contextMenu.templateForElement(grandchild)).toEqual([
- { label: 'A', id: 'A', command: 'b' }
- ]);
- disposable2.dispose();
- expect(contextMenu.templateForElement(grandchild)).toEqual([
- { label: 'A', id: 'A', command: 'c' }
- ]);
- disposable3.dispose();
- expect(contextMenu.templateForElement(grandchild)).toEqual([
- { label: 'A', id: 'A', command: 'a' }
- ]);
- disposable1.dispose();
- expect(contextMenu.templateForElement(grandchild)).toEqual([
- { label: 'A', id: 'A', command: 'd' }
- ]);
- });
- it('allows multiple separators, but not adjacent to each other', function() {
- 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', id: 'A', command: 'a' },
- { type: 'separator' },
- { label: 'B', id: 'B', command: 'b' },
- { type: 'separator' },
- { label: 'C', id: 'C', command: 'c' }
- ]);
- });
- it('excludes items marked for display in devMode unless in dev mode', function() {
- contextMenu.add({
- '.grandchild': [
- { label: 'A', command: 'a', devMode: true },
- { label: 'B', command: 'b', devMode: false }
- ]
- });
- expect(contextMenu.templateForElement(grandchild)).toEqual([
- { label: 'B', id: 'B', command: 'b' }
- ]);
- contextMenu.devMode = true;
- expect(contextMenu.templateForElement(grandchild)).toEqual([
- { label: 'A', id: 'A', command: 'a' },
- { label: 'B', id: 'B', command: 'b' }
- ]);
- });
- it('allows items to be associated with `created` hooks which are invoked on template construction with the item and event', function() {
- let createdEvent = null;
- const item = {
- label: 'A',
- command: 'a',
- created(event) {
- this.command = 'b';
- createdEvent = event;
- }
- };
- contextMenu.add({ '.grandchild': [item] });
- const dispatchedEvent = { target: grandchild };
- expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual([
- { label: 'A', id: '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', function() {
- let shouldDisplayEvent = null;
- let shouldDisplay = true;
- const item = {
- label: 'A',
- command: 'a',
- shouldDisplay(event) {
- this.foo = 'bar';
- shouldDisplayEvent = event;
- return shouldDisplay;
- }
- };
- contextMenu.add({ '.grandchild': [item] });
- const dispatchedEvent = { target: grandchild };
- expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual([
- { label: 'A', id: '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', function() {
- 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', function() {
- 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', function() {
- 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', function() {
- 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', function() {
- let addError = null;
- try {
- contextMenu.add({ '<>': [{ label: 'A', command: 'a' }] });
- } catch (error) {
- addError = error;
- }
- expect(addError.message).toContain('<>');
- });
- it('calls `created` hooks for submenu items', function() {
- const item = {
- label: 'A',
- command: 'B',
- submenu: [
- {
- label: 'C',
- created(event) {
- this.label = 'D';
- }
- }
- ]
- };
- contextMenu.add({ '.grandchild': [item] });
- const dispatchedEvent = { target: grandchild };
- expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual([
- {
- label: 'A',
- id: 'A',
- command: 'B',
- submenu: [
- {
- label: 'D',
- id: 'D'
- }
- ]
- }
- ]);
- });
- });
- describe('::templateForEvent(target)', function() {
- let [keymaps, item] = [];
- beforeEach(function() {
- 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', function() {
- child.focus();
- const dispatchedEvent = { target: child };
- expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual([
- {
- label: 'My Command',
- id: 'My Command',
- command: 'test:my-command',
- accelerator: 'Ctrl+A',
- submenu: [
- {
- label: 'My Other Command',
- id: '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', function() {
- grandchild.focus();
- const dispatchedEvent = { target: grandchild };
- expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual([
- {
- label: 'My Command',
- id: 'My Command',
- command: 'test:my-command',
- accelerator: 'Ctrl+A',
- submenu: [
- {
- label: 'My Other Command',
- id: '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', function() {
- parent.focus();
- const dispatchedEvent = { target: parent };
- expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual([
- {
- label: 'My Command',
- id: 'My Command',
- command: 'test:my-command',
- submenu: [
- {
- label: 'My Other Command',
- id: 'My Other Command',
- command: 'test:my-other-command'
- }
- ]
- }
- ]);
- });
- it('adds accelerators based on focus, not context menu target', function() {
- grandchild.focus();
- const dispatchedEvent = { target: parent };
- expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual([
- {
- label: 'My Command',
- id: 'My Command',
- command: 'test:my-command',
- accelerator: 'Ctrl+A',
- submenu: [
- {
- label: 'My Other Command',
- id: 'My Other Command',
- command: 'test:my-other-command',
- accelerator: 'Shift+B'
- }
- ]
- }
- ]);
- });
- it('does not add accelerators for multi-keystroke key bindings', function() {
- 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();
- const label = process.platform === 'darwin' ? '⌃A ⌃B' : 'Ctrl+A Ctrl+B';
- expect(contextMenu.templateForEvent({ target: child })).toEqual([
- {
- label: `Multi-keystroke command [${label}]`,
- id: `Multi-keystroke command`,
- command: 'test:multi-keystroke-command'
- }
- ]);
- });
- });
- describe('::templateForEvent(target) (sorting)', function() {
- it('applies simple sorting rules', function() {
- contextMenu.add({
- '.parent': [
- {
- label: 'My Command',
- command: 'test:my-command',
- after: ['test:my-other-command']
- },
- {
- label: 'My Other Command',
- command: 'test:my-other-command'
- }
- ]
- });
- const dispatchedEvent = { target: parent };
- expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual([
- {
- label: 'My Other Command',
- id: 'My Other Command',
- command: 'test:my-other-command'
- },
- {
- label: 'My Command',
- id: 'My Command',
- command: 'test:my-command',
- after: ['test:my-other-command']
- }
- ]);
- });
- it('applies sorting rules recursively to submenus', function() {
- contextMenu.add({
- '.parent': [
- {
- label: 'Parent',
- submenu: [
- {
- label: 'My Command',
- command: 'test:my-command',
- after: ['test:my-other-command']
- },
- {
- label: 'My Other Command',
- command: 'test:my-other-command'
- }
- ]
- }
- ]
- });
- const dispatchedEvent = { target: parent };
- expect(contextMenu.templateForEvent(dispatchedEvent)).toEqual([
- {
- label: 'Parent',
- id: `Parent`,
- submenu: [
- {
- label: 'My Other Command',
- id: 'My Other Command',
- command: 'test:my-other-command'
- },
- {
- label: 'My Command',
- id: 'My Command',
- command: 'test:my-command',
- after: ['test:my-other-command']
- }
- ]
- }
- ]);
- });
- });
- });
|