visibility-state-spec.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import { BaseWindow, BrowserWindow, BrowserWindowConstructorOptions, webContents, WebContents, WebContentsView } from 'electron/main';
  2. import { expect } from 'chai';
  3. import * as cp from 'node:child_process';
  4. import { once } from 'node:events';
  5. import * as path from 'node:path';
  6. import { ifdescribe, waitUntil } from './lib/spec-helpers';
  7. import { closeAllWindows } from './lib/window-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: BaseWindow & {webContents: WebContents};
  12. before(() => {
  13. for (const checkWin of BaseWindow.getAllWindows()) {
  14. console.log('WINDOW EXISTS BEFORE TEST STARTED:', checkWin.title, checkWin.id);
  15. }
  16. });
  17. afterEach(async () => {
  18. await closeAllWindows();
  19. w = null as unknown as BrowserWindow;
  20. const existingWCS = webContents.getAllWebContents();
  21. existingWCS.forEach((contents) => contents.close());
  22. });
  23. const load = () => w.webContents.loadFile(path.resolve(__dirname, 'fixtures', 'chromium', 'visibilitystate.html'));
  24. async function haveVisibilityState (state: string) {
  25. const docVisState = await w.webContents.executeJavaScript('document.visibilityState');
  26. return docVisState === state;
  27. }
  28. const itWithOptions = (name: string, options: BrowserWindowConstructorOptions, fn: Mocha.Func) => {
  29. it(name, async function (...args) {
  30. w = new BrowserWindow({
  31. ...options,
  32. paintWhenInitiallyHidden: false,
  33. webPreferences: {
  34. ...(options.webPreferences || {}),
  35. nodeIntegration: true,
  36. contextIsolation: false
  37. }
  38. });
  39. if (options.show && process.platform === 'darwin') {
  40. await once(w, 'show');
  41. }
  42. await Promise.resolve(fn.apply(this, args));
  43. });
  44. it(name + ' with BaseWindow', async function (...args) {
  45. const baseWindow = new BaseWindow({
  46. ...options
  47. });
  48. const wcv = new WebContentsView({ webPreferences: { ...(options.webPreferences ?? {}), nodeIntegration: true, contextIsolation: false } });
  49. baseWindow.contentView = wcv;
  50. w = Object.assign(baseWindow, { webContents: wcv.webContents });
  51. if (options.show && process.platform === 'darwin') {
  52. await once(w, 'show');
  53. }
  54. await Promise.resolve(fn.apply(this, args));
  55. });
  56. };
  57. itWithOptions('should be visible when the window is initially shown by default', {}, async () => {
  58. load();
  59. await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled();
  60. });
  61. itWithOptions('should be visible when the window is initially shown', {
  62. show: true
  63. }, async () => {
  64. load();
  65. await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled();
  66. });
  67. itWithOptions('should be hidden when the window is initially hidden', {
  68. show: false
  69. }, async () => {
  70. load();
  71. await expect(waitUntil(async () => await haveVisibilityState('hidden'))).to.eventually.be.fulfilled();
  72. });
  73. itWithOptions('should be visible when the window is initially hidden but shown before the page is loaded', {
  74. show: false
  75. }, async () => {
  76. w.show();
  77. load();
  78. await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled();
  79. });
  80. itWithOptions('should be hidden when the window is initially shown but hidden before the page is loaded', {
  81. show: true
  82. }, async () => {
  83. w.hide();
  84. load();
  85. await expect(waitUntil(async () => await haveVisibilityState('hidden'))).to.eventually.be.fulfilled();
  86. });
  87. itWithOptions('should be toggle between visible and hidden as the window is hidden and shown', {}, async () => {
  88. load();
  89. await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled();
  90. w.hide();
  91. await expect(waitUntil(async () => await haveVisibilityState('hidden'))).to.eventually.be.fulfilled();
  92. w.show();
  93. await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled();
  94. });
  95. itWithOptions('should become hidden when a window is minimized', {}, async () => {
  96. load();
  97. await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled();
  98. w.minimize();
  99. await expect(waitUntil(async () => await haveVisibilityState('hidden'))).to.eventually.be.fulfilled();
  100. });
  101. itWithOptions('should become visible when a window is restored', {}, async () => {
  102. load();
  103. await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled();
  104. w.minimize();
  105. await expect(waitUntil(async () => await haveVisibilityState('hidden'))).to.eventually.be.fulfilled();
  106. w.restore();
  107. await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled();
  108. });
  109. ifdescribe(process.platform === 'darwin')('on platforms that support occlusion detection', () => {
  110. let child: cp.ChildProcess;
  111. const makeOtherWindow = (opts: { x: number; y: number; width: number; height: number; }) => {
  112. child = cp.spawn(process.execPath, [path.resolve(__dirname, 'fixtures', 'chromium', 'other-window.js'), `${opts.x}`, `${opts.y}`, `${opts.width}`, `${opts.height}`]);
  113. return new Promise<void>(resolve => {
  114. child.stdout!.on('data', (chunk) => {
  115. if (chunk.toString().includes('__ready__')) resolve();
  116. });
  117. });
  118. };
  119. afterEach(() => {
  120. if (child && !child.killed) {
  121. child.kill('SIGTERM');
  122. }
  123. });
  124. itWithOptions('should be visible when two windows are on screen', {
  125. x: 0,
  126. y: 0,
  127. width: 200,
  128. height: 200
  129. }, async () => {
  130. await makeOtherWindow({
  131. x: 200,
  132. y: 0,
  133. width: 200,
  134. height: 200
  135. });
  136. load();
  137. await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled();
  138. });
  139. itWithOptions('should be visible when two windows are on screen that overlap partially', {
  140. x: 50,
  141. y: 50,
  142. width: 150,
  143. height: 150
  144. }, async () => {
  145. await makeOtherWindow({
  146. x: 100,
  147. y: 0,
  148. width: 200,
  149. height: 200
  150. });
  151. load();
  152. await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled();
  153. });
  154. itWithOptions('should be hidden when a second window completely occludes the current window', {
  155. x: 50,
  156. y: 50,
  157. width: 50,
  158. height: 50
  159. }, async function () {
  160. this.timeout(240000);
  161. load();
  162. await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled();
  163. makeOtherWindow({
  164. x: 0,
  165. y: 0,
  166. width: 300,
  167. height: 300
  168. });
  169. await expect(waitUntil(async () => await haveVisibilityState('hidden'))).to.eventually.be.fulfilled();
  170. });
  171. });
  172. });