2020-03-20 20:28:31 +00:00
|
|
|
import * as cp from 'child_process';
|
|
|
|
import * as path from 'path';
|
|
|
|
import { expect } from 'chai';
|
2020-04-07 00:04:09 +00:00
|
|
|
import { BrowserWindow, Menu, MenuItem } from 'electron/main';
|
2020-03-20 20:28:31 +00:00
|
|
|
import { sortMenuItems } from '../lib/browser/api/menu-utils';
|
|
|
|
import { emittedOnce } from './events-helpers';
|
2020-07-01 19:14:38 +00:00
|
|
|
import { ifit, delay } from './spec-helpers';
|
2020-03-20 20:28:31 +00:00
|
|
|
import { closeWindow } from './window-helpers';
|
2016-01-12 02:40:23 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
2019-12-09 19:17:36 +00:00
|
|
|
|
2019-07-26 14:17:14 +00:00
|
|
|
describe('Menu module', function () {
|
2017-10-27 20:45:58 +00:00
|
|
|
describe('Menu.buildFromTemplate', () => {
|
|
|
|
it('should be able to attach extra fields', () => {
|
2017-10-25 00:27:26 +00:00
|
|
|
const menu = Menu.buildFromTemplate([
|
2016-01-12 02:40:23 +00:00
|
|
|
{
|
|
|
|
label: 'text',
|
|
|
|
extra: 'field'
|
2019-07-26 14:17:14 +00:00
|
|
|
} as MenuItem | Record<string, any>
|
2020-03-20 20:28:31 +00:00
|
|
|
]);
|
|
|
|
expect((menu.items[0] as any).extra).to.equal('field');
|
|
|
|
});
|
2016-02-17 01:09:41 +00:00
|
|
|
|
2019-02-06 18:04:40 +00:00
|
|
|
it('should be able to accept only MenuItems', () => {
|
|
|
|
const menu = Menu.buildFromTemplate([
|
|
|
|
new MenuItem({ label: 'one' }),
|
|
|
|
new MenuItem({ label: 'two' })
|
2020-03-20 20:28:31 +00:00
|
|
|
]);
|
|
|
|
expect(menu.items[0].label).to.equal('one');
|
|
|
|
expect(menu.items[1].label).to.equal('two');
|
|
|
|
});
|
2019-02-06 18:04:40 +00:00
|
|
|
|
|
|
|
it('should be able to accept only MenuItems in a submenu', () => {
|
|
|
|
const menu = Menu.buildFromTemplate([
|
|
|
|
{
|
|
|
|
label: 'one',
|
|
|
|
submenu: [
|
2019-07-26 14:17:14 +00:00
|
|
|
new MenuItem({ label: 'two' }) as any
|
2019-02-06 18:04:40 +00:00
|
|
|
]
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
]);
|
|
|
|
expect(menu.items[0].label).to.equal('one');
|
|
|
|
expect(menu.items[0].submenu!.items[0].label).to.equal('two');
|
|
|
|
});
|
2019-02-06 18:04:40 +00:00
|
|
|
|
|
|
|
it('should be able to accept MenuItems and plain objects', () => {
|
|
|
|
const menu = Menu.buildFromTemplate([
|
|
|
|
new MenuItem({ label: 'one' }),
|
|
|
|
{ label: 'two' }
|
2020-03-20 20:28:31 +00:00
|
|
|
]);
|
|
|
|
expect(menu.items[0].label).to.equal('one');
|
|
|
|
expect(menu.items[1].label).to.equal('two');
|
|
|
|
});
|
2019-02-06 18:04:40 +00:00
|
|
|
|
2017-10-27 20:45:58 +00:00
|
|
|
it('does not modify the specified template', () => {
|
2020-03-20 20:28:31 +00:00
|
|
|
const template = [{ label: 'text', submenu: [{ label: 'sub' }] }];
|
|
|
|
const templateCopy = JSON.parse(JSON.stringify(template));
|
|
|
|
Menu.buildFromTemplate(template);
|
|
|
|
expect(template).to.deep.equal(templateCopy);
|
|
|
|
});
|
2016-02-17 01:09:41 +00:00
|
|
|
|
2017-10-27 20:45:58 +00:00
|
|
|
it('does not throw exceptions for undefined/null values', () => {
|
2018-06-30 07:25:28 +00:00
|
|
|
expect(() => {
|
2016-03-17 23:09:16 +00:00
|
|
|
Menu.buildFromTemplate([
|
|
|
|
{
|
|
|
|
label: 'text',
|
|
|
|
accelerator: undefined
|
2016-03-17 23:14:31 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
label: 'text again',
|
2019-07-26 14:17:14 +00:00
|
|
|
accelerator: null as any
|
2016-03-17 23:09:16 +00:00
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
]);
|
|
|
|
}).to.not.throw();
|
|
|
|
});
|
2016-03-17 23:09:16 +00:00
|
|
|
|
2018-08-17 20:10:14 +00:00
|
|
|
it('does throw exceptions for empty objects and null values', () => {
|
|
|
|
expect(() => {
|
2020-03-20 20:28:31 +00:00
|
|
|
Menu.buildFromTemplate([{}, null as any]);
|
|
|
|
}).to.throw(/Invalid template for MenuItem: must have at least one of label, role or type/);
|
|
|
|
});
|
2018-08-17 20:10:14 +00:00
|
|
|
|
|
|
|
it('does throw exception for object without role, label, or type attribute', () => {
|
|
|
|
expect(() => {
|
2020-03-20 20:28:31 +00:00
|
|
|
Menu.buildFromTemplate([{ visible: true }]);
|
|
|
|
}).to.throw(/Invalid template for MenuItem: must have at least one of label, role or type/);
|
|
|
|
});
|
2020-04-30 15:29:02 +00:00
|
|
|
|
2018-08-17 20:10:14 +00:00
|
|
|
it('does throw exception for undefined', () => {
|
|
|
|
expect(() => {
|
2020-03-20 20:28:31 +00:00
|
|
|
Menu.buildFromTemplate([undefined as any]);
|
|
|
|
}).to.throw(/Invalid template for MenuItem: must have at least one of label, role or type/);
|
|
|
|
});
|
2018-08-17 20:10:14 +00:00
|
|
|
|
2020-04-30 15:29:02 +00:00
|
|
|
it('throws when an non-array is passed as a template', () => {
|
|
|
|
expect(() => {
|
|
|
|
Menu.buildFromTemplate('hello' as any);
|
|
|
|
}).to.throw(/Invalid template for Menu: Menu template must be an array/);
|
|
|
|
});
|
|
|
|
|
2018-05-05 16:37:29 +00:00
|
|
|
describe('Menu sorting and building', () => {
|
|
|
|
describe('sorts groups', () => {
|
|
|
|
it('does a simple sort', () => {
|
|
|
|
const items = [
|
|
|
|
{
|
|
|
|
label: 'two',
|
|
|
|
id: '2',
|
2020-03-20 15:12:18 +00:00
|
|
|
afterGroupContaining: ['1']
|
|
|
|
},
|
2018-05-05 16:37:29 +00:00
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one'
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
|
|
|
const expected = [
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one'
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two',
|
|
|
|
afterGroupContaining: ['1']
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(sortMenuItems(items)).to.deep.equal(expected);
|
|
|
|
});
|
2018-05-05 16:37:29 +00:00
|
|
|
|
2019-02-06 18:04:40 +00:00
|
|
|
it('does a simple sort with MenuItems', () => {
|
2020-03-20 20:28:31 +00:00
|
|
|
const firstItem = new MenuItem({ id: '1', label: 'one' });
|
2019-02-06 18:04:40 +00:00
|
|
|
const secondItem = new MenuItem({
|
|
|
|
label: 'two',
|
|
|
|
id: '2',
|
|
|
|
afterGroupContaining: ['1']
|
2020-03-20 20:28:31 +00:00
|
|
|
});
|
|
|
|
const sep = new MenuItem({ type: 'separator' });
|
2019-02-06 18:04:40 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
const items = [secondItem, sep, firstItem];
|
|
|
|
const expected = [firstItem, sep, secondItem];
|
2019-02-06 18:04:40 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(sortMenuItems(items)).to.deep.equal(expected);
|
|
|
|
});
|
2019-02-06 18:04:40 +00:00
|
|
|
|
2018-05-05 16:37:29 +00:00
|
|
|
it('resolves cycles by ignoring things that conflict', () => {
|
|
|
|
const items = [
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two',
|
|
|
|
afterGroupContaining: ['1']
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one',
|
|
|
|
afterGroupContaining: ['2']
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
|
|
|
const expected = [
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one',
|
|
|
|
afterGroupContaining: ['2']
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two',
|
|
|
|
afterGroupContaining: ['1']
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(sortMenuItems(items)).to.deep.equal(expected);
|
|
|
|
});
|
2018-05-05 16:37:29 +00:00
|
|
|
|
|
|
|
it('ignores references to commands that do not exist', () => {
|
|
|
|
const items = [
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one'
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two',
|
|
|
|
afterGroupContaining: ['does-not-exist']
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
|
|
|
const expected = [
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one'
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two',
|
|
|
|
afterGroupContaining: ['does-not-exist']
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(sortMenuItems(items)).to.deep.equal(expected);
|
|
|
|
});
|
2018-05-05 16:37:29 +00:00
|
|
|
|
|
|
|
it('only respects the first matching [before|after]GroupContaining rule in a given group', () => {
|
|
|
|
const items = [
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one'
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '3',
|
|
|
|
label: 'three',
|
|
|
|
beforeGroupContaining: ['1']
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: '4',
|
|
|
|
label: 'four',
|
|
|
|
afterGroupContaining: ['2']
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two'
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
|
|
|
const expected = [
|
|
|
|
{
|
|
|
|
id: '3',
|
|
|
|
label: 'three',
|
|
|
|
beforeGroupContaining: ['1']
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: '4',
|
|
|
|
label: 'four',
|
|
|
|
afterGroupContaining: ['2']
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one'
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two'
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(sortMenuItems(items)).to.deep.equal(expected);
|
|
|
|
});
|
|
|
|
});
|
2018-05-05 16:37:29 +00:00
|
|
|
|
|
|
|
describe('moves an item to a different group by merging groups', () => {
|
|
|
|
it('can move a group of one item', () => {
|
|
|
|
const items = [
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one'
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two'
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '3',
|
|
|
|
label: 'three',
|
|
|
|
after: ['1']
|
|
|
|
},
|
|
|
|
{ type: 'separator' }
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
|
|
|
const expected = [
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: '3',
|
|
|
|
label: 'three',
|
|
|
|
after: ['1']
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two'
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(sortMenuItems(items)).to.deep.equal(expected);
|
|
|
|
});
|
2018-05-05 16:37:29 +00:00
|
|
|
|
|
|
|
it("moves all items in the moving item's group", () => {
|
|
|
|
const items = [
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one'
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two'
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '3',
|
|
|
|
label: 'three',
|
|
|
|
after: ['1']
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: '4',
|
|
|
|
label: 'four'
|
|
|
|
},
|
|
|
|
{ type: 'separator' }
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
|
|
|
const expected = [
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: '3',
|
|
|
|
label: 'three',
|
|
|
|
after: ['1']
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: '4',
|
|
|
|
label: 'four'
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two'
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(sortMenuItems(items)).to.deep.equal(expected);
|
|
|
|
});
|
2018-05-05 16:37:29 +00:00
|
|
|
|
|
|
|
it("ignores positions relative to commands that don't exist", () => {
|
|
|
|
const items = [
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one'
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two'
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '3',
|
|
|
|
label: 'three',
|
|
|
|
after: ['does-not-exist']
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: '4',
|
|
|
|
label: 'four',
|
|
|
|
after: ['1']
|
|
|
|
},
|
|
|
|
{ type: 'separator' }
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
|
|
|
const expected = [
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: '3',
|
|
|
|
label: 'three',
|
|
|
|
after: ['does-not-exist']
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: '4',
|
|
|
|
label: 'four',
|
|
|
|
after: ['1']
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two'
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(sortMenuItems(items)).to.deep.equal(expected);
|
|
|
|
});
|
2018-05-05 16:37:29 +00:00
|
|
|
|
|
|
|
it('can handle recursive group merging', () => {
|
|
|
|
const items = [
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one',
|
|
|
|
after: ['3']
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two',
|
|
|
|
before: ['1']
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: '3',
|
|
|
|
label: 'three'
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
|
|
|
const expected = [
|
|
|
|
{
|
|
|
|
id: '3',
|
|
|
|
label: 'three'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two',
|
|
|
|
before: ['1']
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one',
|
|
|
|
after: ['3']
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(sortMenuItems(items)).to.deep.equal(expected);
|
|
|
|
});
|
2018-05-05 16:37:29 +00:00
|
|
|
|
|
|
|
it('can merge multiple groups when given a list of before/after commands', () => {
|
|
|
|
const items = [
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one'
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two'
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '3',
|
|
|
|
label: 'three',
|
|
|
|
after: ['1', '2']
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
|
|
|
const expected = [
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: '3',
|
|
|
|
label: 'three',
|
|
|
|
after: ['1', '2']
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(sortMenuItems(items)).to.deep.equal(expected);
|
|
|
|
});
|
2018-05-05 16:37:29 +00:00
|
|
|
|
|
|
|
it('can merge multiple groups based on both before/after commands', () => {
|
|
|
|
const items = [
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one'
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two'
|
|
|
|
},
|
|
|
|
{ type: 'separator' },
|
|
|
|
{
|
|
|
|
id: '3',
|
|
|
|
label: 'three',
|
|
|
|
after: ['1'],
|
|
|
|
before: ['2']
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
|
|
|
const expected = [
|
|
|
|
{
|
|
|
|
id: '1',
|
|
|
|
label: 'one'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: '3',
|
|
|
|
label: 'three',
|
|
|
|
after: ['1'],
|
|
|
|
before: ['2']
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: '2',
|
|
|
|
label: 'two'
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
];
|
2018-05-05 16:37:29 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(sortMenuItems(items)).to.deep.equal(expected);
|
|
|
|
});
|
|
|
|
});
|
2018-05-05 16:37:29 +00:00
|
|
|
|
2017-10-27 20:45:58 +00:00
|
|
|
it('should position before existing item', () => {
|
2017-10-25 00:27:26 +00:00
|
|
|
const menu = Menu.buildFromTemplate([
|
2016-01-12 02:40:23 +00:00
|
|
|
{
|
2018-05-05 16:37:29 +00:00
|
|
|
id: '2',
|
|
|
|
label: 'two'
|
2016-01-12 02:40:23 +00:00
|
|
|
}, {
|
2018-05-05 16:37:29 +00:00
|
|
|
id: '3',
|
|
|
|
label: 'three'
|
2016-01-12 02:40:23 +00:00
|
|
|
}, {
|
|
|
|
id: '1',
|
2018-05-05 16:37:29 +00:00
|
|
|
label: 'one',
|
|
|
|
before: ['2']
|
2016-01-12 02:40:23 +00:00
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
]);
|
2018-05-05 16:37:29 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(menu.items[0].label).to.equal('one');
|
|
|
|
expect(menu.items[1].label).to.equal('two');
|
|
|
|
expect(menu.items[2].label).to.equal('three');
|
|
|
|
});
|
2016-02-17 01:09:41 +00:00
|
|
|
|
2017-10-27 20:45:58 +00:00
|
|
|
it('should position after existing item', () => {
|
2017-10-25 00:27:26 +00:00
|
|
|
const menu = Menu.buildFromTemplate([
|
2016-01-12 02:40:23 +00:00
|
|
|
{
|
|
|
|
id: '2',
|
2018-05-05 16:37:29 +00:00
|
|
|
label: 'two',
|
|
|
|
after: ['1']
|
|
|
|
},
|
2016-01-12 02:40:23 +00:00
|
|
|
{
|
|
|
|
id: '1',
|
2018-05-05 16:37:29 +00:00
|
|
|
label: 'one'
|
2016-01-12 02:40:23 +00:00
|
|
|
}, {
|
|
|
|
id: '3',
|
2018-05-05 16:37:29 +00:00
|
|
|
label: 'three'
|
2016-01-12 02:40:23 +00:00
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
]);
|
2018-02-05 17:55:12 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(menu.items[0].label).to.equal('one');
|
|
|
|
expect(menu.items[1].label).to.equal('two');
|
|
|
|
expect(menu.items[2].label).to.equal('three');
|
|
|
|
});
|
2018-02-05 17:55:12 +00:00
|
|
|
|
|
|
|
it('should filter excess menu separators', () => {
|
|
|
|
const menuOne = Menu.buildFromTemplate([
|
|
|
|
{
|
|
|
|
type: 'separator'
|
|
|
|
}, {
|
|
|
|
label: 'a'
|
|
|
|
}, {
|
|
|
|
label: 'b'
|
|
|
|
}, {
|
|
|
|
label: 'c'
|
|
|
|
}, {
|
|
|
|
type: 'separator'
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
]);
|
2018-02-05 17:55:12 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(menuOne.items).to.have.length(3);
|
|
|
|
expect(menuOne.items[0].label).to.equal('a');
|
|
|
|
expect(menuOne.items[1].label).to.equal('b');
|
|
|
|
expect(menuOne.items[2].label).to.equal('c');
|
2018-02-05 17:55:12 +00:00
|
|
|
|
|
|
|
const menuTwo = Menu.buildFromTemplate([
|
|
|
|
{
|
|
|
|
type: 'separator'
|
|
|
|
}, {
|
|
|
|
type: 'separator'
|
|
|
|
}, {
|
|
|
|
label: 'a'
|
|
|
|
}, {
|
|
|
|
label: 'b'
|
|
|
|
}, {
|
|
|
|
label: 'c'
|
|
|
|
}, {
|
|
|
|
type: 'separator'
|
|
|
|
}, {
|
|
|
|
type: 'separator'
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
]);
|
2018-02-05 17:55:12 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(menuTwo.items).to.have.length(3);
|
|
|
|
expect(menuTwo.items[0].label).to.equal('a');
|
|
|
|
expect(menuTwo.items[1].label).to.equal('b');
|
|
|
|
expect(menuTwo.items[2].label).to.equal('c');
|
|
|
|
});
|
2016-02-17 01:09:41 +00:00
|
|
|
|
2018-05-05 16:37:29 +00:00
|
|
|
it('should continue inserting items at next index when no specifier is present', () => {
|
2017-10-25 00:27:26 +00:00
|
|
|
const menu = Menu.buildFromTemplate([
|
2016-01-12 02:40:23 +00:00
|
|
|
{
|
|
|
|
id: '2',
|
2018-05-05 16:37:29 +00:00
|
|
|
label: 'two'
|
2016-01-12 02:40:23 +00:00
|
|
|
}, {
|
|
|
|
id: '3',
|
2018-05-05 16:37:29 +00:00
|
|
|
label: 'three'
|
2016-01-12 02:40:23 +00:00
|
|
|
}, {
|
2018-05-05 16:37:29 +00:00
|
|
|
id: '4',
|
|
|
|
label: 'four'
|
2016-01-12 02:40:23 +00:00
|
|
|
}, {
|
2018-05-05 16:37:29 +00:00
|
|
|
id: '5',
|
|
|
|
label: 'five'
|
2016-01-12 02:40:23 +00:00
|
|
|
}, {
|
2018-05-05 16:37:29 +00:00
|
|
|
id: '1',
|
|
|
|
label: 'one',
|
|
|
|
before: ['2']
|
2016-01-12 02:40:23 +00:00
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
]);
|
2018-05-05 16:37:29 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(menu.items[0].label).to.equal('one');
|
|
|
|
expect(menu.items[1].label).to.equal('two');
|
|
|
|
expect(menu.items[2].label).to.equal('three');
|
|
|
|
expect(menu.items[3].label).to.equal('four');
|
|
|
|
expect(menu.items[4].label).to.equal('five');
|
|
|
|
});
|
2019-02-06 18:04:40 +00:00
|
|
|
|
|
|
|
it('should continue inserting MenuItems at next index when no specifier is present', () => {
|
|
|
|
const menu = Menu.buildFromTemplate([
|
|
|
|
new MenuItem({
|
|
|
|
id: '2',
|
|
|
|
label: 'two'
|
|
|
|
}), new MenuItem({
|
|
|
|
id: '3',
|
|
|
|
label: 'three'
|
|
|
|
}), new MenuItem({
|
|
|
|
id: '4',
|
|
|
|
label: 'four'
|
|
|
|
}), new MenuItem({
|
|
|
|
id: '5',
|
|
|
|
label: 'five'
|
|
|
|
}), new MenuItem({
|
|
|
|
id: '1',
|
|
|
|
label: 'one',
|
|
|
|
before: ['2']
|
|
|
|
})
|
2020-03-20 20:28:31 +00:00
|
|
|
]);
|
2019-02-06 18:04:40 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(menu.items[0].label).to.equal('one');
|
|
|
|
expect(menu.items[1].label).to.equal('two');
|
|
|
|
expect(menu.items[2].label).to.equal('three');
|
|
|
|
expect(menu.items[3].label).to.equal('four');
|
|
|
|
expect(menu.items[4].label).to.equal('five');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2016-02-17 01:09:41 +00:00
|
|
|
|
2017-10-27 20:45:58 +00:00
|
|
|
describe('Menu.getMenuItemById', () => {
|
|
|
|
it('should return the item with the given id', () => {
|
2017-10-25 00:27:26 +00:00
|
|
|
const menu = Menu.buildFromTemplate([
|
2017-09-27 00:05:51 +00:00
|
|
|
{
|
|
|
|
label: 'View',
|
|
|
|
submenu: [
|
|
|
|
{
|
|
|
|
label: 'Enter Fullscreen',
|
2018-05-05 16:37:29 +00:00
|
|
|
accelerator: 'ControlCommandF',
|
2017-09-27 00:05:51 +00:00
|
|
|
id: 'fullScreen'
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
]);
|
|
|
|
const fsc = menu.getMenuItemById('fullScreen');
|
|
|
|
expect(menu.items[0].submenu!.items[0]).to.equal(fsc);
|
|
|
|
});
|
2018-10-19 20:09:40 +00:00
|
|
|
|
|
|
|
it('should return the separator with the given id', () => {
|
|
|
|
const menu = Menu.buildFromTemplate([
|
|
|
|
{
|
|
|
|
label: 'Item 1',
|
|
|
|
id: 'item_1'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 'separator',
|
|
|
|
type: 'separator'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: 'Item 2',
|
|
|
|
id: 'item_2'
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
]);
|
|
|
|
const separator = menu.getMenuItemById('separator');
|
|
|
|
expect(separator).to.be.an('object');
|
|
|
|
expect(separator).to.equal(menu.items[1]);
|
|
|
|
});
|
|
|
|
});
|
2017-09-27 00:05:51 +00:00
|
|
|
|
2017-10-27 20:45:58 +00:00
|
|
|
describe('Menu.insert', () => {
|
2019-03-18 14:58:42 +00:00
|
|
|
it('should throw when attempting to insert at out-of-range indices', () => {
|
|
|
|
const menu = Menu.buildFromTemplate([
|
|
|
|
{ label: '1' },
|
|
|
|
{ label: '2' },
|
|
|
|
{ label: '3' }
|
2020-03-20 20:28:31 +00:00
|
|
|
]);
|
2019-03-18 14:58:42 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
const item = new MenuItem({ label: 'badInsert' });
|
2019-03-18 14:58:42 +00:00
|
|
|
|
|
|
|
expect(() => {
|
2020-03-20 20:28:31 +00:00
|
|
|
menu.insert(9999, item);
|
|
|
|
}).to.throw(/Position 9999 cannot be greater than the total MenuItem count/);
|
2019-03-18 14:58:42 +00:00
|
|
|
|
|
|
|
expect(() => {
|
2020-03-20 20:28:31 +00:00
|
|
|
menu.insert(-9999, item);
|
|
|
|
}).to.throw(/Position -9999 cannot be less than 0/);
|
|
|
|
});
|
2019-03-18 14:58:42 +00:00
|
|
|
|
2017-10-27 20:45:58 +00:00
|
|
|
it('should store item in @items by its index', () => {
|
2017-10-25 00:27:26 +00:00
|
|
|
const menu = Menu.buildFromTemplate([
|
2018-09-13 16:10:51 +00:00
|
|
|
{ label: '1' },
|
|
|
|
{ label: '2' },
|
|
|
|
{ label: '3' }
|
2020-03-20 20:28:31 +00:00
|
|
|
]);
|
2017-10-25 02:40:31 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
const item = new MenuItem({ label: 'inserted' });
|
2017-10-25 00:27:26 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
menu.insert(1, item);
|
|
|
|
expect(menu.items[0].label).to.equal('1');
|
|
|
|
expect(menu.items[1].label).to.equal('inserted');
|
|
|
|
expect(menu.items[2].label).to.equal('2');
|
|
|
|
expect(menu.items[3].label).to.equal('3');
|
|
|
|
});
|
|
|
|
});
|
2016-02-17 01:09:41 +00:00
|
|
|
|
2017-10-27 20:45:58 +00:00
|
|
|
describe('Menu.append', () => {
|
|
|
|
it('should add the item to the end of the menu', () => {
|
2017-10-25 02:40:31 +00:00
|
|
|
const menu = Menu.buildFromTemplate([
|
2018-09-13 16:10:51 +00:00
|
|
|
{ label: '1' },
|
|
|
|
{ label: '2' },
|
|
|
|
{ label: '3' }
|
2020-03-20 20:28:31 +00:00
|
|
|
]);
|
2017-10-25 02:40:31 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
const item = new MenuItem({ label: 'inserted' });
|
|
|
|
menu.append(item);
|
2018-02-23 13:53:59 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(menu.items[0].label).to.equal('1');
|
|
|
|
expect(menu.items[1].label).to.equal('2');
|
|
|
|
expect(menu.items[2].label).to.equal('3');
|
|
|
|
expect(menu.items[3].label).to.equal('inserted');
|
|
|
|
});
|
|
|
|
});
|
2017-10-25 02:40:31 +00:00
|
|
|
|
2018-01-27 17:38:55 +00:00
|
|
|
describe('Menu.popup', () => {
|
2020-03-20 20:28:31 +00:00
|
|
|
let w: BrowserWindow;
|
|
|
|
let menu: Menu;
|
2017-10-25 00:27:26 +00:00
|
|
|
|
|
|
|
beforeEach(() => {
|
2020-03-20 20:28:31 +00:00
|
|
|
w = new BrowserWindow({ show: false, width: 200, height: 200 });
|
2017-10-25 00:27:26 +00:00
|
|
|
menu = Menu.buildFromTemplate([
|
2018-09-13 16:10:51 +00:00
|
|
|
{ label: '1' },
|
|
|
|
{ label: '2' },
|
|
|
|
{ label: '3' }
|
2020-03-20 20:28:31 +00:00
|
|
|
]);
|
|
|
|
});
|
2017-02-16 18:58:12 +00:00
|
|
|
|
2019-07-26 14:17:14 +00:00
|
|
|
afterEach(async () => {
|
2020-03-20 20:28:31 +00:00
|
|
|
menu.closePopup();
|
|
|
|
menu.closePopup(w);
|
|
|
|
await closeWindow(w);
|
|
|
|
w = null as unknown as BrowserWindow;
|
|
|
|
});
|
2017-02-16 18:58:12 +00:00
|
|
|
|
2018-03-16 21:31:10 +00:00
|
|
|
it('throws an error if options is not an object', () => {
|
2018-06-30 07:25:28 +00:00
|
|
|
expect(() => {
|
2020-03-20 20:28:31 +00:00
|
|
|
menu.popup('this is a string, not an object' as any);
|
|
|
|
}).to.throw(/Options must be an object/);
|
|
|
|
});
|
2018-03-16 21:31:10 +00:00
|
|
|
|
2018-08-08 22:38:52 +00:00
|
|
|
it('allows for options to be optional', () => {
|
|
|
|
expect(() => {
|
2020-03-20 20:28:31 +00:00
|
|
|
menu.popup({});
|
|
|
|
}).to.not.throw();
|
|
|
|
});
|
2018-08-08 22:38:52 +00:00
|
|
|
|
2018-01-27 16:23:46 +00:00
|
|
|
it('should emit menu-will-show event', (done) => {
|
2020-03-20 20:28:31 +00:00
|
|
|
menu.on('menu-will-show', () => { done(); });
|
|
|
|
menu.popup({ window: w });
|
|
|
|
});
|
2018-01-27 16:23:46 +00:00
|
|
|
|
|
|
|
it('should emit menu-will-close event', (done) => {
|
2020-03-20 20:28:31 +00:00
|
|
|
menu.on('menu-will-close', () => { done(); });
|
|
|
|
menu.popup({ window: w });
|
2019-07-26 14:17:14 +00:00
|
|
|
// https://github.com/electron/electron/issues/19411
|
|
|
|
setTimeout(() => {
|
2020-03-20 20:28:31 +00:00
|
|
|
menu.closePopup();
|
|
|
|
});
|
|
|
|
});
|
2018-01-27 16:23:46 +00:00
|
|
|
|
2017-09-13 21:13:45 +00:00
|
|
|
it('returns immediately', () => {
|
2020-03-20 20:28:31 +00:00
|
|
|
const input = { window: w, x: 100, y: 101 };
|
|
|
|
const output = menu.popup(input) as unknown as {x: number, y: number, browserWindow: BrowserWindow};
|
|
|
|
expect(output.x).to.equal(input.x);
|
|
|
|
expect(output.y).to.equal(input.y);
|
|
|
|
expect(output.browserWindow).to.equal(input.window);
|
|
|
|
});
|
2017-12-08 22:52:21 +00:00
|
|
|
|
|
|
|
it('works without a given BrowserWindow and options', () => {
|
2020-03-20 20:28:31 +00:00
|
|
|
const { browserWindow, x, y } = menu.popup({ x: 100, y: 101 }) as unknown as {x: number, y: number, browserWindow: BrowserWindow};
|
2017-12-11 21:54:43 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(browserWindow.constructor.name).to.equal('BrowserWindow');
|
|
|
|
expect(x).to.equal(100);
|
|
|
|
expect(y).to.equal(101);
|
|
|
|
});
|
2017-12-08 22:52:21 +00:00
|
|
|
|
2018-02-20 01:59:47 +00:00
|
|
|
it('works with a given BrowserWindow, options and callback', (done) => {
|
2018-09-13 16:10:51 +00:00
|
|
|
const { x, y } = menu.popup({
|
2018-02-20 16:10:53 +00:00
|
|
|
window: w,
|
|
|
|
x: 100,
|
|
|
|
y: 101,
|
|
|
|
callback: () => done()
|
2020-03-20 20:28:31 +00:00
|
|
|
}) as unknown as {x: number, y: number};
|
2017-12-11 21:54:43 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(x).to.equal(100);
|
|
|
|
expect(y).to.equal(101);
|
2019-07-26 14:17:14 +00:00
|
|
|
// https://github.com/electron/electron/issues/19411
|
|
|
|
setTimeout(() => {
|
2020-03-20 20:28:31 +00:00
|
|
|
menu.closePopup();
|
|
|
|
});
|
|
|
|
});
|
2018-02-19 18:15:49 +00:00
|
|
|
|
|
|
|
it('works with a given BrowserWindow, no options, and a callback', (done) => {
|
2020-03-20 20:28:31 +00:00
|
|
|
menu.popup({ window: w, callback: () => done() });
|
2019-07-26 14:17:14 +00:00
|
|
|
// https://github.com/electron/electron/issues/19411
|
|
|
|
setTimeout(() => {
|
2020-03-20 20:28:31 +00:00
|
|
|
menu.closePopup();
|
|
|
|
});
|
|
|
|
});
|
2019-11-20 11:17:39 +00:00
|
|
|
|
2020-07-01 19:14:38 +00:00
|
|
|
it('prevents menu from getting garbage-collected when popuping', async () => {
|
2020-03-20 20:28:31 +00:00
|
|
|
const menu = Menu.buildFromTemplate([{ role: 'paste' }]);
|
|
|
|
menu.popup({ window: w });
|
2019-11-20 11:17:39 +00:00
|
|
|
|
|
|
|
// Keep a weak reference to the menu.
|
2020-06-15 20:56:51 +00:00
|
|
|
// eslint-disable-next-line no-undef
|
|
|
|
const wr = new (globalThis as any).WeakRef(menu);
|
2019-11-20 11:17:39 +00:00
|
|
|
|
2020-07-01 19:14:38 +00:00
|
|
|
await delay();
|
|
|
|
|
|
|
|
// Do garbage collection, since |menu| is not referenced in this closure
|
|
|
|
// it would be gone after next call.
|
|
|
|
const v8Util = process._linkedBinding('electron_common_v8_util');
|
|
|
|
v8Util.requestGarbageCollectionForTesting();
|
|
|
|
|
|
|
|
await delay();
|
|
|
|
|
|
|
|
// Try to receive menu from weak reference.
|
|
|
|
if (wr.deref()) {
|
|
|
|
wr.deref().closePopup();
|
|
|
|
} else {
|
|
|
|
throw new Error('Menu is garbage-collected while popuping');
|
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
});
|
|
|
|
});
|
2017-10-25 02:40:31 +00:00
|
|
|
|
2017-10-27 20:45:58 +00:00
|
|
|
describe('Menu.setApplicationMenu', () => {
|
2017-11-07 21:29:37 +00:00
|
|
|
it('sets a menu', () => {
|
|
|
|
const menu = Menu.buildFromTemplate([
|
2018-09-13 16:10:51 +00:00
|
|
|
{ label: '1' },
|
|
|
|
{ label: '2' }
|
2020-03-20 20:28:31 +00:00
|
|
|
]);
|
2018-02-23 13:53:59 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
Menu.setApplicationMenu(menu);
|
|
|
|
expect(Menu.getApplicationMenu()).to.not.be.null('application menu');
|
|
|
|
});
|
2017-11-07 21:29:37 +00:00
|
|
|
|
2020-03-03 21:35:05 +00:00
|
|
|
// TODO(nornagon): this causes the focus handling tests to fail
|
|
|
|
it.skip('unsets a menu with null', () => {
|
2020-03-20 20:28:31 +00:00
|
|
|
Menu.setApplicationMenu(null);
|
|
|
|
expect(Menu.getApplicationMenu()).to.be.null('application menu');
|
|
|
|
});
|
2019-12-09 19:17:36 +00:00
|
|
|
|
|
|
|
ifit(process.platform !== 'darwin')('does not override menu visibility on startup', async () => {
|
2020-03-20 20:28:31 +00:00
|
|
|
const appPath = path.join(fixturesPath, 'api', 'test-menu-visibility');
|
|
|
|
const appProcess = cp.spawn(process.execPath, [appPath]);
|
2019-12-09 19:17:36 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
let output = '';
|
2020-05-26 14:25:57 +00:00
|
|
|
await new Promise((resolve) => {
|
|
|
|
appProcess.stdout.on('data', data => {
|
|
|
|
output += data;
|
|
|
|
if (data.indexOf('Window has') > -1) {
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(output).to.include('Window has no menu');
|
|
|
|
});
|
2019-12-09 19:17:36 +00:00
|
|
|
|
|
|
|
ifit(process.platform !== 'darwin')('does not override null menu on startup', async () => {
|
2020-03-20 20:28:31 +00:00
|
|
|
const appPath = path.join(fixturesPath, 'api', 'test-menu-null');
|
|
|
|
const appProcess = cp.spawn(process.execPath, [appPath]);
|
2019-12-09 19:17:36 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
let output = '';
|
|
|
|
appProcess.stdout.on('data', data => { output += data; });
|
|
|
|
appProcess.stderr.on('data', data => { output += data; });
|
2019-12-09 19:17:36 +00:00
|
|
|
|
2020-03-20 20:28:31 +00:00
|
|
|
const [code] = await emittedOnce(appProcess, 'exit');
|
2020-02-06 16:15:55 +00:00
|
|
|
if (!output.includes('Window has no menu')) {
|
2020-03-20 20:28:31 +00:00
|
|
|
console.log(code, output);
|
2020-02-06 16:15:55 +00:00
|
|
|
}
|
2020-03-20 20:28:31 +00:00
|
|
|
expect(output).to.include('Window has no menu');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|