api-context-bridge-spec.ts 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. import { contextBridge, BrowserWindow, ipcMain } from 'electron'
  2. import { expect } from 'chai'
  3. import * as fs from 'fs-extra'
  4. import * as http from 'http'
  5. import { AddressInfo } from 'net'
  6. import * as os from 'os'
  7. import * as path from 'path'
  8. import { closeWindow } from './window-helpers'
  9. import { emittedOnce } from './events-helpers'
  10. const fixturesPath = path.resolve(__dirname, 'fixtures', 'api', 'context-bridge')
  11. describe('contextBridge', () => {
  12. let w: BrowserWindow
  13. let dir: string
  14. let server: http.Server
  15. before(async () => {
  16. server = http.createServer((req, res) => {
  17. res.setHeader('Content-Type', 'text/html')
  18. res.end('')
  19. })
  20. await new Promise(resolve => server.listen(0, resolve))
  21. })
  22. after(async () => {
  23. if (server) await new Promise(resolve => server.close(resolve))
  24. server = null as any
  25. })
  26. afterEach(async () => {
  27. await closeWindow(w)
  28. if (dir) await fs.remove(dir)
  29. })
  30. it('should not be accessible when contextIsolation is disabled', async () => {
  31. w = new BrowserWindow({
  32. show: false,
  33. webPreferences: {
  34. contextIsolation: false,
  35. preload: path.resolve(fixturesPath, 'can-bind-preload.js')
  36. }
  37. })
  38. const [,bound] = await emittedOnce(ipcMain, 'context-bridge-bound', () => w.loadFile(path.resolve(fixturesPath, 'empty.html')))
  39. expect(bound).to.equal(false)
  40. })
  41. it('should be accessible when contextIsolation is enabled', async () => {
  42. w = new BrowserWindow({
  43. show: false,
  44. webPreferences: {
  45. contextIsolation: true,
  46. preload: path.resolve(fixturesPath, 'can-bind-preload.js')
  47. }
  48. })
  49. const [,bound] = await emittedOnce(ipcMain, 'context-bridge-bound', () => w.loadFile(path.resolve(fixturesPath, 'empty.html')))
  50. expect(bound).to.equal(true)
  51. })
  52. const generateTests = (useSandbox: boolean) => {
  53. describe(`with sandbox=${useSandbox}`, () => {
  54. const makeBindingWindow = async (bindingCreator: Function) => {
  55. const preloadContent = `const electron_1 = require('electron');
  56. ${useSandbox ? '' : `require('v8').setFlagsFromString('--expose_gc');
  57. const gc=require('vm').runInNewContext('gc');
  58. electron_1.contextBridge.exposeInMainWorld('GCRunner', {
  59. run: () => gc()
  60. });`}
  61. (${bindingCreator.toString()})();`
  62. const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-spec-preload-'))
  63. dir = tmpDir
  64. await fs.writeFile(path.resolve(tmpDir, 'preload.js'), preloadContent)
  65. w = new BrowserWindow({
  66. show: false,
  67. webPreferences: {
  68. contextIsolation: true,
  69. nodeIntegration: true,
  70. sandbox: useSandbox,
  71. preload: path.resolve(tmpDir, 'preload.js')
  72. }
  73. })
  74. await w.loadURL(`http://127.0.0.1:${(server.address() as AddressInfo).port}`)
  75. }
  76. const callWithBindings = async (fn: Function) => {
  77. return await w.webContents.executeJavaScript(`(${fn.toString()})(window)`)
  78. }
  79. const getGCInfo = async (): Promise<{
  80. functionCount: number
  81. objectCount: number
  82. liveFromValues: number
  83. liveProxyValues: number
  84. }> => {
  85. const [,info] = await emittedOnce(ipcMain, 'gc-info', () => w.webContents.send('get-gc-info'))
  86. return info
  87. }
  88. it('should proxy numbers', async () => {
  89. await makeBindingWindow(() => {
  90. contextBridge.exposeInMainWorld('example', {
  91. myNumber: 123,
  92. })
  93. })
  94. const result = await callWithBindings((root: any) => {
  95. return root.example.myNumber
  96. })
  97. expect(result).to.equal(123)
  98. })
  99. it('should make properties unwriteable', async () => {
  100. await makeBindingWindow(() => {
  101. contextBridge.exposeInMainWorld('example', {
  102. myNumber: 123,
  103. })
  104. })
  105. const result = await callWithBindings((root: any) => {
  106. root.example.myNumber = 456
  107. return root.example.myNumber
  108. })
  109. expect(result).to.equal(123)
  110. })
  111. it('should proxy strings', async () => {
  112. await makeBindingWindow(() => {
  113. contextBridge.exposeInMainWorld('example', {
  114. myString: 'my-words',
  115. })
  116. })
  117. const result = await callWithBindings((root: any) => {
  118. return root.example.myString
  119. })
  120. expect(result).to.equal('my-words')
  121. })
  122. it('should proxy arrays', async () => {
  123. await makeBindingWindow(() => {
  124. contextBridge.exposeInMainWorld('example', {
  125. myArr: [123, 'my-words'],
  126. })
  127. })
  128. const result = await callWithBindings((root: any) => {
  129. return root.example.myArr
  130. })
  131. expect(result).to.deep.equal([123, 'my-words'])
  132. })
  133. it('should make arrays immutable', async () => {
  134. await makeBindingWindow(() => {
  135. contextBridge.exposeInMainWorld('example', {
  136. myArr: [123, 'my-words'],
  137. })
  138. })
  139. const immutable = await callWithBindings((root: any) => {
  140. try {
  141. root.example.myArr.push(456)
  142. return false
  143. } catch {
  144. return true
  145. }
  146. })
  147. expect(immutable).to.equal(true)
  148. })
  149. it('should proxy booleans', async () => {
  150. await makeBindingWindow(() => {
  151. contextBridge.exposeInMainWorld('example', {
  152. myBool: true,
  153. })
  154. })
  155. const result = await callWithBindings((root: any) => {
  156. return root.example.myBool
  157. })
  158. expect(result).to.equal(true)
  159. })
  160. it('should proxy promises and resolve with the correct value', async () => {
  161. await makeBindingWindow(() => {
  162. contextBridge.exposeInMainWorld('example', {
  163. myPromise: Promise.resolve('i-resolved'),
  164. })
  165. })
  166. const result = await callWithBindings(async (root: any) => {
  167. return await root.example.myPromise
  168. })
  169. expect(result).to.equal('i-resolved')
  170. })
  171. it('should proxy promises and reject with the correct value', async () => {
  172. await makeBindingWindow(() => {
  173. contextBridge.exposeInMainWorld('example', {
  174. myPromise: Promise.reject('i-rejected'),
  175. })
  176. })
  177. const result = await callWithBindings(async (root: any) => {
  178. try {
  179. await root.example.myPromise
  180. return null
  181. } catch (err) {
  182. return err
  183. }
  184. })
  185. expect(result).to.equal('i-rejected')
  186. })
  187. it('should proxy promises and resolve with the correct value if it resolves later', async () => {
  188. await makeBindingWindow(() => {
  189. contextBridge.exposeInMainWorld('example', {
  190. myPromise: () => new Promise(r => setTimeout(() => r('delayed'), 20)),
  191. })
  192. })
  193. const result = await callWithBindings(async (root: any) => {
  194. return await root.example.myPromise()
  195. })
  196. expect(result).to.equal('delayed')
  197. })
  198. it('should proxy nested promises correctly', async () => {
  199. await makeBindingWindow(() => {
  200. contextBridge.exposeInMainWorld('example', {
  201. myPromise: () => new Promise(r => setTimeout(() => r(Promise.resolve(123)), 20)),
  202. })
  203. })
  204. const result = await callWithBindings(async (root: any) => {
  205. return await root.example.myPromise()
  206. })
  207. expect(result).to.equal(123)
  208. })
  209. it('should proxy methods', async () => {
  210. await makeBindingWindow(() => {
  211. contextBridge.exposeInMainWorld('example', {
  212. getNumber: () => 123,
  213. getString: () => 'help',
  214. getBoolean: () => false,
  215. getPromise: async () => 'promise'
  216. })
  217. })
  218. const result = await callWithBindings(async (root: any) => {
  219. return [root.example.getNumber(), root.example.getString(), root.example.getBoolean(), await root.example.getPromise()]
  220. })
  221. expect(result).to.deep.equal([123, 'help', false, 'promise'])
  222. })
  223. it('should proxy methods that are callable multiple times', async () => {
  224. await makeBindingWindow(() => {
  225. contextBridge.exposeInMainWorld('example', {
  226. doThing: () => 123
  227. })
  228. })
  229. const result = await callWithBindings(async (root: any) => {
  230. return [root.example.doThing(), root.example.doThing(), root.example.doThing()]
  231. })
  232. expect(result).to.deep.equal([123, 123, 123])
  233. })
  234. it('should proxy methods in the reverse direction', async () => {
  235. await makeBindingWindow(() => {
  236. contextBridge.exposeInMainWorld('example', {
  237. callWithNumber: (fn: any) => fn(123),
  238. })
  239. })
  240. const result = await callWithBindings(async (root: any) => {
  241. return root.example.callWithNumber((n: number) => n + 1)
  242. })
  243. expect(result).to.equal(124)
  244. })
  245. it('should proxy promises in the reverse direction', async () => {
  246. await makeBindingWindow(() => {
  247. contextBridge.exposeInMainWorld('example', {
  248. getPromiseValue: async (p: Promise<any>) => await p,
  249. })
  250. })
  251. const result = await callWithBindings(async (root: any) => {
  252. return await root.example.getPromiseValue(Promise.resolve('my-proxied-value'))
  253. })
  254. expect(result).to.equal('my-proxied-value')
  255. })
  256. it('should proxy objects with number keys', async () => {
  257. await makeBindingWindow(() => {
  258. contextBridge.exposeInMainWorld('example', {
  259. [1]: 123,
  260. [2]: 456,
  261. '3': 789
  262. })
  263. })
  264. const result = await callWithBindings(async (root: any) => {
  265. return [root.example[1], root.example[2], root.example[3], Array.isArray(root.example)]
  266. })
  267. expect(result).to.deep.equal([123, 456, 789, false])
  268. })
  269. it('it should proxy null and undefined correctly', async () => {
  270. await makeBindingWindow(() => {
  271. contextBridge.exposeInMainWorld('example', {
  272. values: [null, undefined]
  273. })
  274. })
  275. const result = await callWithBindings((root: any) => {
  276. // Convert to strings as although the context bridge keeps the right value
  277. // IPC does not
  278. return root.example.values.map((val: any) => `${val}`)
  279. })
  280. expect(result).to.deep.equal(['null', 'undefined'])
  281. })
  282. it('should proxy typed arrays and regexps through the serializer', async () => {
  283. await makeBindingWindow(() => {
  284. contextBridge.exposeInMainWorld('example', {
  285. arr: new Uint8Array(100),
  286. regexp: /a/g
  287. })
  288. })
  289. const result = await callWithBindings((root: any) => {
  290. return [root.example.arr.__proto__ === Uint8Array.prototype, root.example.regexp.__proto__ === RegExp.prototype]
  291. })
  292. expect(result).to.deep.equal([true, true])
  293. })
  294. it('it should handle recursive objects', async () => {
  295. await makeBindingWindow(() => {
  296. const o: any = { value: 135 }
  297. o.o = o
  298. contextBridge.exposeInMainWorld('example', {
  299. o,
  300. })
  301. })
  302. const result = await callWithBindings((root: any) => {
  303. return [root.example.o.value, root.example.o.o.value, root.example.o.o.o.value]
  304. })
  305. expect(result).to.deep.equal([135, 135, 135])
  306. })
  307. it('it should follow expected simple rules of object identity', async () => {
  308. await makeBindingWindow(() => {
  309. const o: any = { value: 135 }
  310. const sub = { thing: 7 }
  311. o.a = sub
  312. o.b = sub
  313. contextBridge.exposeInMainWorld('example', {
  314. o,
  315. })
  316. })
  317. const result = await callWithBindings((root: any) => {
  318. return root.example.a === root.example.b
  319. })
  320. expect(result).to.equal(true)
  321. })
  322. it('it should follow expected complex rules of object identity', async () => {
  323. await makeBindingWindow(() => {
  324. let first: any = null
  325. contextBridge.exposeInMainWorld('example', {
  326. check: (arg: any) => {
  327. if (first === null) {
  328. first = arg
  329. } else {
  330. return first === arg
  331. }
  332. },
  333. })
  334. })
  335. const result = await callWithBindings((root: any) => {
  336. const o = { thing: 123 }
  337. root.example.check(o)
  338. return root.example.check(o)
  339. })
  340. expect(result).to.equal(true)
  341. })
  342. // Can only run tests which use the GCRunner in non-sandboxed environments
  343. if (!useSandbox) {
  344. it('should release the global hold on methods sent across contexts', async () => {
  345. await makeBindingWindow(() => {
  346. require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', (contextBridge as any).debugGC()))
  347. contextBridge.exposeInMainWorld('example', {
  348. getFunction: () => () => 123
  349. });
  350. });
  351. await callWithBindings(async (root: any) => {
  352. root.GCRunner.run();
  353. });
  354. const baseValue = (await getGCInfo()).functionCount;
  355. await callWithBindings(async (root: any) => {
  356. root.x = []
  357. root.x = [root.example.getFunction()];
  358. });
  359. expect((await getGCInfo()).functionCount).to.equal(baseValue + 1);
  360. await callWithBindings(async (root: any) => {
  361. root.x = [];
  362. root.GCRunner.run();
  363. });
  364. expect((await getGCInfo()).functionCount).to.equal(baseValue);
  365. });
  366. it('should release the global hold on objects sent across contexts when the object proxy is de-reffed', async () => {
  367. await makeBindingWindow(() => {
  368. require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', (contextBridge as any).debugGC()))
  369. let myObj: any
  370. contextBridge.exposeInMainWorld('example', {
  371. setObj: (o: any) => {
  372. myObj = o
  373. },
  374. getObj: () => myObj
  375. })
  376. })
  377. await callWithBindings(async (root: any) => {
  378. root.GCRunner.run()
  379. })
  380. // Initial Setup
  381. let info = await getGCInfo()
  382. const baseFrom = info.liveFromValues;
  383. const baseProxy = info.liveProxyValues;
  384. const baseCount = info.objectCount;
  385. // Create Reference
  386. await callWithBindings(async (root: any) => {
  387. root.x = { value: 123 }
  388. root.example.setObj(root.x)
  389. root.GCRunner.run()
  390. })
  391. info = await getGCInfo()
  392. expect(info.liveFromValues).to.equal(baseFrom + 1)
  393. expect(info.liveProxyValues).to.equal(baseProxy + 1)
  394. expect(info.objectCount).to.equal(baseCount + 2)
  395. // Release Reference
  396. await callWithBindings(async (root: any) => {
  397. root.example.setObj(null)
  398. root.GCRunner.run()
  399. })
  400. info = await getGCInfo()
  401. expect(info.liveFromValues).to.equal(baseFrom)
  402. expect(info.liveProxyValues).to.equal(baseProxy)
  403. expect(info.objectCount).to.equal(baseCount)
  404. })
  405. it('should release the global hold on objects sent across contexts when the object source is de-reffed', async () => {
  406. await makeBindingWindow(() => {
  407. require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', (contextBridge as any).debugGC()))
  408. let myObj: any;
  409. contextBridge.exposeInMainWorld('example', {
  410. setObj: (o: any) => {
  411. myObj = o
  412. },
  413. getObj: () => myObj
  414. })
  415. })
  416. await callWithBindings(async (root: any) => {
  417. root.GCRunner.run()
  418. })
  419. // Initial Setup
  420. let info = await getGCInfo()
  421. const baseFrom = info.liveFromValues;
  422. const baseProxy = info.liveProxyValues;
  423. const baseCount = info.objectCount;
  424. // Create Reference
  425. await callWithBindings(async (root: any) => {
  426. root.x = { value: 123 }
  427. root.example.setObj(root.x)
  428. root.GCRunner.run()
  429. })
  430. info = await getGCInfo()
  431. expect(info.liveFromValues).to.equal(baseFrom + 1)
  432. expect(info.liveProxyValues).to.equal(baseProxy + 1)
  433. expect(info.objectCount).to.equal(baseCount + 2)
  434. // Release Reference
  435. await callWithBindings(async (root: any) => {
  436. delete root.x
  437. root.GCRunner.run()
  438. })
  439. info = await getGCInfo()
  440. expect(info.liveFromValues).to.equal(baseFrom)
  441. expect(info.liveProxyValues).to.equal(baseProxy)
  442. expect(info.objectCount).to.equal(baseCount)
  443. })
  444. it('should not crash when the object source is de-reffed AND the object proxy is de-reffed', async () => {
  445. await makeBindingWindow(() => {
  446. require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', (contextBridge as any).debugGC()))
  447. let myObj: any;
  448. contextBridge.exposeInMainWorld('example', {
  449. setObj: (o: any) => {
  450. myObj = o
  451. },
  452. getObj: () => myObj
  453. })
  454. })
  455. await callWithBindings(async (root: any) => {
  456. root.GCRunner.run()
  457. })
  458. // Initial Setup
  459. let info = await getGCInfo()
  460. const baseFrom = info.liveFromValues;
  461. const baseProxy = info.liveProxyValues;
  462. const baseCount = info.objectCount;
  463. // Create Reference
  464. await callWithBindings(async (root: any) => {
  465. root.x = { value: 123 }
  466. root.example.setObj(root.x)
  467. root.GCRunner.run()
  468. })
  469. info = await getGCInfo()
  470. expect(info.liveFromValues).to.equal(baseFrom + 1)
  471. expect(info.liveProxyValues).to.equal(baseProxy + 1)
  472. expect(info.objectCount).to.equal(baseCount + 2)
  473. // Release Reference
  474. await callWithBindings(async (root: any) => {
  475. delete root.x
  476. root.example.setObj(null)
  477. root.GCRunner.run()
  478. })
  479. info = await getGCInfo()
  480. expect(info.liveFromValues).to.equal(baseFrom)
  481. expect(info.liveProxyValues).to.equal(baseProxy)
  482. expect(info.objectCount).to.equal(baseCount)
  483. })
  484. }
  485. if (useSandbox) {
  486. it('should not leak the global hold on methods sent across contexts when reloading a sandboxed renderer', async () => {
  487. await makeBindingWindow(() => {
  488. require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', (contextBridge as any).debugGC()))
  489. contextBridge.exposeInMainWorld('example', {
  490. getFunction: () => () => 123
  491. });
  492. require('electron').ipcRenderer.send('window-ready-for-tasking');
  493. });
  494. const loadPromise = emittedOnce(ipcMain, 'window-ready-for-tasking');
  495. const baseValue = (await getGCInfo()).functionCount;
  496. await callWithBindings((root: any) => {
  497. root.location.reload()
  498. })
  499. await loadPromise
  500. // If this is ever "2" it means we leaked the exposed function and
  501. // therefore the entire context after a reload
  502. expect((await getGCInfo()).functionCount).to.equal(baseValue);
  503. });
  504. }
  505. it('it should not let you overwrite existing exposed things', async () => {
  506. await makeBindingWindow(() => {
  507. let threw = false
  508. contextBridge.exposeInMainWorld('example', {
  509. attempt: 1,
  510. getThrew: () => threw
  511. })
  512. try {
  513. contextBridge.exposeInMainWorld('example', {
  514. attempt: 2,
  515. getThrew: () => threw
  516. })
  517. } catch {
  518. threw = true
  519. }
  520. })
  521. const result = await callWithBindings((root: any) => {
  522. return [root.example.attempt, root.example.getThrew()]
  523. })
  524. expect(result).to.deep.equal([1, true])
  525. })
  526. it('should work with complex nested methods and promises', async () => {
  527. await makeBindingWindow(() => {
  528. contextBridge.exposeInMainWorld('example', {
  529. first: (second: Function) => second(async (fourth: Function) => {
  530. return await fourth()
  531. })
  532. })
  533. })
  534. const result = await callWithBindings((root: any) => {
  535. return root.example.first((third: Function) => {
  536. return third(() => Promise.resolve('final value'))
  537. })
  538. })
  539. expect(result).to.equal('final value')
  540. })
  541. it('should throw an error when recursion depth is exceeded', async () => {
  542. await makeBindingWindow(() => {
  543. contextBridge.exposeInMainWorld('example', {
  544. doThing: (a: any) => console.log(a)
  545. })
  546. })
  547. let threw = await callWithBindings((root: any) => {
  548. try {
  549. let a: any = []
  550. for (let i = 0; i < 999; i++) {
  551. a = [ a ]
  552. }
  553. root.example.doThing(a)
  554. return false
  555. } catch {
  556. return true
  557. }
  558. })
  559. expect(threw).to.equal(false)
  560. threw = await callWithBindings((root: any) => {
  561. try {
  562. let a: any = []
  563. for (let i = 0; i < 1000; i++) {
  564. a = [ a ]
  565. }
  566. root.example.doThing(a)
  567. return false
  568. } catch {
  569. return true
  570. }
  571. })
  572. expect(threw).to.equal(true)
  573. })
  574. it('should not leak prototypes', async () => {
  575. await makeBindingWindow(() => {
  576. contextBridge.exposeInMainWorld('example', {
  577. number: 123,
  578. string: 'string',
  579. boolean: true,
  580. arr: [123, 'string', true, ['foo']],
  581. getObject: () => ({ thing: 123 }),
  582. getNumber: () => 123,
  583. getString: () => 'string',
  584. getBoolean: () => true,
  585. getArr: () => [123, 'string', true, ['foo']],
  586. getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']]}),
  587. getFunctionFromFunction: async () => () => null,
  588. object: {
  589. number: 123,
  590. string: 'string',
  591. boolean: true,
  592. arr: [123, 'string', true, ['foo']],
  593. getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']]}),
  594. },
  595. receiveArguments: (fn: any) => fn({ key: 'value' })
  596. })
  597. })
  598. const result = await callWithBindings(async (root: any) => {
  599. const { example } = root
  600. let arg: any
  601. example.receiveArguments((o: any) => { arg = o })
  602. const protoChecks = [
  603. [example, Object],
  604. [example.number, Number],
  605. [example.string, String],
  606. [example.boolean, Boolean],
  607. [example.arr, Array],
  608. [example.arr[0], Number],
  609. [example.arr[1], String],
  610. [example.arr[2], Boolean],
  611. [example.arr[3], Array],
  612. [example.arr[3][0], String],
  613. [example.getNumber, Function],
  614. [example.getNumber(), Number],
  615. [example.getObject(), Object],
  616. [example.getString(), String],
  617. [example.getBoolean(), Boolean],
  618. [example.getArr(), Array],
  619. [example.getArr()[0], Number],
  620. [example.getArr()[1], String],
  621. [example.getArr()[2], Boolean],
  622. [example.getArr()[3], Array],
  623. [example.getArr()[3][0], String],
  624. [example.getFunctionFromFunction, Function],
  625. [example.getFunctionFromFunction(), Promise],
  626. [await example.getFunctionFromFunction(), Function],
  627. [example.getPromise(), Promise],
  628. [await example.getPromise(), Object],
  629. [(await example.getPromise()).number, Number],
  630. [(await example.getPromise()).string, String],
  631. [(await example.getPromise()).boolean, Boolean],
  632. [(await example.getPromise()).fn, Function],
  633. [(await example.getPromise()).fn(), String],
  634. [(await example.getPromise()).arr, Array],
  635. [(await example.getPromise()).arr[0], Number],
  636. [(await example.getPromise()).arr[1], String],
  637. [(await example.getPromise()).arr[2], Boolean],
  638. [(await example.getPromise()).arr[3], Array],
  639. [(await example.getPromise()).arr[3][0], String],
  640. [example.object, Object],
  641. [example.object.number, Number],
  642. [example.object.string, String],
  643. [example.object.boolean, Boolean],
  644. [example.object.arr, Array],
  645. [example.object.arr[0], Number],
  646. [example.object.arr[1], String],
  647. [example.object.arr[2], Boolean],
  648. [example.object.arr[3], Array],
  649. [example.object.arr[3][0], String],
  650. [await example.object.getPromise(), Object],
  651. [(await example.object.getPromise()).number, Number],
  652. [(await example.object.getPromise()).string, String],
  653. [(await example.object.getPromise()).boolean, Boolean],
  654. [(await example.object.getPromise()).fn, Function],
  655. [(await example.object.getPromise()).fn(), String],
  656. [(await example.object.getPromise()).arr, Array],
  657. [(await example.object.getPromise()).arr[0], Number],
  658. [(await example.object.getPromise()).arr[1], String],
  659. [(await example.object.getPromise()).arr[2], Boolean],
  660. [(await example.object.getPromise()).arr[3], Array],
  661. [(await example.object.getPromise()).arr[3][0], String],
  662. [arg, Object],
  663. [arg.key, String]
  664. ]
  665. return {
  666. protoMatches: protoChecks.map(([a, Constructor]) => a.__proto__ === Constructor.prototype)
  667. }
  668. })
  669. // Every protomatch should be true
  670. expect(result.protoMatches).to.deep.equal(result.protoMatches.map(() => true))
  671. })
  672. })
  673. }
  674. generateTests(true)
  675. generateTests(false)
  676. })