api-app-spec.ts 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460
  1. import * as chai from 'chai'
  2. import * as chaiAsPromised from 'chai-as-promised'
  3. import * as cp from 'child_process'
  4. import * as https from 'https'
  5. import * as http from 'http'
  6. import * as net from 'net'
  7. import * as fs from 'fs'
  8. import * as path from 'path'
  9. import { homedir } from 'os'
  10. import split = require('split')
  11. import { app, BrowserWindow, Menu } from 'electron'
  12. import { emittedOnce } from './events-helpers'
  13. import { closeWindow, closeAllWindows } from './window-helpers'
  14. import { ifdescribe, ifit } from './spec-helpers'
  15. const features = process.electronBinding('features')
  16. const { expect } = chai
  17. chai.use(chaiAsPromised)
  18. const fixturesPath = path.resolve(__dirname, '../spec/fixtures')
  19. describe('electron module', () => {
  20. it('does not expose internal modules to require', () => {
  21. expect(() => {
  22. require('clipboard')
  23. }).to.throw(/Cannot find module 'clipboard'/)
  24. })
  25. describe('require("electron")', () => {
  26. it('always returns the internal electron module', () => {
  27. require('electron')
  28. })
  29. })
  30. })
  31. describe('app module', () => {
  32. let server: https.Server
  33. let secureUrl: string
  34. const certPath = path.join(fixturesPath, 'certificates')
  35. before((done) => {
  36. const options = {
  37. key: fs.readFileSync(path.join(certPath, 'server.key')),
  38. cert: fs.readFileSync(path.join(certPath, 'server.pem')),
  39. ca: [
  40. fs.readFileSync(path.join(certPath, 'rootCA.pem')),
  41. fs.readFileSync(path.join(certPath, 'intermediateCA.pem'))
  42. ],
  43. requestCert: true,
  44. rejectUnauthorized: false
  45. }
  46. server = https.createServer(options, (req, res) => {
  47. if ((req as any).client.authorized) {
  48. res.writeHead(200)
  49. res.end('<title>authorized</title>')
  50. } else {
  51. res.writeHead(401)
  52. res.end('<title>denied</title>')
  53. }
  54. })
  55. server.listen(0, '127.0.0.1', () => {
  56. const port = (server.address() as net.AddressInfo).port
  57. secureUrl = `https://127.0.0.1:${port}`
  58. done()
  59. })
  60. })
  61. after(done => {
  62. server.close(() => done())
  63. })
  64. describe('app.getVersion()', () => {
  65. it('returns the version field of package.json', () => {
  66. expect(app.getVersion()).to.equal('0.1.0')
  67. })
  68. })
  69. describe('app.setVersion(version)', () => {
  70. it('overrides the version', () => {
  71. expect(app.getVersion()).to.equal('0.1.0')
  72. app.setVersion('test-version')
  73. expect(app.getVersion()).to.equal('test-version')
  74. app.setVersion('0.1.0')
  75. })
  76. })
  77. describe('app.name', () => {
  78. it('returns the name field of package.json', () => {
  79. expect(app.name).to.equal('Electron Test Main')
  80. })
  81. it('overrides the name', () => {
  82. expect(app.name).to.equal('Electron Test Main')
  83. app.name = 'test-name'
  84. expect(app.name).to.equal('test-name')
  85. app.name = 'Electron Test Main'
  86. })
  87. })
  88. // TODO(codebytere): remove when propertyification is complete
  89. describe('app.getName()', () => {
  90. it('returns the name field of package.json', () => {
  91. expect(app.getName()).to.equal('Electron Test Main')
  92. })
  93. })
  94. // TODO(codebytere): remove when propertyification is complete
  95. describe('app.setName(name)', () => {
  96. it('overrides the name', () => {
  97. expect(app.getName()).to.equal('Electron Test Main')
  98. app.setName('test-name')
  99. expect(app.getName()).to.equal('test-name')
  100. app.setName('Electron Test Main')
  101. })
  102. })
  103. describe('app.getLocale()', () => {
  104. it('should not be empty', () => {
  105. expect(app.getLocale()).to.not.equal('')
  106. })
  107. })
  108. describe('app.getLocaleCountryCode()', () => {
  109. it('should be empty or have length of two', () => {
  110. let expectedLength = 2
  111. if (isCI && process.platform === 'linux') {
  112. // Linux CI machines have no locale.
  113. expectedLength = 0
  114. }
  115. expect(app.getLocaleCountryCode()).to.be.a('string').and.have.lengthOf(expectedLength)
  116. })
  117. })
  118. describe('app.isPackaged', () => {
  119. it('should be false durings tests', () => {
  120. expect(app.isPackaged).to.equal(false)
  121. })
  122. })
  123. describe('app.isInApplicationsFolder()', () => {
  124. before(function () {
  125. if (process.platform !== 'darwin') {
  126. this.skip()
  127. }
  128. })
  129. it('should be false during tests', () => {
  130. expect(app.isInApplicationsFolder()).to.equal(false)
  131. })
  132. })
  133. describe('app.exit(exitCode)', () => {
  134. let appProcess: cp.ChildProcess | null = null
  135. afterEach(() => {
  136. if (appProcess) appProcess.kill()
  137. })
  138. it('emits a process exit event with the code', async () => {
  139. const appPath = path.join(fixturesPath, 'api', 'quit-app')
  140. const electronPath = process.execPath
  141. let output = ''
  142. appProcess = cp.spawn(electronPath, [appPath])
  143. if (appProcess && appProcess.stdout) {
  144. appProcess.stdout.on('data', data => { output += data })
  145. }
  146. const [code] = await emittedOnce(appProcess, 'close')
  147. if (process.platform !== 'win32') {
  148. expect(output).to.include('Exit event with code: 123')
  149. }
  150. expect(code).to.equal(123)
  151. })
  152. it('closes all windows', async function () {
  153. const appPath = path.join(fixturesPath, 'api', 'exit-closes-all-windows-app')
  154. const electronPath = process.execPath
  155. appProcess = cp.spawn(electronPath, [appPath])
  156. const [code, signal] = await emittedOnce(appProcess, 'close')
  157. expect(signal).to.equal(null, 'exit signal should be null, if you see this please tag @MarshallOfSound')
  158. expect(code).to.equal(123, 'exit code should be 123, if you see this please tag @MarshallOfSound')
  159. })
  160. it('exits gracefully', async function () {
  161. if (!['darwin', 'linux'].includes(process.platform)) {
  162. this.skip()
  163. return
  164. }
  165. const electronPath = process.execPath
  166. const appPath = path.join(fixturesPath, 'api', 'singleton')
  167. appProcess = cp.spawn(electronPath, [appPath])
  168. // Singleton will send us greeting data to let us know it's running.
  169. // After that, ask it to exit gracefully and confirm that it does.
  170. if (appProcess && appProcess.stdout) {
  171. appProcess.stdout.on('data', data => appProcess!.kill())
  172. }
  173. const [code, signal] = await emittedOnce(appProcess, 'close')
  174. const message = `code:\n${code}\nsignal:\n${signal}`
  175. expect(code).to.equal(0, message)
  176. expect(signal).to.equal(null, message)
  177. })
  178. })
  179. describe('app.requestSingleInstanceLock', () => {
  180. it('prevents the second launch of app', function (done) {
  181. this.timeout(120000)
  182. const appPath = path.join(fixturesPath, 'api', 'singleton')
  183. const first = cp.spawn(process.execPath, [appPath])
  184. first.once('exit', code => {
  185. expect(code).to.equal(0)
  186. })
  187. // Start second app when received output.
  188. first.stdout.once('data', () => {
  189. const second = cp.spawn(process.execPath, [appPath])
  190. second.once('exit', code => {
  191. expect(code).to.equal(1)
  192. done()
  193. })
  194. })
  195. })
  196. it('passes arguments to the second-instance event', async () => {
  197. const appPath = path.join(fixturesPath, 'api', 'singleton')
  198. const first = cp.spawn(process.execPath, [appPath])
  199. const firstExited = emittedOnce(first, 'exit')
  200. // Wait for the first app to boot.
  201. const firstStdoutLines = first.stdout.pipe(split())
  202. while ((await emittedOnce(firstStdoutLines, 'data')).toString() !== 'started') {
  203. // wait.
  204. }
  205. const data2Promise = emittedOnce(firstStdoutLines, 'data')
  206. const secondInstanceArgs = [process.execPath, appPath, '--some-switch', 'some-arg']
  207. const second = cp.spawn(secondInstanceArgs[0], secondInstanceArgs.slice(1))
  208. const [code2] = await emittedOnce(second, 'exit')
  209. expect(code2).to.equal(1)
  210. const [code1] = await firstExited
  211. expect(code1).to.equal(0)
  212. const data2 = (await data2Promise)[0].toString('ascii')
  213. const secondInstanceArgsReceived: string[] = JSON.parse(data2.toString('ascii'))
  214. const expected = process.platform === 'win32'
  215. ? [process.execPath, '--some-switch', '--allow-file-access-from-files', secondInstanceArgsReceived.find(x => x.includes('original-process-start-time')), appPath, 'some-arg']
  216. : secondInstanceArgs
  217. expect(secondInstanceArgsReceived).to.eql(expected,
  218. `expected ${JSON.stringify(expected)} but got ${data2.toString('ascii')}`)
  219. })
  220. })
  221. describe('app.relaunch', () => {
  222. let server: net.Server | null = null
  223. const socketPath = process.platform === 'win32' ? '\\\\.\\pipe\\electron-app-relaunch' : '/tmp/electron-app-relaunch'
  224. beforeEach(done => {
  225. fs.unlink(socketPath, () => {
  226. server = net.createServer()
  227. server.listen(socketPath)
  228. done()
  229. })
  230. })
  231. afterEach((done) => {
  232. server!.close(() => {
  233. if (process.platform === 'win32') {
  234. done()
  235. } else {
  236. fs.unlink(socketPath, () => done())
  237. }
  238. })
  239. })
  240. it('relaunches the app', function (done) {
  241. this.timeout(120000)
  242. let state = 'none'
  243. server!.once('error', error => done(error))
  244. server!.on('connection', client => {
  245. client.once('data', data => {
  246. if (String(data) === 'false' && state === 'none') {
  247. state = 'first-launch'
  248. } else if (String(data) === 'true' && state === 'first-launch') {
  249. done()
  250. } else {
  251. done(`Unexpected state: ${state}`)
  252. }
  253. })
  254. })
  255. const appPath = path.join(fixturesPath, 'api', 'relaunch')
  256. cp.spawn(process.execPath, [appPath])
  257. })
  258. })
  259. describe('app.setUserActivity(type, userInfo)', () => {
  260. before(function () {
  261. if (process.platform !== 'darwin') {
  262. this.skip()
  263. }
  264. })
  265. it('sets the current activity', () => {
  266. app.setUserActivity('com.electron.testActivity', { testData: '123' })
  267. expect(app.getCurrentActivityType()).to.equal('com.electron.testActivity')
  268. })
  269. })
  270. // xdescribe('app.importCertificate', () => {
  271. // let w = null
  272. // before(function () {
  273. // if (process.platform !== 'linux') {
  274. // this.skip()
  275. // }
  276. // })
  277. // afterEach(() => closeWindow(w).then(() => { w = null }))
  278. // it('can import certificate into platform cert store', done => {
  279. // const options = {
  280. // certificate: path.join(certPath, 'client.p12'),
  281. // password: 'electron'
  282. // }
  283. // w = new BrowserWindow({
  284. // show: false,
  285. // webPreferences: {
  286. // nodeIntegration: true
  287. // }
  288. // })
  289. // w.webContents.on('did-finish-load', () => {
  290. // expect(w.webContents.getTitle()).to.equal('authorized')
  291. // done()
  292. // })
  293. // ipcRenderer.once('select-client-certificate', (event, webContentsId, list) => {
  294. // expect(webContentsId).to.equal(w.webContents.id)
  295. // expect(list).to.have.lengthOf(1)
  296. // expect(list[0]).to.deep.equal({
  297. // issuerName: 'Intermediate CA',
  298. // subjectName: 'Client Cert',
  299. // issuer: { commonName: 'Intermediate CA' },
  300. // subject: { commonName: 'Client Cert' }
  301. // })
  302. // event.sender.send('client-certificate-response', list[0])
  303. // })
  304. // app.importCertificate(options, result => {
  305. // expect(result).toNotExist()
  306. // ipcRenderer.sendSync('set-client-certificate-option', false)
  307. // w.loadURL(secureUrl)
  308. // })
  309. // })
  310. // })
  311. describe('BrowserWindow events', () => {
  312. let w: BrowserWindow = null as any
  313. afterEach(() => closeWindow(w).then(() => { w = null as any }))
  314. it('should emit browser-window-focus event when window is focused', async () => {
  315. const emitted = emittedOnce(app, 'browser-window-focus')
  316. w = new BrowserWindow({ show: false })
  317. w.emit('focus')
  318. const [, window] = await emitted
  319. expect(window.id).to.equal(w.id)
  320. })
  321. it('should emit browser-window-blur event when window is blured', async () => {
  322. const emitted = emittedOnce(app, 'browser-window-blur')
  323. w = new BrowserWindow({ show: false })
  324. w.emit('blur')
  325. const [, window] = await emitted
  326. expect(window.id).to.equal(w.id)
  327. })
  328. it('should emit browser-window-created event when window is created', async () => {
  329. const emitted = emittedOnce(app, 'browser-window-created')
  330. w = new BrowserWindow({ show: false })
  331. const [, window] = await emitted
  332. expect(window.id).to.equal(w.id)
  333. })
  334. it('should emit web-contents-created event when a webContents is created', async () => {
  335. const emitted = emittedOnce(app, 'web-contents-created')
  336. w = new BrowserWindow({ show: false })
  337. const [, webContents] = await emitted
  338. expect(webContents.id).to.equal(w.webContents.id)
  339. })
  340. // FIXME: re-enable this test on win32.
  341. ifit(process.platform !== 'win32')('should emit renderer-process-crashed event when renderer crashes', async () => {
  342. w = new BrowserWindow({
  343. show: false,
  344. webPreferences: {
  345. nodeIntegration: true
  346. }
  347. })
  348. await w.loadURL('about:blank')
  349. const emitted = emittedOnce(app, 'renderer-process-crashed')
  350. w.webContents.executeJavaScript('process.crash()')
  351. const [, webContents] = await emitted
  352. expect(webContents).to.equal(w.webContents)
  353. })
  354. ifdescribe(features.isDesktopCapturerEnabled())('desktopCapturer module filtering', () => {
  355. it('should emit desktop-capturer-get-sources event when desktopCapturer.getSources() is invoked', async () => {
  356. w = new BrowserWindow({
  357. show: false,
  358. webPreferences: {
  359. nodeIntegration: true
  360. }
  361. })
  362. await w.loadURL('about:blank')
  363. const promise = emittedOnce(app, 'desktop-capturer-get-sources')
  364. w.webContents.executeJavaScript(`require('electron').desktopCapturer.getSources({ types: ['screen'] })`)
  365. const [, webContents] = await promise
  366. expect(webContents).to.equal(w.webContents)
  367. })
  368. })
  369. describe('remote module filtering', () => {
  370. it('should emit remote-require event when remote.require() is invoked', async () => {
  371. w = new BrowserWindow({
  372. show: false,
  373. webPreferences: {
  374. nodeIntegration: true
  375. }
  376. })
  377. await w.loadURL('about:blank')
  378. const promise = emittedOnce(app, 'remote-require')
  379. w.webContents.executeJavaScript(`require('electron').remote.require('test')`)
  380. const [, webContents, moduleName] = await promise
  381. expect(webContents).to.equal(w.webContents)
  382. expect(moduleName).to.equal('test')
  383. })
  384. it('should emit remote-get-global event when remote.getGlobal() is invoked', async () => {
  385. w = new BrowserWindow({
  386. show: false,
  387. webPreferences: {
  388. nodeIntegration: true
  389. }
  390. })
  391. await w.loadURL('about:blank')
  392. const promise = emittedOnce(app, 'remote-get-global')
  393. w.webContents.executeJavaScript(`require('electron').remote.getGlobal('test')`)
  394. const [, webContents, globalName] = await promise
  395. expect(webContents).to.equal(w.webContents)
  396. expect(globalName).to.equal('test')
  397. })
  398. it('should emit remote-get-builtin event when remote.getBuiltin() is invoked', async () => {
  399. w = new BrowserWindow({
  400. show: false,
  401. webPreferences: {
  402. nodeIntegration: true
  403. }
  404. })
  405. await w.loadURL('about:blank')
  406. const promise = emittedOnce(app, 'remote-get-builtin')
  407. w.webContents.executeJavaScript(`require('electron').remote.app`)
  408. const [, webContents, moduleName] = await promise
  409. expect(webContents).to.equal(w.webContents)
  410. expect(moduleName).to.equal('app')
  411. })
  412. it('should emit remote-get-current-window event when remote.getCurrentWindow() is invoked', async () => {
  413. w = new BrowserWindow({
  414. show: false,
  415. webPreferences: {
  416. nodeIntegration: true
  417. }
  418. })
  419. await w.loadURL('about:blank')
  420. const promise = emittedOnce(app, 'remote-get-current-window')
  421. w.webContents.executeJavaScript(`require('electron').remote.getCurrentWindow()`)
  422. const [, webContents] = await promise
  423. expect(webContents).to.equal(w.webContents)
  424. })
  425. it('should emit remote-get-current-web-contents event when remote.getCurrentWebContents() is invoked', async () => {
  426. w = new BrowserWindow({
  427. show: false,
  428. webPreferences: {
  429. nodeIntegration: true
  430. }
  431. })
  432. await w.loadURL('about:blank')
  433. const promise = emittedOnce(app, 'remote-get-current-web-contents')
  434. w.webContents.executeJavaScript(`require('electron').remote.getCurrentWebContents()`)
  435. const [, webContents] = await promise
  436. expect(webContents).to.equal(w.webContents)
  437. })
  438. })
  439. })
  440. describe('app.badgeCount', () => {
  441. const platformIsNotSupported =
  442. (process.platform === 'win32') ||
  443. (process.platform === 'linux' && !app.isUnityRunning())
  444. const platformIsSupported = !platformIsNotSupported
  445. const expectedBadgeCount = 42
  446. after(() => { app.badgeCount = 0 })
  447. describe('on supported platform', () => {
  448. it('sets a badge count', function () {
  449. if (platformIsNotSupported) return this.skip()
  450. app.badgeCount = expectedBadgeCount
  451. expect(app.badgeCount).to.equal(expectedBadgeCount)
  452. })
  453. })
  454. describe('on unsupported platform', () => {
  455. it('does not set a badge count', function () {
  456. if (platformIsSupported) return this.skip()
  457. app.badgeCount = 9999
  458. expect(app.badgeCount).to.equal(0)
  459. })
  460. })
  461. })
  462. describe('app.get/setLoginItemSettings API', function () {
  463. const updateExe = path.resolve(path.dirname(process.execPath), '..', 'Update.exe')
  464. const processStartArgs = [
  465. '--processStart', `"${path.basename(process.execPath)}"`,
  466. '--process-start-args', `"--hidden"`
  467. ]
  468. before(function () {
  469. if (process.platform === 'linux' || process.mas) this.skip()
  470. })
  471. beforeEach(() => {
  472. app.setLoginItemSettings({ openAtLogin: false })
  473. app.setLoginItemSettings({ openAtLogin: false, path: updateExe, args: processStartArgs })
  474. })
  475. afterEach(() => {
  476. app.setLoginItemSettings({ openAtLogin: false })
  477. app.setLoginItemSettings({ openAtLogin: false, path: updateExe, args: processStartArgs })
  478. })
  479. it('sets and returns the app as a login item', done => {
  480. app.setLoginItemSettings({ openAtLogin: true })
  481. expect(app.getLoginItemSettings()).to.deep.equal({
  482. openAtLogin: true,
  483. openAsHidden: false,
  484. wasOpenedAtLogin: false,
  485. wasOpenedAsHidden: false,
  486. restoreState: false
  487. })
  488. done()
  489. })
  490. it('adds a login item that loads in hidden mode', done => {
  491. app.setLoginItemSettings({ openAtLogin: true, openAsHidden: true })
  492. expect(app.getLoginItemSettings()).to.deep.equal({
  493. openAtLogin: true,
  494. openAsHidden: process.platform === 'darwin' && !process.mas, // Only available on macOS
  495. wasOpenedAtLogin: false,
  496. wasOpenedAsHidden: false,
  497. restoreState: false
  498. })
  499. done()
  500. })
  501. it('correctly sets and unsets the LoginItem', function () {
  502. expect(app.getLoginItemSettings().openAtLogin).to.equal(false)
  503. app.setLoginItemSettings({ openAtLogin: true })
  504. expect(app.getLoginItemSettings().openAtLogin).to.equal(true)
  505. app.setLoginItemSettings({ openAtLogin: false })
  506. expect(app.getLoginItemSettings().openAtLogin).to.equal(false)
  507. })
  508. it('correctly sets and unsets the LoginItem as hidden', function () {
  509. if (process.platform !== 'darwin') this.skip()
  510. expect(app.getLoginItemSettings().openAtLogin).to.equal(false)
  511. expect(app.getLoginItemSettings().openAsHidden).to.equal(false)
  512. app.setLoginItemSettings({ openAtLogin: true, openAsHidden: true })
  513. expect(app.getLoginItemSettings().openAtLogin).to.equal(true)
  514. expect(app.getLoginItemSettings().openAsHidden).to.equal(true)
  515. app.setLoginItemSettings({ openAtLogin: true, openAsHidden: false })
  516. expect(app.getLoginItemSettings().openAtLogin).to.equal(true)
  517. expect(app.getLoginItemSettings().openAsHidden).to.equal(false)
  518. })
  519. it('allows you to pass a custom executable and arguments', function () {
  520. if (process.platform !== 'win32') this.skip()
  521. app.setLoginItemSettings({ openAtLogin: true, path: updateExe, args: processStartArgs })
  522. expect(app.getLoginItemSettings().openAtLogin).to.equal(false)
  523. expect(app.getLoginItemSettings({
  524. path: updateExe,
  525. args: processStartArgs
  526. }).openAtLogin).to.equal(true)
  527. })
  528. })
  529. describe('accessibilitySupportEnabled property', () => {
  530. if (process.platform === 'linux') return
  531. it('returns whether the Chrome has accessibility APIs enabled', () => {
  532. expect(app.accessibilitySupportEnabled).to.be.a('boolean')
  533. //TODO(codebytere): remove when propertyification is complete
  534. expect(app.isAccessibilitySupportEnabled).to.be.a('function')
  535. expect(app.setAccessibilitySupportEnabled).to.be.a('function')
  536. })
  537. })
  538. describe('getAppPath', () => {
  539. it('works for directories with package.json', async () => {
  540. const { appPath } = await runTestApp('app-path')
  541. expect(appPath).to.equal(path.resolve(fixturesPath, 'api/app-path'))
  542. })
  543. it('works for directories with index.js', async () => {
  544. const { appPath } = await runTestApp('app-path/lib')
  545. expect(appPath).to.equal(path.resolve(fixturesPath, 'api/app-path/lib'))
  546. })
  547. it('works for files without extension', async () => {
  548. const { appPath } = await runTestApp('app-path/lib/index')
  549. expect(appPath).to.equal(path.resolve(fixturesPath, 'api/app-path/lib'))
  550. })
  551. it('works for files', async () => {
  552. const { appPath } = await runTestApp('app-path/lib/index.js')
  553. expect(appPath).to.equal(path.resolve(fixturesPath, 'api/app-path/lib'))
  554. })
  555. })
  556. describe('getPath("logs")', () => {
  557. const logsPaths = {
  558. 'darwin': path.resolve(homedir(), 'Library', 'Logs'),
  559. 'linux': path.resolve(homedir(), 'AppData', app.name),
  560. 'win32': path.resolve(homedir(), 'AppData', app.name),
  561. }
  562. it('has no logs directory by default', () => {
  563. // this won't be deterministic except on CI since
  564. // users may or may not have this dir
  565. if (!isCI) return
  566. const osLogPath = (logsPaths as any)[process.platform]
  567. expect(fs.existsSync(osLogPath)).to.be.false
  568. })
  569. it('creates a new logs directory if one does not exist', () => {
  570. expect(() => { app.getPath('logs') }).to.not.throw()
  571. const osLogPath = (logsPaths as any)[process.platform]
  572. expect(fs.existsSync(osLogPath)).to.be.true
  573. })
  574. })
  575. describe('getPath(name)', () => {
  576. it('returns paths that exist', () => {
  577. const paths = [
  578. fs.existsSync(app.getPath('exe')),
  579. fs.existsSync(app.getPath('home')),
  580. fs.existsSync(app.getPath('temp'))
  581. ]
  582. expect(paths).to.deep.equal([true, true, true])
  583. })
  584. it('throws an error when the name is invalid', () => {
  585. expect(() => {
  586. app.getPath('does-not-exist' as any)
  587. }).to.throw(/Failed to get 'does-not-exist' path/)
  588. })
  589. it('returns the overridden path', () => {
  590. app.setPath('music', __dirname)
  591. expect(app.getPath('music')).to.equal(__dirname)
  592. })
  593. })
  594. describe('setPath(name, path)', () => {
  595. it('does not create a new directory by default', () => {
  596. const badPath = path.join(__dirname, 'music')
  597. expect(fs.existsSync(badPath)).to.be.false
  598. app.setPath('music', badPath)
  599. expect(fs.existsSync(badPath)).to.be.false
  600. expect(() => { app.getPath(badPath as any) }).to.throw()
  601. })
  602. })
  603. describe('select-client-certificate event', () => {
  604. let w: BrowserWindow
  605. before(function () {
  606. if (process.platform === 'linux') {
  607. this.skip()
  608. }
  609. })
  610. beforeEach(() => {
  611. w = new BrowserWindow({
  612. show: false,
  613. webPreferences: {
  614. nodeIntegration: true,
  615. partition: 'empty-certificate'
  616. }
  617. })
  618. })
  619. afterEach(() => closeWindow(w).then(() => { w = null as any }))
  620. it('can respond with empty certificate list', async () => {
  621. app.once('select-client-certificate', function (event, webContents, url, list, callback) {
  622. console.log('select-client-certificate emitted')
  623. event.preventDefault()
  624. callback()
  625. })
  626. await w.webContents.loadURL(secureUrl)
  627. expect(w.webContents.getTitle()).to.equal('denied')
  628. })
  629. })
  630. describe('setAsDefaultProtocolClient(protocol, path, args)', () => {
  631. const protocol = 'electron-test'
  632. const updateExe = path.resolve(path.dirname(process.execPath), '..', 'Update.exe')
  633. const processStartArgs = [
  634. '--processStart', `"${path.basename(process.execPath)}"`,
  635. '--process-start-args', `"--hidden"`
  636. ]
  637. let Winreg: any
  638. let classesKey: any
  639. before(function () {
  640. if (process.platform !== 'win32') {
  641. this.skip()
  642. } else {
  643. Winreg = require('winreg')
  644. classesKey = new Winreg({
  645. hive: Winreg.HKCU,
  646. key: '\\Software\\Classes\\'
  647. })
  648. }
  649. })
  650. after(function (done) {
  651. if (process.platform !== 'win32') {
  652. done()
  653. } else {
  654. const protocolKey = new Winreg({
  655. hive: Winreg.HKCU,
  656. key: `\\Software\\Classes\\${protocol}`
  657. })
  658. // The last test leaves the registry dirty,
  659. // delete the protocol key for those of us who test at home
  660. protocolKey.destroy(() => done())
  661. }
  662. })
  663. beforeEach(() => {
  664. app.removeAsDefaultProtocolClient(protocol)
  665. app.removeAsDefaultProtocolClient(protocol, updateExe, processStartArgs)
  666. })
  667. afterEach(() => {
  668. app.removeAsDefaultProtocolClient(protocol)
  669. expect(app.isDefaultProtocolClient(protocol)).to.equal(false)
  670. app.removeAsDefaultProtocolClient(protocol, updateExe, processStartArgs)
  671. expect(app.isDefaultProtocolClient(protocol, updateExe, processStartArgs)).to.equal(false)
  672. })
  673. it('sets the app as the default protocol client', () => {
  674. expect(app.isDefaultProtocolClient(protocol)).to.equal(false)
  675. app.setAsDefaultProtocolClient(protocol)
  676. expect(app.isDefaultProtocolClient(protocol)).to.equal(true)
  677. })
  678. it('allows a custom path and args to be specified', () => {
  679. expect(app.isDefaultProtocolClient(protocol, updateExe, processStartArgs)).to.equal(false)
  680. app.setAsDefaultProtocolClient(protocol, updateExe, processStartArgs)
  681. expect(app.isDefaultProtocolClient(protocol, updateExe, processStartArgs)).to.equal(true)
  682. expect(app.isDefaultProtocolClient(protocol)).to.equal(false)
  683. })
  684. it('creates a registry entry for the protocol class', (done) => {
  685. app.setAsDefaultProtocolClient(protocol)
  686. classesKey.keys((error: Error, keys: any[]) => {
  687. if (error) throw error
  688. const exists = !!keys.find(key => key.key.includes(protocol))
  689. expect(exists).to.equal(true)
  690. done()
  691. })
  692. })
  693. it('completely removes a registry entry for the protocol class', (done) => {
  694. app.setAsDefaultProtocolClient(protocol)
  695. app.removeAsDefaultProtocolClient(protocol)
  696. classesKey.keys((error: Error, keys: any[]) => {
  697. if (error) throw error
  698. const exists = !!keys.find(key => key.key.includes(protocol))
  699. expect(exists).to.equal(false)
  700. done()
  701. })
  702. })
  703. it('only unsets a class registry key if it contains other data', (done) => {
  704. app.setAsDefaultProtocolClient(protocol)
  705. const protocolKey = new Winreg({
  706. hive: Winreg.HKCU,
  707. key: `\\Software\\Classes\\${protocol}`
  708. })
  709. protocolKey.set('test-value', 'REG_BINARY', '123', () => {
  710. app.removeAsDefaultProtocolClient(protocol)
  711. classesKey.keys((error: Error, keys: any[]) => {
  712. if (error) throw error
  713. const exists = !!keys.find(key => key.key.includes(protocol))
  714. expect(exists).to.equal(true)
  715. done()
  716. })
  717. })
  718. })
  719. })
  720. describe('app launch through uri', () => {
  721. before(function () {
  722. if (process.platform !== 'win32') {
  723. this.skip()
  724. }
  725. })
  726. it('does not launch for argument following a URL', done => {
  727. const appPath = path.join(fixturesPath, 'api', 'quit-app')
  728. // App should exit with non 123 code.
  729. const first = cp.spawn(process.execPath, [appPath, 'electron-test:?', 'abc'])
  730. first.once('exit', code => {
  731. expect(code).to.not.equal(123)
  732. done()
  733. })
  734. })
  735. it('launches successfully for argument following a file path', done => {
  736. const appPath = path.join(fixturesPath, 'api', 'quit-app')
  737. // App should exit with code 123.
  738. const first = cp.spawn(process.execPath, [appPath, 'e:\\abc', 'abc'])
  739. first.once('exit', code => {
  740. expect(code).to.equal(123)
  741. done()
  742. })
  743. })
  744. it('launches successfully for multiple URIs following --', done => {
  745. const appPath = path.join(fixturesPath, 'api', 'quit-app')
  746. // App should exit with code 123.
  747. const first = cp.spawn(process.execPath, [appPath, '--', 'http://electronjs.org', 'electron-test://testdata'])
  748. first.once('exit', code => {
  749. expect(code).to.equal(123)
  750. done()
  751. })
  752. })
  753. })
  754. describe('getFileIcon() API', () => {
  755. const iconPath = path.join(__dirname, 'fixtures/assets/icon.ico')
  756. const sizes = {
  757. small: 16,
  758. normal: 32,
  759. large: process.platform === 'win32' ? 32 : 48
  760. }
  761. // (alexeykuzmin): `.skip()` called in `before`
  762. // doesn't affect nested `describe`s.
  763. beforeEach(function () {
  764. // FIXME Get these specs running on Linux CI
  765. if (process.platform === 'linux' && isCI) {
  766. this.skip()
  767. }
  768. })
  769. it('fetches a non-empty icon', async () => {
  770. const icon = await app.getFileIcon(iconPath)
  771. expect(icon.isEmpty()).to.equal(false)
  772. })
  773. it('fetches normal icon size by default', async () => {
  774. const icon = await app.getFileIcon(iconPath)
  775. const size = icon.getSize()
  776. expect(size.height).to.equal(sizes.normal)
  777. expect(size.width).to.equal(sizes.normal)
  778. })
  779. describe('size option', () => {
  780. it('fetches a small icon', async () => {
  781. const icon = await app.getFileIcon(iconPath, { size: 'small' })
  782. const size = icon.getSize()
  783. expect(size.height).to.equal(sizes.small)
  784. expect(size.width).to.equal(sizes.small)
  785. })
  786. it('fetches a normal icon', async () => {
  787. const icon = await app.getFileIcon(iconPath, { size: 'normal' })
  788. const size = icon.getSize()
  789. expect(size.height).to.equal(sizes.normal)
  790. expect(size.width).to.equal(sizes.normal)
  791. })
  792. it('fetches a large icon', async () => {
  793. // macOS does not support large icons
  794. if (process.platform === 'darwin') return
  795. const icon = await app.getFileIcon(iconPath, { size: 'large' })
  796. const size = icon.getSize()
  797. expect(size.height).to.equal(sizes.large)
  798. expect(size.width).to.equal(sizes.large)
  799. })
  800. })
  801. })
  802. describe('getAppMetrics() API', () => {
  803. it('returns memory and cpu stats of all running electron processes', () => {
  804. const appMetrics = app.getAppMetrics()
  805. expect(appMetrics).to.be.an('array').and.have.lengthOf.at.least(1, 'App memory info object is not > 0')
  806. const types = []
  807. for (const entry of appMetrics) {
  808. expect(entry.pid).to.be.above(0, 'pid is not > 0')
  809. expect(entry.type).to.be.a('string').that.does.not.equal('')
  810. expect(entry.creationTime).to.be.a('number').that.is.greaterThan(0)
  811. types.push(entry.type)
  812. expect(entry.cpu).to.have.ownProperty('percentCPUUsage').that.is.a('number')
  813. expect(entry.cpu).to.have.ownProperty('idleWakeupsPerSecond').that.is.a('number')
  814. expect(entry.memory).to.have.property('workingSetSize').that.is.greaterThan(0)
  815. expect(entry.memory).to.have.property('peakWorkingSetSize').that.is.greaterThan(0)
  816. if (process.platform === 'win32') {
  817. expect(entry.memory).to.have.property('privateBytes').that.is.greaterThan(0)
  818. }
  819. if (process.platform !== 'linux') {
  820. expect(entry.sandboxed).to.be.a('boolean')
  821. }
  822. if (process.platform === 'win32') {
  823. expect(entry.integrityLevel).to.be.a('string')
  824. }
  825. }
  826. if (process.platform === 'darwin') {
  827. expect(types).to.include('GPU')
  828. }
  829. expect(types).to.include('Browser')
  830. })
  831. })
  832. describe('getGPUFeatureStatus() API', () => {
  833. it('returns the graphic features statuses', () => {
  834. const features = app.getGPUFeatureStatus()
  835. expect(features).to.have.ownProperty('webgl').that.is.a('string')
  836. expect(features).to.have.ownProperty('gpu_compositing').that.is.a('string')
  837. })
  838. })
  839. describe('getGPUInfo() API', () => {
  840. const appPath = path.join(fixturesPath, 'api', 'gpu-info.js')
  841. const getGPUInfo = async (type: string) => {
  842. const appProcess = cp.spawn(process.execPath, [appPath, type])
  843. let gpuInfoData = ''
  844. let errorData = ''
  845. appProcess.stdout.on('data', (data) => {
  846. gpuInfoData += data
  847. })
  848. appProcess.stderr.on('data', (data) => {
  849. errorData += data
  850. })
  851. const [exitCode] = await emittedOnce(appProcess, 'exit')
  852. if (exitCode === 0) {
  853. // return info data on successful exit
  854. return JSON.parse(gpuInfoData)
  855. } else {
  856. // return error if not clean exit
  857. return Promise.reject(new Error(errorData))
  858. }
  859. }
  860. const verifyBasicGPUInfo = async (gpuInfo: any) => {
  861. // Devices information is always present in the available info.
  862. expect(gpuInfo).to.have.ownProperty('gpuDevice')
  863. .that.is.an('array')
  864. .and.does.not.equal([])
  865. const device = gpuInfo.gpuDevice[0]
  866. expect(device).to.be.an('object')
  867. .and.to.have.property('deviceId')
  868. .that.is.a('number')
  869. .not.lessThan(0)
  870. }
  871. it('succeeds with basic GPUInfo', async () => {
  872. const gpuInfo = await getGPUInfo('basic')
  873. await verifyBasicGPUInfo(gpuInfo)
  874. })
  875. it('succeeds with complete GPUInfo', async () => {
  876. const completeInfo = await getGPUInfo('complete')
  877. if (process.platform === 'linux') {
  878. // For linux and macOS complete info is same as basic info
  879. await verifyBasicGPUInfo(completeInfo)
  880. const basicInfo = await getGPUInfo('basic')
  881. expect(completeInfo).to.deep.equal(basicInfo)
  882. } else {
  883. // Gl version is present in the complete info.
  884. expect(completeInfo).to.have.ownProperty('auxAttributes')
  885. .that.is.an('object')
  886. expect(completeInfo.auxAttributes).to.have.ownProperty('glVersion')
  887. .that.is.a('string')
  888. .and.does.not.equal([])
  889. }
  890. })
  891. it('fails for invalid info_type', () => {
  892. const invalidType = 'invalid'
  893. const expectedErrorMessage = "Invalid info type. Use 'basic' or 'complete'"
  894. return expect(app.getGPUInfo(invalidType as any)).to.eventually.be.rejectedWith(expectedErrorMessage)
  895. })
  896. })
  897. describe('sandbox options', () => {
  898. let appProcess: cp.ChildProcess = null as any
  899. let server: net.Server = null as any
  900. const socketPath = process.platform === 'win32' ? '\\\\.\\pipe\\electron-mixed-sandbox' : '/tmp/electron-mixed-sandbox'
  901. beforeEach(function (done) {
  902. if (process.platform === 'linux' && (process.arch === 'arm64' || process.arch === 'arm')) {
  903. // Our ARM tests are run on VSTS rather than CircleCI, and the Docker
  904. // setup on VSTS disallows syscalls that Chrome requires for setting up
  905. // sandboxing.
  906. // See:
  907. // - https://docs.docker.com/engine/security/seccomp/#significant-syscalls-blocked-by-the-default-profile
  908. // - https://chromium.googlesource.com/chromium/src/+/70.0.3538.124/sandbox/linux/services/credentials.cc#292
  909. // - https://github.com/docker/docker-ce/blob/ba7dfc59ccfe97c79ee0d1379894b35417b40bca/components/engine/profiles/seccomp/seccomp_default.go#L497
  910. // - https://blog.jessfraz.com/post/how-to-use-new-docker-seccomp-profiles/
  911. //
  912. // Adding `--cap-add SYS_ADMIN` or `--security-opt seccomp=unconfined`
  913. // to the Docker invocation allows the syscalls that Chrome needs, but
  914. // are probably more permissive than we'd like.
  915. this.skip()
  916. }
  917. fs.unlink(socketPath, () => {
  918. server = net.createServer()
  919. server.listen(socketPath)
  920. done()
  921. })
  922. })
  923. afterEach(done => {
  924. if (appProcess != null) appProcess.kill()
  925. server.close(() => {
  926. if (process.platform === 'win32') {
  927. done()
  928. } else {
  929. fs.unlink(socketPath, () => done())
  930. }
  931. })
  932. })
  933. describe('when app.enableSandbox() is called', () => {
  934. it('adds --enable-sandbox to all renderer processes', done => {
  935. const appPath = path.join(fixturesPath, 'api', 'mixed-sandbox-app')
  936. appProcess = cp.spawn(process.execPath, [appPath, '--app-enable-sandbox'])
  937. server.once('error', error => { done(error) })
  938. server.on('connection', client => {
  939. client.once('data', (data) => {
  940. const argv = JSON.parse(data.toString())
  941. expect(argv.sandbox).to.include('--enable-sandbox')
  942. expect(argv.sandbox).to.not.include('--no-sandbox')
  943. expect(argv.noSandbox).to.include('--enable-sandbox')
  944. expect(argv.noSandbox).to.not.include('--no-sandbox')
  945. expect(argv.noSandboxDevtools).to.equal(true)
  946. expect(argv.sandboxDevtools).to.equal(true)
  947. done()
  948. })
  949. })
  950. })
  951. })
  952. describe('when the app is launched with --enable-sandbox', () => {
  953. it('adds --enable-sandbox to all renderer processes', done => {
  954. const appPath = path.join(fixturesPath, 'api', 'mixed-sandbox-app')
  955. appProcess = cp.spawn(process.execPath, [appPath, '--enable-sandbox'])
  956. server.once('error', error => { done(error) })
  957. server.on('connection', client => {
  958. client.once('data', data => {
  959. const argv = JSON.parse(data.toString())
  960. expect(argv.sandbox).to.include('--enable-sandbox')
  961. expect(argv.sandbox).to.not.include('--no-sandbox')
  962. expect(argv.noSandbox).to.include('--enable-sandbox')
  963. expect(argv.noSandbox).to.not.include('--no-sandbox')
  964. expect(argv.noSandboxDevtools).to.equal(true)
  965. expect(argv.sandboxDevtools).to.equal(true)
  966. done()
  967. })
  968. })
  969. })
  970. })
  971. })
  972. describe('disableDomainBlockingFor3DAPIs() API', () => {
  973. it('throws when called after app is ready', () => {
  974. expect(() => {
  975. app.disableDomainBlockingFor3DAPIs()
  976. }).to.throw(/before app is ready/)
  977. })
  978. })
  979. const dockDescribe = process.platform === 'darwin' ? describe : describe.skip
  980. dockDescribe('dock APIs', () => {
  981. after(async () => {
  982. await app.dock.show()
  983. })
  984. describe('dock.setMenu', () => {
  985. it('can be retrieved via dock.getMenu', () => {
  986. expect(app.dock.getMenu()).to.equal(null)
  987. const menu = new Menu()
  988. app.dock.setMenu(menu)
  989. expect(app.dock.getMenu()).to.equal(menu)
  990. })
  991. it('keeps references to the menu', () => {
  992. app.dock.setMenu(new Menu())
  993. const v8Util = process.electronBinding('v8_util')
  994. v8Util.requestGarbageCollectionForTesting()
  995. })
  996. })
  997. describe('dock.bounce', () => {
  998. it('should return -1 for unknown bounce type', () => {
  999. expect(app.dock.bounce('bad type' as any)).to.equal(-1)
  1000. })
  1001. it('should return a positive number for informational type', () => {
  1002. const appHasFocus = !!BrowserWindow.getFocusedWindow()
  1003. if (!appHasFocus) {
  1004. expect(app.dock.bounce('informational')).to.be.at.least(0)
  1005. }
  1006. })
  1007. it('should return a positive number for critical type', () => {
  1008. const appHasFocus = !!BrowserWindow.getFocusedWindow()
  1009. if (!appHasFocus) {
  1010. expect(app.dock.bounce('critical')).to.be.at.least(0)
  1011. }
  1012. })
  1013. })
  1014. describe('dock.cancelBounce', () => {
  1015. it('should not throw', () => {
  1016. app.dock.cancelBounce(app.dock.bounce('critical'))
  1017. })
  1018. })
  1019. describe('dock.setBadge', () => {
  1020. after(() => {
  1021. app.dock.setBadge('')
  1022. })
  1023. it('should not throw', () => {
  1024. app.dock.setBadge('1')
  1025. })
  1026. it('should be retrievable via getBadge', () => {
  1027. app.dock.setBadge('test')
  1028. expect(app.dock.getBadge()).to.equal('test')
  1029. })
  1030. })
  1031. describe('dock.show', () => {
  1032. it('should not throw', () => {
  1033. return app.dock.show().then(() => {
  1034. expect(app.dock.isVisible()).to.equal(true)
  1035. })
  1036. })
  1037. it('returns a Promise', () => {
  1038. expect(app.dock.show()).to.be.a('promise')
  1039. })
  1040. it('eventually fulfills', async () => {
  1041. await expect(app.dock.show()).to.eventually.be.fulfilled.equal(undefined)
  1042. })
  1043. })
  1044. describe('dock.hide', () => {
  1045. it('should not throw', () => {
  1046. app.dock.hide()
  1047. expect(app.dock.isVisible()).to.equal(false)
  1048. })
  1049. })
  1050. })
  1051. describe('whenReady', () => {
  1052. it('returns a Promise', () => {
  1053. expect(app.whenReady()).to.be.a('promise')
  1054. })
  1055. it('becomes fulfilled if the app is already ready', async () => {
  1056. expect(app.isReady()).to.equal(true)
  1057. await expect(app.whenReady()).to.be.eventually.fulfilled.equal(undefined)
  1058. })
  1059. })
  1060. describe('app.applicationMenu', () => {
  1061. it('has the applicationMenu property', () => {
  1062. expect(app).to.have.property('applicationMenu')
  1063. })
  1064. })
  1065. describe('commandLine.hasSwitch', () => {
  1066. it('returns true when present', () => {
  1067. app.commandLine.appendSwitch('foobar1')
  1068. expect(app.commandLine.hasSwitch('foobar1')).to.equal(true)
  1069. })
  1070. it('returns false when not present', () => {
  1071. expect(app.commandLine.hasSwitch('foobar2')).to.equal(false)
  1072. })
  1073. })
  1074. describe('commandLine.hasSwitch (existing argv)', () => {
  1075. it('returns true when present', async () => {
  1076. const { hasSwitch } = await runTestApp('command-line', '--foobar')
  1077. expect(hasSwitch).to.equal(true)
  1078. })
  1079. it('returns false when not present', async () => {
  1080. const { hasSwitch } = await runTestApp('command-line')
  1081. expect(hasSwitch).to.equal(false)
  1082. })
  1083. })
  1084. describe('commandLine.getSwitchValue', () => {
  1085. it('returns the value when present', () => {
  1086. app.commandLine.appendSwitch('foobar', 'æøåü')
  1087. expect(app.commandLine.getSwitchValue('foobar')).to.equal('æøåü')
  1088. })
  1089. it('returns an empty string when present without value', () => {
  1090. app.commandLine.appendSwitch('foobar1')
  1091. expect(app.commandLine.getSwitchValue('foobar1')).to.equal('')
  1092. })
  1093. it('returns an empty string when not present', () => {
  1094. expect(app.commandLine.getSwitchValue('foobar2')).to.equal('')
  1095. })
  1096. })
  1097. describe('commandLine.getSwitchValue (existing argv)', () => {
  1098. it('returns the value when present', async () => {
  1099. const { getSwitchValue } = await runTestApp('command-line', '--foobar=test')
  1100. expect(getSwitchValue).to.equal('test')
  1101. })
  1102. it('returns an empty string when present without value', async () => {
  1103. const { getSwitchValue } = await runTestApp('command-line', '--foobar')
  1104. expect(getSwitchValue).to.equal('')
  1105. })
  1106. it('returns an empty string when not present', async () => {
  1107. const { getSwitchValue } = await runTestApp('command-line')
  1108. expect(getSwitchValue).to.equal('')
  1109. })
  1110. })
  1111. })
  1112. describe('default behavior', () => {
  1113. describe('application menu', () => {
  1114. it('creates the default menu if the app does not set it', async () => {
  1115. const result = await runTestApp('default-menu')
  1116. expect(result).to.equal(false)
  1117. })
  1118. it('does not create the default menu if the app sets a custom menu', async () => {
  1119. const result = await runTestApp('default-menu', '--custom-menu')
  1120. expect(result).to.equal(true)
  1121. })
  1122. it('does not create the default menu if the app sets a null menu', async () => {
  1123. const result = await runTestApp('default-menu', '--null-menu')
  1124. expect(result).to.equal(true)
  1125. })
  1126. })
  1127. describe('window-all-closed', () => {
  1128. it('quits when the app does not handle the event', async () => {
  1129. const result = await runTestApp('window-all-closed')
  1130. expect(result).to.equal(false)
  1131. })
  1132. it('does not quit when the app handles the event', async () => {
  1133. const result = await runTestApp('window-all-closed', '--handle-event')
  1134. expect(result).to.equal(true)
  1135. })
  1136. })
  1137. describe('user agent fallback', () => {
  1138. let initialValue: string
  1139. before(() => {
  1140. initialValue = app.userAgentFallback!
  1141. })
  1142. it('should have a reasonable default', () => {
  1143. expect(initialValue).to.include(`Electron/${process.versions.electron}`)
  1144. expect(initialValue).to.include(`Chrome/${process.versions.chrome}`)
  1145. })
  1146. it('should be overridable', () => {
  1147. app.userAgentFallback = 'test-agent/123'
  1148. expect(app.userAgentFallback).to.equal('test-agent/123')
  1149. })
  1150. it('should be restorable', () => {
  1151. app.userAgentFallback = 'test-agent/123'
  1152. app.userAgentFallback = ''
  1153. expect(app.userAgentFallback).to.equal(initialValue)
  1154. })
  1155. })
  1156. describe('app.allowRendererProcessReuse', () => {
  1157. it('should default to false', () => {
  1158. expect(app.allowRendererProcessReuse).to.equal(false)
  1159. })
  1160. it('should cause renderer processes to get new PIDs when false', async () => {
  1161. const output = await runTestApp('site-instance-overrides', 'false')
  1162. expect(output[0]).to.be.a('number').that.is.greaterThan(0)
  1163. expect(output[1]).to.be.a('number').that.is.greaterThan(0)
  1164. expect(output[0]).to.not.equal(output[1])
  1165. })
  1166. it('should cause renderer processes to keep the same PID when true', async () => {
  1167. const output = await runTestApp('site-instance-overrides', 'true')
  1168. expect(output[0]).to.be.a('number').that.is.greaterThan(0)
  1169. expect(output[1]).to.be.a('number').that.is.greaterThan(0)
  1170. expect(output[0]).to.equal(output[1])
  1171. })
  1172. })
  1173. describe('login event', () => {
  1174. afterEach(closeAllWindows)
  1175. let server: http.Server
  1176. let serverUrl: string
  1177. before((done) => {
  1178. server = http.createServer((request, response) => {
  1179. if (request.headers.authorization) {
  1180. return response.end('ok')
  1181. }
  1182. response
  1183. .writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' })
  1184. .end()
  1185. }).listen(0, '127.0.0.1', () => {
  1186. serverUrl = 'http://127.0.0.1:' + (server.address() as net.AddressInfo).port
  1187. done()
  1188. })
  1189. })
  1190. it('should emit a login event on app when a WebContents hits a 401', async () => {
  1191. const w = new BrowserWindow({ show: false })
  1192. w.loadURL(serverUrl)
  1193. const [, webContents] = await emittedOnce(app, 'login')
  1194. expect(webContents).to.equal(w.webContents)
  1195. })
  1196. })
  1197. })
  1198. async function runTestApp (name: string, ...args: any[]) {
  1199. const appPath = path.join(fixturesPath, 'api', name)
  1200. const electronPath = process.execPath
  1201. const appProcess = cp.spawn(electronPath, [appPath, ...args])
  1202. let output = ''
  1203. appProcess.stdout.on('data', (data) => { output += data })
  1204. await emittedOnce(appProcess.stdout, 'end')
  1205. return JSON.parse(output)
  1206. }