visibility-state-spec.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import { expect } from 'chai'
  2. import * as cp from 'child_process';
  3. import { BrowserWindow, BrowserWindowConstructorOptions, ipcMain } from 'electron'
  4. import * as path from 'path'
  5. import { emittedOnce } from './events-helpers'
  6. import { closeWindow } from './window-helpers';
  7. import { ifdescribe } from './spec-helpers';
  8. // visibilityState specs pass on linux with a real window manager but on CI
  9. // the environment does not let these specs pass
  10. ifdescribe(process.platform !== 'linux' || !isCI)('document.visibilityState', () => {
  11. let w: BrowserWindow
  12. afterEach(() => {
  13. return closeWindow(w)
  14. })
  15. const load = () => w.loadFile(path.resolve(__dirname, 'fixtures', 'chromium', 'visibilitystate.html'))
  16. const itWithOptions = (name: string, options: BrowserWindowConstructorOptions, fn: Mocha.Func) => {
  17. return it(name, async function (...args) {
  18. w = new BrowserWindow({
  19. ...options,
  20. paintWhenInitiallyHidden: false,
  21. webPreferences: {
  22. ...(options.webPreferences || {}),
  23. nodeIntegration: true
  24. }
  25. })
  26. await Promise.resolve(fn.apply(this, args))
  27. })
  28. }
  29. const getVisibilityState = async (): Promise<string> => {
  30. const response = emittedOnce(ipcMain, 'visibility-state')
  31. w.webContents.send('get-visibility-state')
  32. return (await response)[1]
  33. }
  34. itWithOptions('should be visible when the window is initially shown by default', {}, async () => {
  35. await load()
  36. const state = await getVisibilityState()
  37. expect(state).to.equal('visible')
  38. })
  39. itWithOptions('should be visible when the window is initially shown', {
  40. show: true,
  41. }, async () => {
  42. await load()
  43. const state = await getVisibilityState()
  44. expect(state).to.equal('visible')
  45. })
  46. itWithOptions('should be hidden when the window is initially hidden', {
  47. show: false,
  48. }, async () => {
  49. await load()
  50. const state = await getVisibilityState()
  51. expect(state).to.equal('hidden')
  52. })
  53. itWithOptions('should be visible when the window is initially hidden but shown before the page is loaded', {
  54. show: false,
  55. }, async () => {
  56. w.show()
  57. await load()
  58. const state = await getVisibilityState()
  59. expect(state).to.equal('visible')
  60. })
  61. itWithOptions('should be hidden when the window is initially shown but hidden before the page is loaded', {
  62. show: true,
  63. }, async () => {
  64. // TODO(MarshallOfSound): Figure out if we can work around this 1 tick issue for users
  65. if (process.platform === 'darwin') {
  66. // Wait for a tick, the window being "shown" takes 1 tick on macOS
  67. await new Promise(r => setTimeout(r, 0))
  68. }
  69. w.hide()
  70. await load()
  71. const state = await getVisibilityState()
  72. expect(state).to.equal('hidden')
  73. })
  74. itWithOptions('should be toggle between visible and hidden as the window is hidden and shown', {}, async () => {
  75. await load()
  76. expect(await getVisibilityState()).to.equal('visible')
  77. await emittedOnce(ipcMain, 'visibility-change', () => w.hide())
  78. expect(await getVisibilityState()).to.equal('hidden')
  79. await emittedOnce(ipcMain, 'visibility-change', () => w.show())
  80. expect(await getVisibilityState()).to.equal('visible')
  81. })
  82. itWithOptions('should become hidden when a window is minimized', {}, async () => {
  83. await load()
  84. expect(await getVisibilityState()).to.equal('visible')
  85. await emittedOnce(ipcMain, 'visibility-change', () => w.minimize())
  86. expect(await getVisibilityState()).to.equal('hidden')
  87. })
  88. itWithOptions('should become visible when a window is restored', {}, async () => {
  89. await load()
  90. expect(await getVisibilityState()).to.equal('visible')
  91. await emittedOnce(ipcMain, 'visibility-change', () => w.minimize())
  92. await emittedOnce(ipcMain, 'visibility-change', () => w.restore())
  93. expect(await getVisibilityState()).to.equal('visible')
  94. })
  95. describe('on platforms that support occlusion detection', () => {
  96. let child: cp.ChildProcess
  97. before(function() {
  98. if (process.platform !== 'darwin') this.skip()
  99. })
  100. const makeOtherWindow = (opts: { x: number; y: number; width: number; height: number; }) => {
  101. child = cp.spawn(process.execPath, [path.resolve(__dirname, 'fixtures', 'chromium', 'other-window.js'), `${opts.x}`, `${opts.y}`, `${opts.width}`, `${opts.height}`])
  102. return new Promise(resolve => {
  103. child.stdout!.on('data', (chunk) => {
  104. if (chunk.toString().includes('__ready__')) resolve()
  105. })
  106. })
  107. }
  108. afterEach(() => {
  109. if (child && !child.killed) {
  110. child.kill('SIGTERM')
  111. }
  112. })
  113. itWithOptions('should be visible when two windows are on screen', {
  114. x: 0,
  115. y: 0,
  116. width: 200,
  117. height: 200,
  118. }, async () => {
  119. await makeOtherWindow({
  120. x: 200,
  121. y: 0,
  122. width: 200,
  123. height: 200,
  124. })
  125. await load()
  126. const state = await getVisibilityState()
  127. expect(state).to.equal('visible')
  128. })
  129. itWithOptions('should be visible when two windows are on screen that overlap partially', {
  130. x: 50,
  131. y: 50,
  132. width: 150,
  133. height: 150,
  134. }, async () => {
  135. await makeOtherWindow({
  136. x: 100,
  137. y: 0,
  138. width: 200,
  139. height: 200,
  140. })
  141. await load()
  142. const state = await getVisibilityState()
  143. expect(state).to.equal('visible')
  144. })
  145. itWithOptions('should be hidden when a second window completely conceals the current window', {
  146. x: 50,
  147. y: 50,
  148. width: 50,
  149. height: 50,
  150. }, async function () {
  151. this.timeout(240000)
  152. await load()
  153. await emittedOnce(ipcMain, 'visibility-change', async () => {
  154. await makeOtherWindow({
  155. x: 0,
  156. y: 0,
  157. width: 300,
  158. height: 300,
  159. })
  160. })
  161. const state = await getVisibilityState()
  162. expect(state).to.equal('hidden')
  163. })
  164. })
  165. })