api-menu-spec.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. const assert = require('assert')
  2. const {ipcRenderer, remote} = require('electron')
  3. const {Menu, MenuItem} = remote
  4. describe('menu module', function () {
  5. describe('Menu.buildFromTemplate', function () {
  6. it('should be able to attach extra fields', function () {
  7. var menu = Menu.buildFromTemplate([
  8. {
  9. label: 'text',
  10. extra: 'field'
  11. }
  12. ])
  13. assert.equal(menu.items[0].extra, 'field')
  14. })
  15. it('does not modify the specified template', function () {
  16. var template = ipcRenderer.sendSync('eval', "var template = [{label: 'text', submenu: [{label: 'sub'}]}];\nrequire('electron').Menu.buildFromTemplate(template);\ntemplate;")
  17. assert.deepStrictEqual(template, [
  18. {
  19. label: 'text',
  20. submenu: [
  21. {
  22. label: 'sub'
  23. }
  24. ]
  25. }
  26. ])
  27. })
  28. it('does not throw exceptions for undefined/null values', function () {
  29. assert.doesNotThrow(function () {
  30. Menu.buildFromTemplate([
  31. {
  32. label: 'text',
  33. accelerator: undefined
  34. },
  35. {
  36. label: 'text again',
  37. accelerator: null
  38. }
  39. ])
  40. })
  41. })
  42. describe('Menu.buildFromTemplate should reorder based on item position specifiers', function () {
  43. it('should position before existing item', function () {
  44. var menu = Menu.buildFromTemplate([
  45. {
  46. label: '2',
  47. id: '2'
  48. }, {
  49. label: '3',
  50. id: '3'
  51. }, {
  52. label: '1',
  53. id: '1',
  54. position: 'before=2'
  55. }
  56. ])
  57. assert.equal(menu.items[0].label, '1')
  58. assert.equal(menu.items[1].label, '2')
  59. assert.equal(menu.items[2].label, '3')
  60. })
  61. it('should position after existing item', function () {
  62. var menu = Menu.buildFromTemplate([
  63. {
  64. label: '1',
  65. id: '1'
  66. }, {
  67. label: '3',
  68. id: '3'
  69. }, {
  70. label: '2',
  71. id: '2',
  72. position: 'after=1'
  73. }
  74. ])
  75. assert.equal(menu.items[0].label, '1')
  76. assert.equal(menu.items[1].label, '2')
  77. assert.equal(menu.items[2].label, '3')
  78. })
  79. it('should position at endof existing separator groups', function () {
  80. var menu = Menu.buildFromTemplate([
  81. {
  82. type: 'separator',
  83. id: 'numbers'
  84. }, {
  85. type: 'separator',
  86. id: 'letters'
  87. }, {
  88. label: 'a',
  89. id: 'a',
  90. position: 'endof=letters'
  91. }, {
  92. label: '1',
  93. id: '1',
  94. position: 'endof=numbers'
  95. }, {
  96. label: 'b',
  97. id: 'b',
  98. position: 'endof=letters'
  99. }, {
  100. label: '2',
  101. id: '2',
  102. position: 'endof=numbers'
  103. }, {
  104. label: 'c',
  105. id: 'c',
  106. position: 'endof=letters'
  107. }, {
  108. label: '3',
  109. id: '3',
  110. position: 'endof=numbers'
  111. }
  112. ])
  113. assert.equal(menu.items[0].id, 'numbers')
  114. assert.equal(menu.items[1].label, '1')
  115. assert.equal(menu.items[2].label, '2')
  116. assert.equal(menu.items[3].label, '3')
  117. assert.equal(menu.items[4].id, 'letters')
  118. assert.equal(menu.items[5].label, 'a')
  119. assert.equal(menu.items[6].label, 'b')
  120. assert.equal(menu.items[7].label, 'c')
  121. })
  122. it('should create separator group if endof does not reference existing separator group', function () {
  123. var menu = Menu.buildFromTemplate([
  124. {
  125. label: 'a',
  126. id: 'a',
  127. position: 'endof=letters'
  128. }, {
  129. label: '1',
  130. id: '1',
  131. position: 'endof=numbers'
  132. }, {
  133. label: 'b',
  134. id: 'b',
  135. position: 'endof=letters'
  136. }, {
  137. label: '2',
  138. id: '2',
  139. position: 'endof=numbers'
  140. }, {
  141. label: 'c',
  142. id: 'c',
  143. position: 'endof=letters'
  144. }, {
  145. label: '3',
  146. id: '3',
  147. position: 'endof=numbers'
  148. }
  149. ])
  150. assert.equal(menu.items[0].id, 'letters')
  151. assert.equal(menu.items[1].label, 'a')
  152. assert.equal(menu.items[2].label, 'b')
  153. assert.equal(menu.items[3].label, 'c')
  154. assert.equal(menu.items[4].id, 'numbers')
  155. assert.equal(menu.items[5].label, '1')
  156. assert.equal(menu.items[6].label, '2')
  157. assert.equal(menu.items[7].label, '3')
  158. })
  159. it('should continue inserting items at next index when no specifier is present', function () {
  160. var menu = Menu.buildFromTemplate([
  161. {
  162. label: '4',
  163. id: '4'
  164. }, {
  165. label: '5',
  166. id: '5'
  167. }, {
  168. label: '1',
  169. id: '1',
  170. position: 'before=4'
  171. }, {
  172. label: '2',
  173. id: '2'
  174. }, {
  175. label: '3',
  176. id: '3'
  177. }
  178. ])
  179. assert.equal(menu.items[0].label, '1')
  180. assert.equal(menu.items[1].label, '2')
  181. assert.equal(menu.items[2].label, '3')
  182. assert.equal(menu.items[3].label, '4')
  183. assert.equal(menu.items[4].label, '5')
  184. })
  185. })
  186. })
  187. describe('Menu.insert', function () {
  188. it('should store item in @items by its index', function () {
  189. var menu = Menu.buildFromTemplate([
  190. {
  191. label: '1'
  192. }, {
  193. label: '2'
  194. }, {
  195. label: '3'
  196. }
  197. ])
  198. var item = new MenuItem({
  199. label: 'inserted'
  200. })
  201. menu.insert(1, item)
  202. assert.equal(menu.items[0].label, '1')
  203. assert.equal(menu.items[1].label, 'inserted')
  204. assert.equal(menu.items[2].label, '2')
  205. assert.equal(menu.items[3].label, '3')
  206. })
  207. })
  208. describe('MenuItem.click', function () {
  209. it('should be called with the item object passed', function (done) {
  210. var menu = Menu.buildFromTemplate([
  211. {
  212. label: 'text',
  213. click: function (item) {
  214. assert.equal(item.constructor.name, 'MenuItem')
  215. assert.equal(item.label, 'text')
  216. done()
  217. }
  218. }
  219. ])
  220. menu.delegate.executeCommand({}, menu.items[0].commandId)
  221. })
  222. })
  223. describe('MenuItem with checked property', function () {
  224. it('clicking an checkbox item should flip the checked property', function () {
  225. var menu = Menu.buildFromTemplate([
  226. {
  227. label: 'text',
  228. type: 'checkbox'
  229. }
  230. ])
  231. assert.equal(menu.items[0].checked, false)
  232. menu.delegate.executeCommand({}, menu.items[0].commandId)
  233. assert.equal(menu.items[0].checked, true)
  234. })
  235. it('clicking an radio item should always make checked property true', function () {
  236. var menu = Menu.buildFromTemplate([
  237. {
  238. label: 'text',
  239. type: 'radio'
  240. }
  241. ])
  242. menu.delegate.executeCommand({}, menu.items[0].commandId)
  243. assert.equal(menu.items[0].checked, true)
  244. menu.delegate.executeCommand({}, menu.items[0].commandId)
  245. assert.equal(menu.items[0].checked, true)
  246. })
  247. it('at least have one item checked in each group', function () {
  248. var i, j, k, menu, template
  249. template = []
  250. for (i = j = 0; j <= 10; i = ++j) {
  251. template.push({
  252. label: '' + i,
  253. type: 'radio'
  254. })
  255. }
  256. template.push({
  257. type: 'separator'
  258. })
  259. for (i = k = 12; k <= 20; i = ++k) {
  260. template.push({
  261. label: '' + i,
  262. type: 'radio'
  263. })
  264. }
  265. menu = Menu.buildFromTemplate(template)
  266. menu.delegate.menuWillShow()
  267. assert.equal(menu.items[0].checked, true)
  268. assert.equal(menu.items[12].checked, true)
  269. })
  270. it('should assign groupId automatically', function () {
  271. var groupId, i, j, k, l, m, menu, template
  272. template = []
  273. for (i = j = 0; j <= 10; i = ++j) {
  274. template.push({
  275. label: '' + i,
  276. type: 'radio'
  277. })
  278. }
  279. template.push({
  280. type: 'separator'
  281. })
  282. for (i = k = 12; k <= 20; i = ++k) {
  283. template.push({
  284. label: '' + i,
  285. type: 'radio'
  286. })
  287. }
  288. menu = Menu.buildFromTemplate(template)
  289. groupId = menu.items[0].groupId
  290. for (i = l = 0; l <= 10; i = ++l) {
  291. assert.equal(menu.items[i].groupId, groupId)
  292. }
  293. for (i = m = 12; m <= 20; i = ++m) {
  294. assert.equal(menu.items[i].groupId, groupId + 1)
  295. }
  296. })
  297. it("setting 'checked' should flip other items' 'checked' property", function () {
  298. var i, j, k, l, m, menu, n, o, p, q, template
  299. template = []
  300. for (i = j = 0; j <= 10; i = ++j) {
  301. template.push({
  302. label: '' + i,
  303. type: 'radio'
  304. })
  305. }
  306. template.push({
  307. type: 'separator'
  308. })
  309. for (i = k = 12; k <= 20; i = ++k) {
  310. template.push({
  311. label: '' + i,
  312. type: 'radio'
  313. })
  314. }
  315. menu = Menu.buildFromTemplate(template)
  316. for (i = l = 0; l <= 10; i = ++l) {
  317. assert.equal(menu.items[i].checked, false)
  318. }
  319. menu.items[0].checked = true
  320. assert.equal(menu.items[0].checked, true)
  321. for (i = m = 1; m <= 10; i = ++m) {
  322. assert.equal(menu.items[i].checked, false)
  323. }
  324. menu.items[10].checked = true
  325. assert.equal(menu.items[10].checked, true)
  326. for (i = n = 0; n <= 9; i = ++n) {
  327. assert.equal(menu.items[i].checked, false)
  328. }
  329. for (i = o = 12; o <= 20; i = ++o) {
  330. assert.equal(menu.items[i].checked, false)
  331. }
  332. menu.items[12].checked = true
  333. assert.equal(menu.items[10].checked, true)
  334. for (i = p = 0; p <= 9; i = ++p) {
  335. assert.equal(menu.items[i].checked, false)
  336. }
  337. assert.equal(menu.items[12].checked, true)
  338. for (i = q = 13; q <= 20; i = ++q) {
  339. assert.equal(menu.items[i].checked, false)
  340. }
  341. })
  342. })
  343. describe('MenuItem command id', function () {
  344. it('cannot be overwritten', function () {
  345. var item = new MenuItem({
  346. label: 'item'
  347. })
  348. var commandId = item.commandId
  349. assert(commandId != null)
  350. item.commandId = '' + commandId + '-modified'
  351. assert.equal(item.commandId, commandId)
  352. })
  353. })
  354. describe('MenuItem with invalid type', function () {
  355. it('throws an exception', function () {
  356. assert.throws(function () {
  357. Menu.buildFromTemplate([
  358. {
  359. label: 'text',
  360. type: 'not-a-type'
  361. }
  362. ])
  363. }, /Unknown menu item type: not-a-type/)
  364. })
  365. })
  366. describe('MenuItem with submenu type and missing submenu', function () {
  367. it('throws an exception', function () {
  368. assert.throws(function () {
  369. Menu.buildFromTemplate([
  370. {
  371. label: 'text',
  372. type: 'submenu'
  373. }
  374. ])
  375. }, /Invalid submenu/)
  376. })
  377. })
  378. describe('MenuItem role', function () {
  379. it('includes a default label and accelerator', function () {
  380. var item = new MenuItem({role: 'close'})
  381. assert.equal(item.label, process.platform === 'darwin' ? 'Close Window' : 'Close')
  382. assert.equal(item.accelerator, undefined)
  383. assert.equal(item.getDefaultRoleAccelerator(), 'CommandOrControl+W')
  384. item = new MenuItem({role: 'close', label: 'Other', accelerator: 'D'})
  385. assert.equal(item.label, 'Other')
  386. assert.equal(item.accelerator, 'D')
  387. assert.equal(item.getDefaultRoleAccelerator(), 'CommandOrControl+W')
  388. item = new MenuItem({role: 'help'})
  389. assert.equal(item.label, 'Help')
  390. assert.equal(item.accelerator, undefined)
  391. assert.equal(item.getDefaultRoleAccelerator(), undefined)
  392. item = new MenuItem({role: 'hide'})
  393. assert.equal(item.label, 'Hide Electron Test')
  394. assert.equal(item.accelerator, undefined)
  395. assert.equal(item.getDefaultRoleAccelerator(), 'Command+H')
  396. item = new MenuItem({role: 'undo'})
  397. assert.equal(item.label, 'Undo')
  398. assert.equal(item.accelerator, undefined)
  399. assert.equal(item.getDefaultRoleAccelerator(), 'CommandOrControl+Z')
  400. item = new MenuItem({role: 'redo'})
  401. assert.equal(item.label, 'Redo')
  402. assert.equal(item.accelerator, undefined)
  403. assert.equal(item.getDefaultRoleAccelerator(), process.platform === 'win32' ? 'Control+Y' : 'Shift+CommandOrControl+Z')
  404. })
  405. })
  406. })
  407. describe('MenuItem with custom properties in constructor', function () {
  408. it('preserves the custom properties', function () {
  409. var template = [{
  410. label: 'menu 1',
  411. customProp: 'foo',
  412. submenu: []
  413. }]
  414. var menu = Menu.buildFromTemplate(template)
  415. menu.items[0].submenu.append(new MenuItem({
  416. label: 'item 1',
  417. customProp: 'bar',
  418. overrideProperty: 'oops not allowed'
  419. }))
  420. assert.equal(menu.items[0].customProp, 'foo')
  421. assert.equal(menu.items[0].submenu.items[0].label, 'item 1')
  422. assert.equal(menu.items[0].submenu.items[0].customProp, 'bar')
  423. assert.equal(typeof menu.items[0].submenu.items[0].overrideProperty, 'function')
  424. })
  425. })