electron/spec-main/api-ipc-spec.ts

199 lines
6.8 KiB
TypeScript
Raw Normal View History

import { expect } from 'chai'
import { BrowserWindow, ipcMain, IpcMainInvokeEvent } from 'electron'
import { emittedOnce } from './events-helpers'
const v8Util = process.electronBinding('v8_util')
describe('ipc module', () => {
describe('invoke', () => {
2019-11-01 20:37:02 +00:00
let w = (null as unknown as BrowserWindow)
before(async () => {
w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
await w.loadURL('about:blank')
})
after(async () => {
w.destroy()
})
2019-11-01 20:37:02 +00:00
async function rendererInvoke (...args: any[]) {
const { ipcRenderer } = require('electron')
try {
const result = await ipcRenderer.invoke('test', ...args)
2019-11-01 20:37:02 +00:00
ipcRenderer.send('result', { result })
} catch (e) {
2019-11-01 20:37:02 +00:00
ipcRenderer.send('result', { error: e.message })
}
}
it('receives a response from a synchronous handler', async () => {
ipcMain.handleOnce('test', (e: IpcMainInvokeEvent, arg: number) => {
expect(arg).to.equal(123)
return 3
})
const done = new Promise(resolve => ipcMain.once('result', (e, arg) => {
2019-11-01 20:37:02 +00:00
expect(arg).to.deep.equal({ result: 3 })
resolve()
}))
await w.webContents.executeJavaScript(`(${rendererInvoke})(123)`)
await done
})
it('receives a response from an asynchronous handler', async () => {
ipcMain.handleOnce('test', async (e: IpcMainInvokeEvent, arg: number) => {
expect(arg).to.equal(123)
await new Promise(resolve => setImmediate(resolve))
return 3
})
const done = new Promise(resolve => ipcMain.once('result', (e, arg) => {
2019-11-01 20:37:02 +00:00
expect(arg).to.deep.equal({ result: 3 })
resolve()
}))
await w.webContents.executeJavaScript(`(${rendererInvoke})(123)`)
await done
})
it('receives an error from a synchronous handler', async () => {
ipcMain.handleOnce('test', () => {
throw new Error('some error')
})
const done = new Promise(resolve => ipcMain.once('result', (e, arg) => {
expect(arg.error).to.match(/some error/)
resolve()
}))
await w.webContents.executeJavaScript(`(${rendererInvoke})()`)
await done
})
it('receives an error from an asynchronous handler', async () => {
ipcMain.handleOnce('test', async () => {
await new Promise(resolve => setImmediate(resolve))
throw new Error('some error')
})
const done = new Promise(resolve => ipcMain.once('result', (e, arg) => {
expect(arg.error).to.match(/some error/)
resolve()
}))
await w.webContents.executeJavaScript(`(${rendererInvoke})()`)
await done
})
it('throws an error if no handler is registered', async () => {
const done = new Promise(resolve => ipcMain.once('result', (e, arg) => {
expect(arg.error).to.match(/No handler registered/)
resolve()
}))
await w.webContents.executeJavaScript(`(${rendererInvoke})()`)
await done
})
it('throws an error when invoking a handler that was removed', async () => {
ipcMain.handle('test', () => {})
ipcMain.removeHandler('test')
const done = new Promise(resolve => ipcMain.once('result', (e, arg) => {
expect(arg.error).to.match(/No handler registered/)
resolve()
}))
await w.webContents.executeJavaScript(`(${rendererInvoke})()`)
await done
})
it('forbids multiple handlers', async () => {
ipcMain.handle('test', () => {})
try {
expect(() => { ipcMain.handle('test', () => {}) }).to.throw(/second handler/)
} finally {
ipcMain.removeHandler('test')
}
})
it('throws an error in the renderer if the reply callback is dropped', async () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
ipcMain.handleOnce('test', () => new Promise(resolve => {
setTimeout(() => v8Util.requestGarbageCollectionForTesting())
/* never resolve */
}))
w.webContents.executeJavaScript(`(${rendererInvoke})()`)
const [, { error }] = await emittedOnce(ipcMain, 'result')
expect(error).to.match(/reply was never sent/)
})
})
describe('ordering', () => {
2019-11-01 20:37:02 +00:00
let w = (null as unknown as BrowserWindow)
before(async () => {
w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
await w.loadURL('about:blank')
})
after(async () => {
w.destroy()
})
it('between send and sendSync is consistent', async () => {
const received: number[] = []
ipcMain.on('test-async', (e, i) => { received.push(i) })
ipcMain.on('test-sync', (e, i) => { received.push(i); e.returnValue = null })
const done = new Promise(resolve => ipcMain.once('done', () => { resolve() }))
2019-11-01 20:37:02 +00:00
function rendererStressTest () {
const { ipcRenderer } = require('electron')
for (let i = 0; i < 1000; i++) {
switch ((Math.random() * 2) | 0) {
case 0:
ipcRenderer.send('test-async', i)
break
case 1:
ipcRenderer.sendSync('test-sync', i)
break
}
}
2019-11-01 20:37:02 +00:00
ipcRenderer.send('done')
}
try {
w.webContents.executeJavaScript(`(${rendererStressTest})()`)
await done
} finally {
ipcMain.removeAllListeners('test-async')
ipcMain.removeAllListeners('test-sync')
}
expect(received).to.have.lengthOf(1000)
expect(received).to.deep.equal([...received].sort((a, b) => a - b))
})
it('between send, sendSync, and invoke is consistent', async () => {
const received: number[] = []
ipcMain.handle('test-invoke', (e, i) => { received.push(i) })
ipcMain.on('test-async', (e, i) => { received.push(i) })
ipcMain.on('test-sync', (e, i) => { received.push(i); e.returnValue = null })
const done = new Promise(resolve => ipcMain.once('done', () => { resolve() }))
2019-11-01 20:37:02 +00:00
function rendererStressTest () {
const { ipcRenderer } = require('electron')
for (let i = 0; i < 1000; i++) {
switch ((Math.random() * 3) | 0) {
case 0:
ipcRenderer.send('test-async', i)
break
case 1:
ipcRenderer.sendSync('test-sync', i)
break
case 2:
ipcRenderer.invoke('test-invoke', i)
break
}
}
2019-11-01 20:37:02 +00:00
ipcRenderer.send('done')
}
try {
w.webContents.executeJavaScript(`(${rendererStressTest})()`)
await done
} finally {
ipcMain.removeHandler('test-invoke')
ipcMain.removeAllListeners('test-async')
ipcMain.removeAllListeners('test-sync')
}
expect(received).to.have.lengthOf(1000)
expect(received).to.deep.equal([...received].sort((a, b) => a - b))
})
})
})