api-ipc-spec.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import * as chai from 'chai'
  2. import * as chaiAsPromised from 'chai-as-promised'
  3. import { BrowserWindow, ipcMain, IpcMainInvokeEvent } from 'electron'
  4. const { expect } = chai
  5. chai.use(chaiAsPromised)
  6. describe('ipc module', () => {
  7. describe('invoke', () => {
  8. let w = (null as unknown as BrowserWindow);
  9. before(async () => {
  10. w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
  11. await w.loadURL('about:blank')
  12. })
  13. after(async () => {
  14. w.destroy()
  15. })
  16. async function rendererInvoke(...args: any[]) {
  17. const {ipcRenderer} = require('electron')
  18. try {
  19. const result = await ipcRenderer.invoke('test', ...args)
  20. ipcRenderer.send('result', {result})
  21. } catch (e) {
  22. ipcRenderer.send('result', {error: e.message})
  23. }
  24. }
  25. it('receives a response from a synchronous handler', async () => {
  26. ipcMain.handleOnce('test', (e: IpcMainInvokeEvent, arg: number) => {
  27. expect(arg).to.equal(123)
  28. return 3
  29. })
  30. const done = new Promise(resolve => ipcMain.once('result', (e, arg) => {
  31. expect(arg).to.deep.equal({result: 3})
  32. resolve()
  33. }))
  34. await w.webContents.executeJavaScript(`(${rendererInvoke})(123)`)
  35. await done
  36. })
  37. it('receives a response from an asynchronous handler', async () => {
  38. ipcMain.handleOnce('test', async (e: IpcMainInvokeEvent, arg: number) => {
  39. expect(arg).to.equal(123)
  40. await new Promise(setImmediate)
  41. return 3
  42. })
  43. const done = new Promise(resolve => ipcMain.once('result', (e, arg) => {
  44. expect(arg).to.deep.equal({result: 3})
  45. resolve()
  46. }))
  47. await w.webContents.executeJavaScript(`(${rendererInvoke})(123)`)
  48. await done
  49. })
  50. it('receives an error from a synchronous handler', async () => {
  51. ipcMain.handleOnce('test', () => {
  52. throw new Error('some error')
  53. })
  54. const done = new Promise(resolve => ipcMain.once('result', (e, arg) => {
  55. expect(arg.error).to.match(/some error/)
  56. resolve()
  57. }))
  58. await w.webContents.executeJavaScript(`(${rendererInvoke})()`)
  59. await done
  60. })
  61. it('receives an error from an asynchronous handler', async () => {
  62. ipcMain.handleOnce('test', async () => {
  63. await new Promise(setImmediate)
  64. throw new Error('some error')
  65. })
  66. const done = new Promise(resolve => ipcMain.once('result', (e, arg) => {
  67. expect(arg.error).to.match(/some error/)
  68. resolve()
  69. }))
  70. await w.webContents.executeJavaScript(`(${rendererInvoke})()`)
  71. await done
  72. })
  73. it('throws an error if no handler is registered', async () => {
  74. const done = new Promise(resolve => ipcMain.once('result', (e, arg) => {
  75. expect(arg.error).to.match(/No handler registered/)
  76. resolve()
  77. }))
  78. await w.webContents.executeJavaScript(`(${rendererInvoke})()`)
  79. await done
  80. })
  81. it('throws an error when invoking a handler that was removed', async () => {
  82. ipcMain.handle('test', () => {})
  83. ipcMain.removeHandler('test')
  84. const done = new Promise(resolve => ipcMain.once('result', (e, arg) => {
  85. expect(arg.error).to.match(/No handler registered/)
  86. resolve()
  87. }))
  88. await w.webContents.executeJavaScript(`(${rendererInvoke})()`)
  89. await done
  90. })
  91. it('forbids multiple handlers', async () => {
  92. ipcMain.handle('test', () => {})
  93. try {
  94. expect(() => { ipcMain.handle('test', () => {}) }).to.throw(/second handler/)
  95. } finally {
  96. ipcMain.removeHandler('test')
  97. }
  98. })
  99. })
  100. describe('ordering', () => {
  101. let w = (null as unknown as BrowserWindow);
  102. before(async () => {
  103. w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
  104. await w.loadURL('about:blank')
  105. })
  106. after(async () => {
  107. w.destroy()
  108. })
  109. it('between send and sendSync is consistent', async () => {
  110. const received: number[] = []
  111. ipcMain.on('test-async', (e, i) => { received.push(i) })
  112. ipcMain.on('test-sync', (e, i) => { received.push(i); e.returnValue = null })
  113. const done = new Promise(resolve => ipcMain.once('done', () => { resolve() }))
  114. try {
  115. function rendererStressTest() {
  116. const {ipcRenderer} = require('electron')
  117. for (let i = 0; i < 1000; i++) {
  118. switch ((Math.random() * 2) | 0) {
  119. case 0:
  120. ipcRenderer.send('test-async', i)
  121. break;
  122. case 1:
  123. ipcRenderer.sendSync('test-sync', i)
  124. break;
  125. }
  126. }
  127. ipcRenderer.send('done')
  128. }
  129. w.webContents.executeJavaScript(`(${rendererStressTest})()`)
  130. await done
  131. } finally {
  132. ipcMain.removeAllListeners('test-async')
  133. ipcMain.removeAllListeners('test-sync')
  134. }
  135. expect(received).to.have.lengthOf(1000)
  136. expect(received).to.deep.equal([...received].sort((a, b) => a - b))
  137. })
  138. it('between send, sendSync, and invoke is consistent', async () => {
  139. const received: number[] = []
  140. ipcMain.handle('test-invoke', (e, i) => { received.push(i) })
  141. ipcMain.on('test-async', (e, i) => { received.push(i) })
  142. ipcMain.on('test-sync', (e, i) => { received.push(i); e.returnValue = null })
  143. const done = new Promise(resolve => ipcMain.once('done', () => { resolve() }))
  144. try {
  145. function rendererStressTest() {
  146. const {ipcRenderer} = require('electron')
  147. for (let i = 0; i < 1000; i++) {
  148. switch ((Math.random() * 3) | 0) {
  149. case 0:
  150. ipcRenderer.send('test-async', i)
  151. break;
  152. case 1:
  153. ipcRenderer.sendSync('test-sync', i)
  154. break;
  155. case 2:
  156. ipcRenderer.invoke('test-invoke', i)
  157. break;
  158. }
  159. }
  160. ipcRenderer.send('done')
  161. }
  162. w.webContents.executeJavaScript(`(${rendererStressTest})()`)
  163. await done
  164. } finally {
  165. ipcMain.removeHandler('test-invoke')
  166. ipcMain.removeAllListeners('test-async')
  167. ipcMain.removeAllListeners('test-sync')
  168. }
  169. expect(received).to.have.lengthOf(1000)
  170. expect(received).to.deep.equal([...received].sort((a, b) => a - b))
  171. })
  172. })
  173. })