visibility-state-spec.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import { expect } from 'chai';
  2. import * as cp from 'node:child_process';
  3. import { BrowserWindow, BrowserWindowConstructorOptions, ipcMain } from 'electron/main';
  4. import * as path from 'node:path';
  5. import { closeWindow } from './lib/window-helpers';
  6. import { ifdescribe } from './lib/spec-helpers';
  7. import { once } from 'node:events';
  8. import { setTimeout } from 'node:timers/promises';
  9. // visibilityState specs pass on linux with a real window manager but on CI
  10. // the environment does not let these specs pass
  11. ifdescribe(process.platform !== 'linux')('document.visibilityState', () => {
  12. let w: BrowserWindow;
  13. afterEach(() => {
  14. return closeWindow(w);
  15. });
  16. const load = () => w.loadFile(path.resolve(__dirname, 'fixtures', 'chromium', 'visibilitystate.html'));
  17. const itWithOptions = (name: string, options: BrowserWindowConstructorOptions, fn: Mocha.Func) => {
  18. return it(name, async function (...args) {
  19. w = new BrowserWindow({
  20. ...options,
  21. paintWhenInitiallyHidden: false,
  22. webPreferences: {
  23. ...(options.webPreferences || {}),
  24. nodeIntegration: true,
  25. contextIsolation: false
  26. }
  27. });
  28. await Promise.resolve(fn.apply(this, args));
  29. });
  30. };
  31. itWithOptions('should be visible when the window is initially shown by default', {}, async () => {
  32. load();
  33. const [, state] = await once(ipcMain, 'initial-visibility-state');
  34. expect(state).to.equal('visible');
  35. });
  36. itWithOptions('should be visible when the window is initially shown', {
  37. show: true
  38. }, async () => {
  39. load();
  40. const [, state] = await once(ipcMain, 'initial-visibility-state');
  41. expect(state).to.equal('visible');
  42. });
  43. itWithOptions('should be hidden when the window is initially hidden', {
  44. show: false
  45. }, async () => {
  46. load();
  47. const [, state] = await once(ipcMain, 'initial-visibility-state');
  48. expect(state).to.equal('hidden');
  49. });
  50. itWithOptions('should be visible when the window is initially hidden but shown before the page is loaded', {
  51. show: false
  52. }, async () => {
  53. w.show();
  54. load();
  55. const [, state] = await once(ipcMain, 'initial-visibility-state');
  56. expect(state).to.equal('visible');
  57. });
  58. itWithOptions('should be hidden when the window is initially shown but hidden before the page is loaded', {
  59. show: true
  60. }, async () => {
  61. // TODO(MarshallOfSound): Figure out if we can work around this 1 tick issue for users
  62. if (process.platform === 'darwin') {
  63. // Wait for a tick, the window being "shown" takes 1 tick on macOS
  64. await setTimeout(10000);
  65. }
  66. w.hide();
  67. load();
  68. const [, state] = await once(ipcMain, 'initial-visibility-state');
  69. expect(state).to.equal('hidden');
  70. });
  71. itWithOptions('should be toggle between visible and hidden as the window is hidden and shown', {}, async () => {
  72. load();
  73. const [, initialState] = await once(ipcMain, 'initial-visibility-state');
  74. expect(initialState).to.equal('visible');
  75. w.hide();
  76. await once(ipcMain, 'visibility-change-hidden');
  77. w.show();
  78. await once(ipcMain, 'visibility-change-visible');
  79. });
  80. itWithOptions('should become hidden when a window is minimized', {}, async () => {
  81. load();
  82. const [, initialState] = await once(ipcMain, 'initial-visibility-state');
  83. expect(initialState).to.equal('visible');
  84. w.minimize();
  85. const p = once(ipcMain, 'visibility-change-hidden');
  86. w.minimize();
  87. await p;
  88. });
  89. itWithOptions('should become visible when a window is restored', {}, async () => {
  90. load();
  91. const [, initialState] = await once(ipcMain, 'initial-visibility-state');
  92. expect(initialState).to.equal('visible');
  93. w.minimize();
  94. await once(ipcMain, 'visibility-change-hidden');
  95. w.restore();
  96. await once(ipcMain, 'visibility-change-visible');
  97. });
  98. ifdescribe(process.platform === 'darwin')('on platforms that support occlusion detection', () => {
  99. let child: cp.ChildProcess;
  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<void>(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. load();
  126. const [, state] = await once(ipcMain, 'initial-visibility-state');
  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. load();
  142. const [, state] = await once(ipcMain, 'initial-visibility-state');
  143. expect(state).to.equal('visible');
  144. });
  145. itWithOptions('should be hidden when a second window completely occludes the current window', {
  146. x: 50,
  147. y: 50,
  148. width: 50,
  149. height: 50
  150. }, async function () {
  151. this.timeout(240000);
  152. load();
  153. const [, state] = await once(ipcMain, 'initial-visibility-state');
  154. expect(state).to.equal('visible');
  155. makeOtherWindow({
  156. x: 0,
  157. y: 0,
  158. width: 300,
  159. height: 300
  160. });
  161. await once(ipcMain, 'visibility-change-hidden');
  162. });
  163. });
  164. });