modules-spec.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import { expect } from 'chai';
  2. import * as path from 'node:path';
  3. import * as fs from 'node:fs';
  4. import { BrowserWindow } from 'electron/main';
  5. import { ifdescribe, ifit } from './lib/spec-helpers';
  6. import { closeAllWindows } from './lib/window-helpers';
  7. import * as childProcess from 'node:child_process';
  8. import { once } from 'node:events';
  9. const Module = require('node:module') as NodeJS.ModuleInternal;
  10. const nativeModulesEnabled = !process.env.ELECTRON_SKIP_NATIVE_MODULE_TESTS;
  11. describe('modules support', () => {
  12. const fixtures = path.join(__dirname, 'fixtures');
  13. describe('third-party module', () => {
  14. ifdescribe(nativeModulesEnabled)('echo', () => {
  15. afterEach(closeAllWindows);
  16. it('can be required in renderer', async () => {
  17. const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
  18. w.loadURL('about:blank');
  19. await expect(
  20. w.webContents.executeJavaScript(
  21. "{ require('@electron-ci/echo'); null }"
  22. )
  23. ).to.be.fulfilled();
  24. });
  25. it('can be required in node binary', async function () {
  26. const child = childProcess.fork(path.join(fixtures, 'module', 'echo.js'));
  27. const [msg] = await once(child, 'message');
  28. expect(msg).to.equal('ok');
  29. });
  30. ifit(process.platform === 'win32')('can be required if electron.exe is renamed', () => {
  31. const testExecPath = path.join(path.dirname(process.execPath), 'test.exe');
  32. fs.copyFileSync(process.execPath, testExecPath);
  33. try {
  34. const fixture = path.join(fixtures, 'module', 'echo-renamed.js');
  35. expect(fs.existsSync(fixture)).to.be.true();
  36. const child = childProcess.spawnSync(testExecPath, [fixture]);
  37. expect(child.status).to.equal(0);
  38. } finally {
  39. fs.unlinkSync(testExecPath);
  40. }
  41. });
  42. });
  43. const enablePlatforms: NodeJS.Platform[] = [
  44. 'linux',
  45. 'darwin',
  46. 'win32'
  47. ];
  48. ifdescribe(nativeModulesEnabled && enablePlatforms.includes(process.platform))('module that use uv_dlopen', () => {
  49. it('can be required in renderer', async () => {
  50. const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
  51. w.loadURL('about:blank');
  52. await expect(w.webContents.executeJavaScript('{ require(\'@electron-ci/uv-dlopen\'); null }')).to.be.fulfilled();
  53. });
  54. it('can be required in node binary', async function () {
  55. const child = childProcess.fork(path.join(fixtures, 'module', 'uv-dlopen.js'));
  56. const [exitCode] = await once(child, 'exit');
  57. expect(exitCode).to.equal(0);
  58. });
  59. });
  60. describe('q', () => {
  61. describe('Q.when', () => {
  62. it('emits the fullfil callback', (done) => {
  63. const Q = require('q');
  64. Q(true).then((val: boolean) => {
  65. expect(val).to.be.true();
  66. done();
  67. });
  68. });
  69. });
  70. });
  71. describe('require(\'electron/...\')', () => {
  72. it('require(\'electron/lol\') should throw in the main process', () => {
  73. expect(() => {
  74. require('electron/lol');
  75. }).to.throw(/Cannot find module 'electron\/lol'/);
  76. });
  77. it('require(\'electron/lol\') should throw in the renderer process', async () => {
  78. const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
  79. w.loadURL('about:blank');
  80. await expect(w.webContents.executeJavaScript('{ require(\'electron/lol\'); null }')).to.eventually.be.rejected();
  81. });
  82. it('require(\'electron\') should not throw in the main process', () => {
  83. expect(() => {
  84. require('electron');
  85. }).to.not.throw();
  86. });
  87. it('require(\'electron\') should not throw in the renderer process', async () => {
  88. const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
  89. w.loadURL('about:blank');
  90. await expect(w.webContents.executeJavaScript('{ require(\'electron\'); null }')).to.be.fulfilled();
  91. });
  92. it('require(\'electron/main\') should not throw in the main process', () => {
  93. expect(() => {
  94. require('electron/main');
  95. }).to.not.throw();
  96. });
  97. it('require(\'electron/main\') should not throw in the renderer process', async () => {
  98. const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
  99. w.loadURL('about:blank');
  100. await expect(w.webContents.executeJavaScript('{ require(\'electron/main\'); null }')).to.be.fulfilled();
  101. });
  102. it('require(\'electron/renderer\') should not throw in the main process', () => {
  103. expect(() => {
  104. require('electron/renderer');
  105. }).to.not.throw();
  106. });
  107. it('require(\'electron/renderer\') should not throw in the renderer process', async () => {
  108. const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
  109. w.loadURL('about:blank');
  110. await expect(w.webContents.executeJavaScript('{ require(\'electron/renderer\'); null }')).to.be.fulfilled();
  111. });
  112. it('require(\'electron/common\') should not throw in the main process', () => {
  113. expect(() => {
  114. require('electron/common');
  115. }).to.not.throw();
  116. });
  117. it('require(\'electron/common\') should not throw in the renderer process', async () => {
  118. const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
  119. w.loadURL('about:blank');
  120. await expect(w.webContents.executeJavaScript('{ require(\'electron/common\'); null }')).to.be.fulfilled();
  121. });
  122. });
  123. describe('coffeescript', () => {
  124. it('can be registered and used to require .coffee files', () => {
  125. expect(() => {
  126. require('coffeescript').register();
  127. }).to.not.throw();
  128. expect(require('./fixtures/module/test.coffee')).to.be.true();
  129. });
  130. });
  131. });
  132. describe('global variables', () => {
  133. describe('process', () => {
  134. it('can be declared in a module', () => {
  135. expect(require('./fixtures/module/declare-process')).to.equal('declared process');
  136. });
  137. });
  138. describe('global', () => {
  139. it('can be declared in a module', () => {
  140. expect(require('./fixtures/module/declare-global')).to.equal('declared global');
  141. });
  142. });
  143. describe('Buffer', () => {
  144. it('can be declared in a module', () => {
  145. expect(require('./fixtures/module/declare-buffer')).to.equal('declared Buffer');
  146. });
  147. });
  148. });
  149. describe('Module._nodeModulePaths', () => {
  150. describe('when the path is inside the resources path', () => {
  151. it('does not include paths outside of the resources path', () => {
  152. let modulePath = process.resourcesPath;
  153. expect(Module._nodeModulePaths(modulePath)).to.deep.equal([
  154. path.join(process.resourcesPath, 'node_modules')
  155. ]);
  156. modulePath = process.resourcesPath + '-foo';
  157. const nodeModulePaths = Module._nodeModulePaths(modulePath);
  158. expect(nodeModulePaths).to.include(path.join(modulePath, 'node_modules'));
  159. expect(nodeModulePaths).to.include(path.join(modulePath, '..', 'node_modules'));
  160. modulePath = path.join(process.resourcesPath, 'foo');
  161. expect(Module._nodeModulePaths(modulePath)).to.deep.equal([
  162. path.join(process.resourcesPath, 'foo', 'node_modules'),
  163. path.join(process.resourcesPath, 'node_modules')
  164. ]);
  165. modulePath = path.join(process.resourcesPath, 'node_modules', 'foo');
  166. expect(Module._nodeModulePaths(modulePath)).to.deep.equal([
  167. path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules'),
  168. path.join(process.resourcesPath, 'node_modules')
  169. ]);
  170. modulePath = path.join(process.resourcesPath, 'node_modules', 'foo', 'bar');
  171. expect(Module._nodeModulePaths(modulePath)).to.deep.equal([
  172. path.join(process.resourcesPath, 'node_modules', 'foo', 'bar', 'node_modules'),
  173. path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules'),
  174. path.join(process.resourcesPath, 'node_modules')
  175. ]);
  176. modulePath = path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules', 'bar');
  177. expect(Module._nodeModulePaths(modulePath)).to.deep.equal([
  178. path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules', 'bar', 'node_modules'),
  179. path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules'),
  180. path.join(process.resourcesPath, 'node_modules')
  181. ]);
  182. });
  183. });
  184. describe('when the path is outside the resources path', () => {
  185. it('includes paths outside of the resources path', () => {
  186. const modulePath = path.resolve('/foo');
  187. expect(Module._nodeModulePaths(modulePath)).to.deep.equal([
  188. path.join(modulePath, 'node_modules'),
  189. path.resolve('/node_modules')
  190. ]);
  191. });
  192. });
  193. });
  194. describe('require', () => {
  195. describe('when loaded URL is not file: protocol', () => {
  196. afterEach(closeAllWindows);
  197. it('searches for module under app directory', async () => {
  198. const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
  199. w.loadURL('about:blank');
  200. const result = await w.webContents.executeJavaScript('typeof require("q").when');
  201. expect(result).to.equal('function');
  202. });
  203. });
  204. });
  205. describe('esm', () => {
  206. it('can load the built-in "electron" module via ESM import', async () => {
  207. await expect(import('electron')).to.eventually.be.ok();
  208. });
  209. it('the built-in "electron" module loaded via ESM import has the same exports as the CJS module', async () => {
  210. const esmElectron = await import('electron');
  211. const cjsElectron = require('electron');
  212. expect(Object.keys(esmElectron)).to.deep.equal(Object.keys(cjsElectron));
  213. });
  214. });
  215. });