Browse Source

feat: allow Menu.buildFromTemplate() to accept MenuItems (#16697)

* feat: allow Menu.buildFromTemplate to accept MenuItems

* add another spec

* fix linter error

* add submenu spec
Shelley Vohr 6 years ago
parent
commit
858781ba83
3 changed files with 88 additions and 5 deletions
  1. 2 3
      docs/api/menu.md
  2. 12 2
      lib/browser/api/menu.js
  3. 74 0
      spec/api-menu-spec.js

+ 2 - 3
docs/api/menu.md

@@ -52,15 +52,14 @@ for more information on macOS' native actions.
 
 #### `Menu.buildFromTemplate(template)`
 
-* `template` MenuItemConstructorOptions[]
+* `template` (MenuItemConstructorOptions | MenuItem)[]
 
 Returns `Menu`
 
 Generally, the `template` is an array of `options` for constructing a
 [MenuItem](menu-item.md). The usage can be referenced above.
 
-You can also attach other fields to the element of the `template` and they
-will become properties of the constructed menu items.
+You can also attach other fields to the element of the `template` and they will become properties of the constructed menu items.
 
 ### Instance Methods
 

+ 12 - 2
lib/browser/api/menu.js

@@ -168,7 +168,13 @@ Menu.buildFromTemplate = function (template) {
   const sorted = sortTemplate(filtered)
 
   const menu = new Menu()
-  sorted.forEach((item) => menu.append(new MenuItem(item)))
+  sorted.forEach(item => {
+    if (item instanceof MenuItem) {
+      menu.append(item)
+    } else {
+      menu.append(new MenuItem(item))
+    }
+  })
 
   return menu
 }
@@ -178,7 +184,11 @@ Menu.buildFromTemplate = function (template) {
 // validate the template against having the wrong attribute
 function areValidTemplateItems (template) {
   return template.every(item =>
-    item != null && typeof item === 'object' && (item.hasOwnProperty('label') || item.hasOwnProperty('role') || item.type === 'separator'))
+    item != null &&
+    typeof item === 'object' &&
+    (item.hasOwnProperty('label') ||
+     item.hasOwnProperty('role') ||
+     item.type === 'separator'))
 }
 
 function sortTemplate (template) {

+ 74 - 0
spec/api-menu-spec.js

@@ -21,6 +21,37 @@ describe('Menu module', () => {
       expect(menu.items[0].extra).to.equal('field')
     })
 
+    it('should be able to accept only MenuItems', () => {
+      const menu = Menu.buildFromTemplate([
+        new MenuItem({ label: 'one' }),
+        new MenuItem({ label: 'two' })
+      ])
+      expect(menu.items[0].label).to.equal('one')
+      expect(menu.items[1].label).to.equal('two')
+    })
+
+    it('should be able to accept only MenuItems in a submenu', () => {
+      const menu = Menu.buildFromTemplate([
+        {
+          label: 'one',
+          submenu: [
+            new MenuItem({ label: 'two' })
+          ]
+        }
+      ])
+      expect(menu.items[0].label).to.equal('one')
+      expect(menu.items[0].submenu.items[0].label).to.equal('two')
+    })
+
+    it('should be able to accept MenuItems and plain objects', () => {
+      const menu = Menu.buildFromTemplate([
+        new MenuItem({ label: 'one' }),
+        { label: 'two' }
+      ])
+      expect(menu.items[0].label).to.equal('one')
+      expect(menu.items[1].label).to.equal('two')
+    })
+
     it('does not modify the specified template', () => {
       const template = [{ label: 'text', submenu: [{ label: 'sub' }] }]
       const result = ipcRenderer.sendSync('eval', `const template = [{label: 'text', submenu: [{label: 'sub'}]}]\nrequire('electron').Menu.buildFromTemplate(template)\ntemplate`)
@@ -90,6 +121,21 @@ describe('Menu module', () => {
           expect(sortMenuItems(items)).to.deep.equal(expected)
         })
 
+        it('does a simple sort with MenuItems', () => {
+          const firstItem = new MenuItem({ id: '1', label: 'one' })
+          const secondItem = new MenuItem({
+            label: 'two',
+            id: '2',
+            afterGroupContaining: ['1']
+          })
+          const sep = new MenuItem({ type: 'separator' })
+
+          const items = [ secondItem, sep, firstItem ]
+          const expected = [ firstItem, sep, secondItem ]
+
+          expect(sortMenuItems(items)).to.deep.equal(expected)
+        })
+
         it('resolves cycles by ignoring things that conflict', () => {
           const items = [
             {
@@ -571,6 +617,34 @@ describe('Menu module', () => {
         expect(menu.items[3].label).to.equal('four')
         expect(menu.items[4].label).to.equal('five')
       })
+
+      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']
+          })
+        ])
+
+        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')
+      })
     })
   })