visibility-state-spec.ts 6.0 KB

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