Browse Source

chore: move ts-smoke tests to core (#16930)

* chore: move ts smoke tests to core

* fix: fix paths for tsconfig / ts-smoke runner

* update ts-defs version

* do not lintr
Shelley Vohr 6 years ago
parent
commit
319c2853df

+ 2 - 0
docs/api/global-shortcut.md

@@ -47,6 +47,8 @@ The `globalShortcut` module has the following methods:
 * `accelerator` [Accelerator](accelerator.md)
 * `callback` Function
 
+Returns `Boolean` - Whether or not the shortcut was registered successfully.
+
 Registers a global shortcut of `accelerator`. The `callback` is called when
 the registered shortcut is pressed by the user.
 

+ 2 - 2
package.json

@@ -15,7 +15,7 @@
     "dotenv-safe": "^4.0.4",
     "dugite": "^1.45.0",
     "electron-docs-linter": "^2.4.0",
-    "electron-typescript-definitions": "^6.0.0",
+    "electron-typescript-definitions": "^7.0.0",
     "eslint": "^5.13.0",
     "eslint-config-standard": "^12.0.0",
     "eslint-plugin-mocha": "^5.2.0",
@@ -57,7 +57,7 @@
     "lint:docs-relative-links": "python ./script/check-relative-doc-links.py",
     "lint:js-in-markdown": "standard-markdown docs",
     "create-api-json": "electron-docs-linter docs --outfile=electron-api.json",
-    "create-typescript-definitions": "npm run create-api-json && electron-typescript-definitions --in=electron-api.json --out=electron.d.ts",
+    "create-typescript-definitions": "npm run create-api-json && electron-typescript-definitions --in=electron-api.json --out=electron.d.ts && node spec/ts-smoke/runner.js",
     "gn-typescript-definitions": "npm run create-typescript-definitions && cp electron.d.ts",
     "preinstall": "node -e 'process.exit(0)'",
     "precommit": "lint-staged",

+ 4 - 1
script/lint.js

@@ -29,7 +29,10 @@ const BLACKLIST = new Set([
   ['atom', 'common', 'common_message_generator.cc'],
   ['atom', 'common', 'common_message_generator.h'],
   ['atom', 'common', 'node_includes.h'],
-  ['spec', 'static', 'jquery-2.0.3.min.js']
+  ['spec', 'static', 'jquery-2.0.3.min.js'],
+  ['spec', 'ts-smoke', 'electron', 'main.ts'],
+  ['spec', 'ts-smoke', 'electron', 'renderer.ts'],
+  ['spec', 'ts-smoke', 'runner.js']
 ].map(tokens => path.join(SOURCE_ROOT, ...tokens)))
 
 function spawnAndCheckExitCode (cmd, args, opts) {

+ 1159 - 0
spec/ts-smoke/electron/main.ts

@@ -0,0 +1,1159 @@
+// tslint:disable:ordered-imports curly no-console no-angle-bracket-type-assertion object-literal-sort-keys only-arrow-functions
+
+import {
+  app,
+  autoUpdater,
+  BrowserWindow,
+  contentTracing,
+  dialog,
+  globalShortcut,
+  ipcMain,
+  Menu,
+  MenuItem,
+  net,
+  powerMonitor,
+  powerSaveBlocker,
+  protocol,
+  Tray,
+  clipboard,
+  crashReporter,
+  nativeImage,
+  screen,
+  shell,
+  session,
+  systemPreferences,
+  webContents,
+  Event,
+  TouchBar
+} from 'electron'
+
+import * as path from 'path'
+
+// Quick start
+// https://github.com/atom/electron/blob/master/docs/tutorial/quick-start.md
+
+// Keep a global reference of the window object, if you don't, the window will
+// be closed automatically when the javascript object is GCed.
+let mainWindow: Electron.BrowserWindow = null
+const mainWindow2: BrowserWindow = null
+
+// Quit when all windows are closed.
+app.on('window-all-closed', () => {
+  if (process.platform !== 'darwin') {
+    app.quit()
+  }
+})
+
+// Check single instance app
+const gotLock = app.requestSingleInstanceLock()
+
+if (!gotLock) {
+  app.quit()
+  process.exit(0)
+}
+
+// This method will be called when Electron has done everything
+// initialization and ready for creating browser windows.
+app.on('ready', () => {
+  // Create the browser window.
+  mainWindow = new BrowserWindow({ width: 800, height: 600 })
+
+  // and load the index.html of the app.
+  mainWindow.loadURL(`file://${__dirname}/index.html`)
+  mainWindow.loadURL('file://foo/bar', { userAgent: 'cool-agent', httpReferrer: 'greateRefferer' })
+  mainWindow.webContents.loadURL('file://foo/bar', { userAgent: 'cool-agent', httpReferrer: 'greateRefferer' })
+  mainWindow.webContents.loadURL('file://foo/bar', { userAgent: 'cool-agent', httpReferrer: 'greateRefferer', postData: [{ type: 'blob', blobUUID: 'hogefuga' }] })
+
+  mainWindow.webContents.openDevTools()
+  mainWindow.webContents.toggleDevTools()
+  mainWindow.webContents.openDevTools({ mode: 'detach' })
+  mainWindow.webContents.closeDevTools()
+  mainWindow.webContents.addWorkSpace('/path/to/workspace')
+  mainWindow.webContents.removeWorkSpace('/path/to/workspace')
+  const opened: boolean = mainWindow.webContents.isDevToolsOpened()
+  const focused = mainWindow.webContents.isDevToolsFocused()
+  // Emitted when the window is closed.
+  mainWindow.on('closed', () => {
+    // Dereference the window object, usually you would store windows
+    // in an array if your app supports multi windows, this is the time
+    // when you should delete the corresponding element.
+    mainWindow = null
+  })
+
+  mainWindow.webContents.setVisualZoomLevelLimits(50, 200)
+  mainWindow.webContents.setLayoutZoomLevelLimits(50, 200)
+
+  mainWindow.webContents.print({ silent: true, printBackground: false })
+  mainWindow.webContents.print()
+
+  mainWindow.webContents.printToPDF({
+    marginsType: 1,
+    pageSize: 'A3',
+    printBackground: true,
+    printSelectionOnly: true,
+    landscape: true
+  }, (error: Error, data: Buffer) => console.log(error))
+
+  mainWindow.webContents.printToPDF({}, (err, data) => console.log(err))
+
+  mainWindow.webContents.executeJavaScript('return true;').then((v: boolean) => console.log(v))
+  mainWindow.webContents.executeJavaScript('return true;', true)
+  mainWindow.webContents.executeJavaScript('return true;', true, (result: boolean) => console.log(result))
+  mainWindow.webContents.insertText('blah, blah, blah')
+  mainWindow.webContents.startDrag({ file: '/path/to/img.png', icon: nativeImage.createFromPath('/path/to/icon.png') })
+  mainWindow.webContents.findInPage('blah')
+  mainWindow.webContents.findInPage('blah', {
+    forward: true,
+    matchCase: false
+  })
+  mainWindow.webContents.stopFindInPage('clearSelection')
+  mainWindow.webContents.stopFindInPage('keepSelection')
+  mainWindow.webContents.stopFindInPage('activateSelection')
+
+  mainWindow.loadURL('https://github.com')
+
+  mainWindow.webContents.on('did-finish-load', function () {
+    mainWindow.webContents.savePage('/tmp/test.html', 'HTMLComplete', function (error) {
+      if (!error) { console.log('Save page successfully') }
+    })
+  })
+
+  try {
+    mainWindow.webContents.debugger.attach('1.1')
+  } catch (err) {
+    console.log('Debugger attach failed : ', err)
+  }
+
+  mainWindow.webContents.debugger.on('detach', function (event, reason) {
+    console.log('Debugger detached due to : ', reason)
+  })
+
+  mainWindow.webContents.debugger.on('message', function (event, method, params) {
+    if (method === 'Network.requestWillBeSent') {
+      if (params.request.url === 'https://www.github.com') {
+        mainWindow.webContents.debugger.detach()
+      }
+    }
+  })
+
+  mainWindow.webContents.debugger.sendCommand('Network.enable')
+  mainWindow.webContents.capturePage((image) => {
+    console.log(image.toDataURL())
+  })
+  mainWindow.webContents.capturePage({ x: 0, y: 0, width: 100, height: 200 }, (image) => {
+    console.log(image.toPNG())
+  })
+})
+
+app.commandLine.appendSwitch('enable-web-bluetooth')
+
+app.on('ready', () => {
+  mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
+    event.preventDefault()
+
+    const result = (() => {
+      for (const device of deviceList) {
+        if (device.deviceName === 'test') {
+          return device
+        }
+      }
+      return null
+    })()
+
+    if (!result) {
+      callback('')
+    } else {
+      callback(result.deviceId)
+    }
+  })
+})
+
+// Locale
+app.getLocale()
+
+// Desktop environment integration
+// https://github.com/atom/electron/blob/master/docs/tutorial/desktop-environment-integration.md
+
+app.addRecentDocument('/Users/USERNAME/Desktop/work.type')
+app.clearRecentDocuments()
+const dockMenu = Menu.buildFromTemplate([
+  <Electron.MenuItemConstructorOptions> {
+    label: 'New Window',
+    click: () => {
+      console.log('New Window')
+    }
+  },
+  <Electron.MenuItemConstructorOptions> {
+    label: 'New Window with Settings',
+    submenu: [
+      <Electron.MenuItemConstructorOptions> { label: 'Basic' },
+      <Electron.MenuItemConstructorOptions> { label: 'Pro' }
+    ]
+  },
+  <Electron.MenuItemConstructorOptions> { label: 'New Command...' },
+  <Electron.MenuItemConstructorOptions> {
+    label: 'Edit',
+    submenu: [
+      {
+        label: 'Undo',
+        accelerator: 'CmdOrCtrl+Z',
+        role: 'undo'
+      },
+      {
+        label: 'Redo',
+        accelerator: 'Shift+CmdOrCtrl+Z',
+        role: 'redo'
+      },
+      {
+        type: 'separator'
+      },
+      {
+        label: 'Cut',
+        accelerator: 'CmdOrCtrl+X',
+        role: 'cut'
+      },
+      {
+        label: 'Copy',
+        accelerator: 'CmdOrCtrl+C',
+        role: 'copy'
+      },
+      {
+        label: 'Paste',
+        accelerator: 'CmdOrCtrl+V',
+        role: 'paste'
+      }
+    ]
+  }
+])
+app.dock.setMenu(dockMenu)
+app.dock.setBadge('foo')
+const dockid = app.dock.bounce('informational')
+app.dock.cancelBounce(dockid)
+app.dock.setIcon('/path/to/icon.png')
+
+app.setBadgeCount(app.getBadgeCount() + 1)
+
+app.setUserTasks([
+  <Electron.Task> {
+    program: process.execPath,
+    arguments: '--new-window',
+    iconPath: process.execPath,
+    iconIndex: 0,
+    title: 'New Window',
+    description: 'Create a new window'
+  }
+])
+app.setUserTasks([])
+
+app.setJumpList([
+  {
+    type: 'custom',
+    name: 'Recent Projects',
+    items: [
+      { type: 'file', path: 'C:\\Projects\\project1.proj' },
+      { type: 'file', path: 'C:\\Projects\\project2.proj' }
+    ]
+  },
+  { // has a name so type is assumed to be "custom"
+    name: 'Tools',
+    items: [
+      {
+        type: 'task',
+        title: 'Tool A',
+        program: process.execPath,
+        args: '--run-tool-a',
+        iconPath: process.execPath,
+        iconIndex: 0,
+        description: 'Runs Tool A'
+      },
+      {
+        type: 'task',
+        title: 'Tool B',
+        program: process.execPath,
+        args: '--run-tool-b',
+        iconPath: process.execPath,
+        iconIndex: 0,
+        description: 'Runs Tool B'
+      }]
+  },
+  {
+    type: 'frequent'
+  },
+  { // has no name and no type so type is assumed to be "tasks"
+    items: [
+      {
+        type: 'task',
+        title: 'New Project',
+        program: process.execPath,
+        args: '--new-project',
+        description: 'Create a new project.'
+      },
+      {
+        type: 'separator'
+      },
+      {
+        type: 'task',
+        title: 'Recover Project',
+        program: process.execPath,
+        args: '--recover-project',
+        description: 'Recover Project'
+      }]
+  }
+])
+
+if (app.isUnityRunning()) {
+  console.log('unity running')
+}
+if (app.isAccessibilitySupportEnabled()) {
+  console.log('a11y running')
+}
+app.setLoginItemSettings({ openAtLogin: true, openAsHidden: false })
+console.log(app.getLoginItemSettings().wasOpenedAtLogin)
+app.setAboutPanelOptions({
+  applicationName: 'Test',
+  version: '1.2.3'
+})
+
+let window = new BrowserWindow()
+window.setProgressBar(0.5)
+window.setRepresentedFilename('/etc/passwd')
+window.setDocumentEdited(true)
+window.previewFile('/path/to/file')
+window.previewFile('/path/to/file', 'Displayed Name')
+window.setVibrancy('light')
+window.setVibrancy('titlebar')
+
+// Online/Offline Event Detection
+// https://github.com/atom/electron/blob/master/docs/tutorial/online-offline-events.md
+
+let onlineStatusWindow: Electron.BrowserWindow
+
+app.on('ready', () => {
+  onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false, vibrancy: 'sidebar' })
+  onlineStatusWindow.loadURL(`file://${__dirname}/online-status.html`)
+})
+app.on('accessibility-support-changed', (_, enabled) => console.log('accessibility: ' + enabled))
+
+ipcMain.on('online-status-changed', (event: any, status: any) => {
+  console.log(status)
+})
+
+// Synopsis
+// https://github.com/atom/electron/blob/master/docs/api/synopsis.md
+
+app.on('ready', () => {
+  window = new BrowserWindow({
+    width: 800,
+    height: 600,
+    titleBarStyle: 'hiddenInset'
+  })
+  window.loadURL('https://github.com')
+})
+
+// Supported Chrome command line switches
+// https://github.com/atom/electron/blob/master/docs/api/chrome-command-line-switches.md
+
+app.commandLine.appendSwitch('remote-debugging-port', '8315')
+app.commandLine.appendSwitch('host-rules', 'MAP * 127.0.0.1')
+app.commandLine.appendSwitch('vmodule', 'console=0')
+
+// systemPreferences
+// https://github.com/electron/electron/blob/master/docs/api/system-preferences.md
+
+const browserOptions = {
+  width: 1000,
+  height: 800,
+  transparent: false,
+  frame: true
+}
+
+// Make the window transparent only if the platform supports it.
+if (process.platform !== 'win32' || systemPreferences.isAeroGlassEnabled()) {
+  browserOptions.transparent = true
+  browserOptions.frame = false
+}
+
+if (process.platform === 'win32') {
+  systemPreferences.on('color-changed', () => { console.log('color changed') })
+  systemPreferences.on('inverted-color-scheme-changed', (_, inverted) => console.log(inverted ? 'inverted' : 'not inverted'))
+  console.log('Color for menu is', systemPreferences.getColor('menu'))
+}
+
+// Create the window.
+const win1 = new BrowserWindow(browserOptions)
+
+// Navigate.
+if (browserOptions.transparent) {
+  win1.loadURL('file://' + __dirname + '/index.html')
+} else {
+  // No transparency, so we load a fallback that uses basic styles.
+  win1.loadURL('file://' + __dirname + '/fallback.html')
+}
+
+// app
+// https://github.com/atom/electron/blob/master/docs/api/app.md
+
+app.on('certificate-error', function (event, webContents, url, error, certificate, callback) {
+  if (url === 'https://github.com') {
+    // Verification logic.
+    event.preventDefault()
+    callback(true)
+  } else {
+    callback(false)
+  }
+})
+
+app.on('select-client-certificate', function (event, webContents, url, list, callback) {
+  event.preventDefault()
+  callback(list[0])
+})
+
+app.on('login', function (event, webContents, request, authInfo, callback) {
+  event.preventDefault()
+  callback('username', 'secret')
+})
+
+const win2 = new BrowserWindow({ show: false })
+win2.once('ready-to-show', () => {
+  win2.show()
+})
+
+app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) })
+app.exit(0)
+
+// auto-updater
+// https://github.com/atom/electron/blob/master/docs/api/auto-updater.md
+
+autoUpdater.setFeedURL({
+  url: 'http://mycompany.com/myapp/latest?version=' + app.getVersion(),
+  headers: {
+    key: 'value'
+  },
+  serverType: 'default'
+})
+autoUpdater.checkForUpdates()
+autoUpdater.quitAndInstall()
+
+autoUpdater.on('error', (error) => {
+  console.log('error', error)
+})
+
+autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName, releaseDate, updateURL) => {
+  console.log('update-downloaded', releaseNotes, releaseName, releaseDate, updateURL)
+})
+
+// browser-window
+// https://github.com/atom/electron/blob/master/docs/api/browser-window.md
+
+let win3 = new BrowserWindow({ width: 800, height: 600, show: false })
+win3.on('closed', () => {
+  win3 = null
+})
+
+win3.loadURL('https://github.com')
+win3.show()
+
+const toolbarRect = document.getElementById('toolbar').getBoundingClientRect()
+win3.setSheetOffset(toolbarRect.height)
+
+const installed = BrowserWindow.getDevToolsExtensions().hasOwnProperty('devtron')
+
+// content-tracing
+// https://github.com/atom/electron/blob/master/docs/api/content-tracing.md
+
+const options = {
+  categoryFilter: '*',
+  traceOptions: 'record-until-full,enable-sampling'
+}
+
+contentTracing.startRecording(options, function () {
+  console.log('Tracing started')
+  setTimeout(function () {
+    contentTracing.stopRecording('', function (path) {
+      console.log('Tracing data recorded to ' + path)
+    })
+  }, 5000)
+})
+
+// dialog
+// https://github.com/atom/electron/blob/master/docs/api/dialog.md
+
+// variant without browserWindow
+let openDialogResult: string[] = dialog.showOpenDialog({
+  title: 'Testing showOpenDialog',
+  defaultPath: '/var/log/syslog',
+  filters: [{ name: '', extensions: [''] }],
+  properties: ['openFile', 'openDirectory', 'multiSelections']
+})
+
+// variant with browserWindow
+openDialogResult = dialog.showOpenDialog(win3, {
+  title: 'Testing showOpenDialog',
+  defaultPath: '/var/log/syslog',
+  filters: [{ name: '', extensions: [''] }],
+  properties: ['openFile', 'openDirectory', 'multiSelections']
+})
+
+// global-shortcut
+// https://github.com/atom/electron/blob/master/docs/api/global-shortcut.md
+
+// Register a 'ctrl+x' shortcut listener.
+const ret = globalShortcut.register('ctrl+x', () => {
+  console.log('ctrl+x is pressed')
+})
+if (!ret) { console.log('registerion fails') }
+
+// Check whether a shortcut is registered.
+console.log(globalShortcut.isRegistered('ctrl+x'))
+
+// Unregister a shortcut.
+globalShortcut.unregister('ctrl+x')
+
+// Unregister all shortcuts.
+globalShortcut.unregisterAll()
+
+// ipcMain
+// https://github.com/atom/electron/blob/master/docs/api/ipc-main-process.md
+
+ipcMain.on('asynchronous-message', (event: Electron.Event, arg: any) => {
+  console.log(arg) // prints "ping"
+  event.sender.send('asynchronous-reply', 'pong')
+})
+
+ipcMain.on('synchronous-message', (event: Electron.Event, arg: any) => {
+  console.log(arg) // prints "ping"
+  event.returnValue = 'pong'
+})
+
+ipcMain.on('synchronous-message', (event: Event, arg: any) => {
+  console.log("event isn't namespaced and refers to the correct type.")
+  event.returnValue = 'pong'
+})
+
+const winWindows = new BrowserWindow({
+  width: 800,
+  height: 600,
+  show: false,
+  thickFrame: false,
+  type: 'toolbar'
+})
+
+// menu-item
+// https://github.com/atom/electron/blob/master/docs/api/menu-item.md
+
+const menuItem = new MenuItem({})
+
+menuItem.label = 'Hello World!'
+menuItem.click = (passedMenuItem: Electron.MenuItem, browserWindow: Electron.BrowserWindow) => {
+  console.log('click', passedMenuItem, browserWindow)
+}
+
+// menu
+// https://github.com/atom/electron/blob/master/docs/api/menu.md
+
+let menu = new Menu()
+menu.append(new MenuItem({ label: 'MenuItem1', click: () => { console.log('item 1 clicked') } }))
+menu.append(new MenuItem({ type: 'separator' }))
+menu.append(new MenuItem({ label: 'MenuItem2', type: 'checkbox', checked: true }))
+menu.insert(0, menuItem)
+
+console.log(menu.items)
+
+const pos = screen.getCursorScreenPoint()
+menu.popup({ x: pos.x, y: pos.y })
+
+// main.js
+const template = <Electron.MenuItemConstructorOptions[]> [
+  {
+    label: 'Electron',
+    submenu: [
+      {
+        label: 'About Electron',
+        role: 'about'
+      },
+      {
+        type: 'separator'
+      },
+      {
+        label: 'Services',
+        role: 'services',
+        submenu: []
+      },
+      {
+        type: 'separator'
+      },
+      {
+        label: 'Hide Electron',
+        accelerator: 'Command+H',
+        role: 'hide'
+      },
+      {
+        label: 'Hide Others',
+        accelerator: 'Command+Shift+H',
+        role: 'hideothers'
+      },
+      {
+        label: 'Show All',
+        role: 'unhide'
+      },
+      {
+        type: 'separator'
+      },
+      {
+        label: 'Quit',
+        accelerator: 'Command+Q',
+        click: () => { app.quit() }
+      }
+    ]
+  },
+  {
+    label: 'Edit',
+    submenu: [
+      {
+        label: 'Undo',
+        accelerator: 'Command+Z',
+        role: 'undo'
+      },
+      {
+        label: 'Redo',
+        accelerator: 'Shift+Command+Z',
+        role: 'redo'
+      },
+      {
+        type: 'separator'
+      },
+      {
+        label: 'Cut',
+        accelerator: 'Command+X',
+        role: 'cut'
+      },
+      {
+        label: 'Copy',
+        accelerator: 'Command+C',
+        role: 'copy'
+      },
+      {
+        label: 'Paste',
+        accelerator: 'Command+V',
+        role: 'paste'
+      },
+      {
+        label: 'Select All',
+        accelerator: 'Command+A',
+        role: 'selectall'
+      }
+    ]
+  },
+  {
+    label: 'View',
+    submenu: [
+      {
+        label: 'Reload',
+        accelerator: 'Command+R',
+        click: (item, focusedWindow) => {
+          if (focusedWindow) {
+            focusedWindow.webContents.reloadIgnoringCache()
+          }
+        }
+      },
+      {
+        label: 'Toggle DevTools',
+        accelerator: 'Alt+Command+I',
+        click: (item, focusedWindow) => {
+          if (focusedWindow) {
+            focusedWindow.webContents.toggleDevTools()
+          }
+        }
+      },
+      {
+        type: 'separator'
+      },
+      {
+        label: 'Actual Size',
+        accelerator: 'CmdOrCtrl+0',
+        click: (item, focusedWindow) => {
+          if (focusedWindow) {
+            focusedWindow.webContents.setZoomLevel(0)
+          }
+        }
+      },
+      {
+        label: 'Zoom In',
+        accelerator: 'CmdOrCtrl+Plus',
+        click: (item, focusedWindow) => {
+          if (focusedWindow) {
+            const { webContents } = focusedWindow
+            const zoomLevel = webContents.getZoomLevel()
+            webContents.setZoomLevel(zoomLevel + 0.5)
+          }
+        }
+      },
+      {
+        label: 'Zoom Out',
+        accelerator: 'CmdOrCtrl+-',
+        click: (item, focusedWindow) => {
+          if (focusedWindow) {
+            const { webContents } = focusedWindow
+            const zoomLevel = webContents.getZoomLevel()
+            webContents.setZoomLevel(zoomLevel - 0.5)
+          }
+        }
+      }
+    ]
+  },
+  {
+    label: 'Window',
+    submenu: [
+      {
+        label: 'Minimize',
+        accelerator: 'Command+M',
+        role: 'minimize'
+      },
+      {
+        label: 'Close',
+        accelerator: 'Command+W',
+        role: 'close'
+      },
+      {
+        type: 'separator'
+      },
+      {
+        label: 'Bring All to Front',
+        role: 'front'
+      }
+    ]
+  },
+  {
+    label: 'Help',
+    submenu: []
+  }
+]
+
+menu = Menu.buildFromTemplate(template)
+
+Menu.setApplicationMenu(menu) // Must be called within app.on('ready', function(){ ... });
+
+Menu.buildFromTemplate([
+  { label: '4', id: '4' },
+  { label: '5', id: '5', after: ['4'] },
+  { label: '1', id: '1', before: ['4'] },
+  { label: '2', id: '2' },
+  { label: '3', id: '3' }
+])
+
+Menu.buildFromTemplate([
+  { label: 'a' },
+  { label: '1' },
+  { label: 'b' },
+  { label: '2' },
+  { label: 'c' },
+  { label: '3' }
+])
+
+// net
+// https://github.com/electron/electron/blob/master/docs/api/net.md
+
+app.on('ready', () => {
+  const request = net.request('https://github.com')
+  request.setHeader('Some-Custom-Header-Name', 'Some-Custom-Header-Value')
+  const header = request.getHeader('Some-Custom-Header-Name')
+  request.removeHeader('Some-Custom-Header-Name')
+  request.on('response', (response) => {
+    console.log(`Status code: ${response.statusCode}`)
+    console.log(`Status message: ${response.statusMessage}`)
+    console.log(`Headers: ${JSON.stringify(response.headers)}`)
+    console.log(`Http version: ${response.httpVersion}`)
+    console.log(`Major Http version: ${response.httpVersionMajor}`)
+    console.log(`Minor Http version: ${response.httpVersionMinor}`)
+    response.on('data', (chunk) => {
+      console.log(`BODY: ${chunk}`)
+    })
+    response.on('end', () => {
+      console.log('No more data in response.')
+    })
+    response.on('error', () => {
+      console.log('"error" event emitted')
+    })
+    response.on('aborted', () => {
+      console.log('"aborted" event emitted')
+    })
+  })
+  request.on('login', (authInfo, callback) => {
+    callback('username', 'password')
+  })
+  request.on('finish', () => {
+    console.log('"finish" event emitted')
+  })
+  request.on('abort', () => {
+    console.log('"abort" event emitted')
+  })
+  request.on('error', () => {
+    console.log('"error" event emitted')
+  })
+  request.write('Hello World!', 'utf-8')
+  request.end('Hello World!', 'utf-8')
+  request.abort()
+})
+
+// power-monitor
+// https://github.com/atom/electron/blob/master/docs/api/power-monitor.md
+
+app.on('ready', () => {
+  powerMonitor.on('suspend', () => {
+    console.log('The system is going to sleep')
+  })
+  powerMonitor.on('resume', () => {
+    console.log('The system has resumed from sleep')
+  })
+  powerMonitor.on('on-ac', () => {
+    console.log('The system changed to AC power')
+  })
+  powerMonitor.on('on-battery', () => {
+    console.log('The system changed to battery power')
+  })
+})
+
+// power-save-blocker
+// https://github.com/atom/electron/blob/master/docs/api/power-save-blocker.md
+
+const id = powerSaveBlocker.start('prevent-display-sleep')
+console.log(powerSaveBlocker.isStarted(id))
+
+powerSaveBlocker.stop(id)
+
+// protocol
+// https://github.com/atom/electron/blob/master/docs/api/protocol.md
+
+app.on('ready', () => {
+  protocol.registerSchemesAsPrivileged([{ scheme: 'https', privileges: { standard: true, allowServiceWorkers: true } }])
+
+  protocol.registerFileProtocol('atom', (request, callback) => {
+    callback(`${__dirname}/${request.url}`)
+  })
+
+  protocol.registerBufferProtocol('atom', (request, callback) => {
+    callback({ mimeType: 'text/html', data: new Buffer('<h5>Response</h5>') })
+  })
+
+  protocol.registerStringProtocol('atom', (request, callback) => {
+    callback('Hello World!')
+  })
+
+  protocol.registerHttpProtocol('atom', (request, callback) => {
+    callback({ url: request.url, method: request.method })
+  })
+
+  protocol.unregisterProtocol('atom', (error) => {
+    console.log(error ? error.message : 'ok')
+  })
+
+  protocol.isProtocolHandled('atom', (handled) => {
+    console.log(handled)
+  })
+})
+
+// tray
+// https://github.com/atom/electron/blob/master/docs/api/tray.md
+
+let appIcon: Electron.Tray = null
+app.on('ready', () => {
+  appIcon = new Tray('/path/to/my/icon')
+  const contextMenu = Menu.buildFromTemplate([
+    { label: 'Item1', type: 'radio' },
+    { label: 'Item2', type: 'radio' },
+    { label: 'Item3', type: 'radio', checked: true },
+    { label: 'Item4', type: 'radio' }
+  ])
+  appIcon.setToolTip('This is my application.')
+  appIcon.setContextMenu(contextMenu)
+  appIcon.setImage('/path/to/new/icon')
+  appIcon.popUpContextMenu(contextMenu, { x: 100, y: 100 })
+
+  appIcon.on('click', (event, bounds) => {
+    console.log('click', event, bounds)
+  })
+
+  appIcon.on('balloon-show', () => {
+    console.log('balloon-show')
+  })
+
+  appIcon.displayBalloon({
+    title: 'Hello World!',
+    content: 'This the the balloon content.'
+  })
+})
+
+// clipboard
+// https://github.com/atom/electron/blob/master/docs/api/clipboard.md
+
+{
+  let str: string
+  clipboard.writeText('Example String')
+  clipboard.writeText('Example String', 'selection')
+  clipboard.writeBookmark('foo', 'http://example.com')
+  clipboard.writeBookmark('foo', 'http://example.com', 'selection')
+  clipboard.writeFindText('foo')
+  str = clipboard.readText('selection')
+  str = clipboard.readFindText()
+  console.log(clipboard.availableFormats())
+  console.log(clipboard.readBookmark().title)
+  clipboard.clear()
+
+  clipboard.write({
+    html: '<html></html>',
+    text: 'Hello World!',
+    image: clipboard.readImage()
+  })
+}
+
+// crash-reporter
+// https://github.com/atom/electron/blob/master/docs/api/crash-reporter.md
+
+crashReporter.start({
+  productName: 'YourName',
+  companyName: 'YourCompany',
+  submitURL: 'https://your-domain.com/url-to-submit',
+  uploadToServer: true,
+  extra: {
+    someKey: 'value'
+  }
+})
+
+console.log(crashReporter.getLastCrashReport())
+console.log(crashReporter.getUploadedReports())
+
+// nativeImage
+// https://github.com/atom/electron/blob/master/docs/api/native-image.md
+
+const appIcon2 = new Tray('/Users/somebody/images/icon.png')
+const window2 = new BrowserWindow({ icon: '/Users/somebody/images/window.png' })
+const image = clipboard.readImage()
+const appIcon3 = new Tray(image)
+const appIcon4 = new Tray('/Users/somebody/images/icon.png')
+
+const image2 = nativeImage.createFromPath('/Users/somebody/images/icon.png')
+
+// process
+// https://github.com/electron/electron/blob/master/docs/api/process.md
+
+console.log(process.versions.electron)
+console.log(process.versions.chrome)
+console.log(process.type)
+console.log(process.resourcesPath)
+console.log(process.mas)
+console.log(process.windowsStore)
+process.noAsar = true
+process.crash()
+process.hang()
+process.setFdLimit(8192)
+
+// screen
+// https://github.com/atom/electron/blob/master/docs/api/screen.md
+
+app.on('ready', () => {
+  const size = screen.getPrimaryDisplay().workAreaSize
+  mainWindow = new BrowserWindow({ width: size.width, height: size.height })
+})
+
+app.on('ready', () => {
+  const displays = screen.getAllDisplays()
+  let externalDisplay: any = null
+  for (const i in displays) {
+    if (displays[i].bounds.x > 0 || displays[i].bounds.y > 0) {
+      externalDisplay = displays[i]
+      break
+    }
+  }
+
+  if (externalDisplay) {
+    mainWindow = new BrowserWindow({
+      x: externalDisplay.bounds.x + 50,
+      y: externalDisplay.bounds.y + 50
+    })
+  }
+
+  screen.on('display-added', (event, display) => {
+    console.log('display-added', display)
+  })
+
+  screen.on('display-removed', (event, display) => {
+    console.log('display-removed', display)
+  })
+
+  screen.on('display-metrics-changed', (event, display, changes) => {
+    console.log('display-metrics-changed', display, changes)
+  })
+})
+
+// shell
+// https://github.com/atom/electron/blob/master/docs/api/shell.md
+
+shell.showItemInFolder('/home/user/Desktop/test.txt')
+shell.openItem('/home/user/Desktop/test.txt')
+shell.moveItemToTrash('/home/user/Desktop/test.txt')
+
+shell.openExternal('https://github.com', {
+  activate: false
+})
+
+shell.beep()
+
+shell.writeShortcutLink('/home/user/Desktop/shortcut.lnk', 'update', shell.readShortcutLink('/home/user/Desktop/shortcut.lnk'))
+
+// cookies
+// https://github.com/atom/electron/blob/master/docs/api/cookies.md
+{
+  const { session } = require('electron')
+
+  // Query all cookies.
+  session.defaultSession.cookies.get({})
+    .then((cookies) => {
+      console.log(cookies)
+    }).catch((error) => {
+      console.log(error)
+    })
+
+  // Query all cookies associated with a specific url.
+  session.defaultSession.cookies.get({ url: 'http://www.github.com' })
+    .then((cookies) => {
+      console.log(cookies)
+    }).catch((error) => {
+      console.log(error)
+    })
+
+  // Set a cookie with the given cookie data;
+  // may overwrite equivalent cookies if they exist.
+  const cookie = { url: 'http://www.github.com', name: 'dummy_name', value: 'dummy' }
+  session.defaultSession.cookies.set(cookie)
+    .then(() => {
+      // success
+    }, (error) => {
+      console.error(error)
+    })
+}
+
+// session
+// https://github.com/atom/electron/blob/master/docs/api/session.md
+
+session.defaultSession.on('will-download', (event, item, webContents) => {
+  event.preventDefault()
+  require('request')(item.getURL(), (data: any) => {
+    require('fs').writeFileSync('/somewhere', data)
+  })
+})
+
+// In the main process.
+session.defaultSession.on('will-download', (event, item, webContents) => {
+  // Set the save path, making Electron not to prompt a save dialog.
+  item.setSavePath('/tmp/save.pdf')
+  console.log(item.getSavePath())
+  console.log(item.getMimeType())
+  console.log(item.getFilename())
+  console.log(item.getTotalBytes())
+
+  item.on('updated', (_event, state) => {
+    if (state === 'interrupted') {
+      console.log('Download is interrupted but can be resumed')
+    } else if (state === 'progressing') {
+      if (item.isPaused()) {
+        console.log('Download is paused')
+      } else {
+        console.log(`Received bytes: ${item.getReceivedBytes()}`)
+      }
+    }
+  })
+
+  item.on('done', function (e, state) {
+    if (state === 'completed') {
+      console.log('Download successfully')
+    } else {
+      console.log(`Download failed: ${state}`)
+    }
+  })
+})
+
+// To emulate a GPRS connection with 50kbps throughput and 500 ms latency.
+session.defaultSession.enableNetworkEmulation({
+  latency: 500,
+  downloadThroughput: 6400,
+  uploadThroughput: 6400
+})
+
+// To emulate a network outage.
+session.defaultSession.enableNetworkEmulation({
+  offline: true
+})
+
+session.defaultSession.setCertificateVerifyProc((request, callback) => {
+  const { hostname } = request
+  if (hostname === 'github.com') {
+    callback(0)
+  } else {
+    callback(-2)
+  }
+})
+
+session.defaultSession.setPermissionRequestHandler(function (webContents, permission, callback) {
+  if (webContents.getURL() === 'github.com') {
+    if (permission === 'notifications') {
+      callback(false)
+      return
+    }
+  }
+
+  callback(true)
+})
+
+// consider any url ending with `example.com`, `foobar.com`, `baz`
+// for integrated authentication.
+session.defaultSession.allowNTLMCredentialsForDomains('*example.com, *foobar.com, *baz')
+
+// consider all urls for integrated authentication.
+session.defaultSession.allowNTLMCredentialsForDomains('*')
+
+// Modify the user agent for all requests to the following urls.
+const filter = {
+  urls: ['https://*.github.com/*', '*://electron.github.io']
+}
+
+session.defaultSession.webRequest.onBeforeSendHeaders(filter, function (details: any, callback: any) {
+  details.requestHeaders['User-Agent'] = 'MyAgent'
+  callback({ cancel: false, requestHeaders: details.requestHeaders })
+})
+
+app.on('ready', function () {
+  const protocol = session.defaultSession.protocol
+  protocol.registerFileProtocol('atom', function (request, callback) {
+    const url = request.url.substr(7)
+    callback(path.normalize(__dirname + '/' + url))
+  }, function (error) {
+    if (error) {
+      console.error('Failed to register protocol')
+    }
+  })
+})
+
+// webContents
+// https://github.com/electron/electron/blob/master/docs/api/web-contents.md
+
+console.log(webContents.getAllWebContents())
+console.log(webContents.getFocusedWebContents())
+
+const win4 = new BrowserWindow({
+  webPreferences: {
+    offscreen: true
+  }
+})
+
+win4.webContents.on('paint', (event, dirty, _image) => {
+  console.log(dirty, _image.getBitmap())
+})
+
+win4.loadURL('http://github.com')
+
+const unusedTouchBar = new TouchBar({
+  items: [
+    new TouchBar.TouchBarButton({ label: '' }),
+    new TouchBar.TouchBarLabel({ label: '' })
+  ]
+})

+ 266 - 0
spec/ts-smoke/electron/renderer.ts

@@ -0,0 +1,266 @@
+
+import {
+  ipcRenderer,
+  remote,
+  webFrame,
+  clipboard,
+  crashReporter,
+  nativeImage,
+  screen,
+  shell
+} from 'electron'
+
+import * as fs from 'fs'
+
+// In renderer process (web page).
+// https://github.com/atom/electron/blob/master/docs/api/ipc-renderer.md
+console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong"
+
+ipcRenderer.on('asynchronous-reply', (event: Electron.Event, arg: any) => {
+  console.log(arg) // prints "pong"
+  event.sender.send('another-message', 'Hello World!')
+})
+
+ipcRenderer.send('asynchronous-message', 'ping')
+
+// remote
+// https://github.com/atom/electron/blob/master/docs/api/remote.md
+
+const BrowserWindow = remote.BrowserWindow
+const win = new BrowserWindow({ width: 800, height: 600 })
+win.loadURL('https://github.com')
+
+remote.getCurrentWindow().on('close', () => {
+  // blabla...
+})
+
+remote.getCurrentWindow().capturePage(buf => {
+  fs.writeFile('/tmp/screenshot.png', buf, err => {
+    console.log(err)
+  })
+})
+
+remote.getCurrentWebContents().print()
+
+remote.getCurrentWindow().capturePage(buf => {
+  remote.require('fs').writeFile('/tmp/screenshot.png', buf, (err: Error) => {
+    console.log(err)
+  })
+})
+
+// web-frame
+// https://github.com/atom/electron/blob/master/docs/api/web-frame.md
+
+webFrame.setZoomFactor(2)
+console.log(webFrame.getZoomFactor())
+
+webFrame.setZoomLevel(200)
+console.log(webFrame.getZoomLevel())
+
+webFrame.setVisualZoomLevelLimits(50, 200)
+webFrame.setLayoutZoomLevelLimits(50, 200)
+
+webFrame.setSpellCheckProvider('en-US', {
+  spellCheck (words, callback) {
+    setTimeout(() => {
+      const spellchecker = require('spellchecker')
+      const misspelled = words.filter(x => spellchecker.isMisspelled(x))
+      callback(misspelled)
+    }, 0)
+  }
+})
+
+webFrame.insertText('text')
+
+webFrame.executeJavaScript('JSON.stringify({})', false, (result) => {
+  console.log(result)
+}).then((result: string) => console.log('OK:' + result))
+
+console.log(webFrame.getResourceUsage())
+webFrame.clearCache()
+
+// clipboard
+// https://github.com/atom/electron/blob/master/docs/api/clipboard.md
+
+clipboard.writeText('Example String')
+clipboard.writeText('Example String', 'selection')
+console.log(clipboard.readText('selection'))
+console.log(clipboard.availableFormats())
+clipboard.clear()
+
+clipboard.write({
+  html: '<html></html>',
+  text: 'Hello World!',
+  bookmark: 'Bookmark name',
+  image: clipboard.readImage()
+})
+
+// crash-reporter
+// https://github.com/atom/electron/blob/master/docs/api/crash-reporter.md
+
+crashReporter.start({
+  productName: 'YourName',
+  companyName: 'YourCompany',
+  submitURL: 'https://your-domain.com/url-to-submit',
+  uploadToServer: true
+})
+
+// desktopCapturer
+// https://github.com/atom/electron/blob/master/docs/api/desktop-capturer.md
+
+const desktopCapturer = require('electron').desktopCapturer
+
+desktopCapturer.getSources({ types: ['window', 'screen'] }, function (error, sources) {
+  if (error) throw error
+  for (let i = 0; i < sources.length; ++i) {
+    if (sources[i].name == 'Electron') {
+      (navigator as any).webkitGetUserMedia({
+        audio: false,
+        video: {
+          mandatory: {
+            chromeMediaSource: 'desktop',
+            chromeMediaSourceId: sources[i].id,
+            minWidth: 1280,
+            maxWidth: 1280,
+            minHeight: 720,
+            maxHeight: 720
+          }
+        }
+      }, gotStream, getUserMediaError)
+      return
+    }
+  }
+})
+
+function gotStream (stream: any) {
+  (document.querySelector('video') as HTMLVideoElement).src = URL.createObjectURL(stream)
+}
+
+function getUserMediaError (error: Error) {
+  console.log('getUserMediaError', error)
+}
+
+// File object
+// https://github.com/atom/electron/blob/master/docs/api/file-object.md
+
+/*
+<div id="holder">
+  Drag your file here
+</div>
+*/
+
+const holder = document.getElementById('holder')
+
+holder.ondragover = function () {
+  return false
+}
+
+holder.ondragleave = holder.ondragend = function () {
+  return false
+}
+
+holder.ondrop = function (e) {
+  e.preventDefault()
+  const file = e.dataTransfer.files[0]
+  console.log('File you dragged here is', file.path)
+  return false
+}
+
+// nativeImage
+// https://github.com/atom/electron/blob/master/docs/api/native-image.md
+
+const Tray = remote.Tray
+const appIcon2 = new Tray('/Users/somebody/images/icon.png')
+const window2 = new BrowserWindow({ icon: '/Users/somebody/images/window.png' })
+const image = clipboard.readImage()
+const appIcon3 = new Tray(image)
+const appIcon4 = new Tray('/Users/somebody/images/icon.png')
+
+// https://github.com/electron/electron/blob/master/docs/api/process.md
+
+// preload.js
+const _setImmediate = setImmediate
+const _clearImmediate = clearImmediate
+process.once('loaded', function () {
+  global.setImmediate = _setImmediate
+  global.clearImmediate = _clearImmediate
+})
+
+// screen
+// https://github.com/atom/electron/blob/master/docs/api/screen.md
+
+const app = remote.app
+
+let mainWindow: Electron.BrowserWindow = null
+
+app.on('ready', () => {
+  const size = screen.getPrimaryDisplay().workAreaSize
+  mainWindow = new BrowserWindow({ width: size.width, height: size.height })
+})
+
+app.on('ready', () => {
+  const displays = screen.getAllDisplays()
+  let externalDisplay: any = null
+  for (const i in displays) {
+    if (displays[i].bounds.x > 0 || displays[i].bounds.y > 0) {
+      externalDisplay = displays[i]
+      break
+    }
+  }
+
+  if (externalDisplay) {
+    mainWindow = new BrowserWindow({
+      x: externalDisplay.bounds.x + 50,
+      y: externalDisplay.bounds.y + 50
+    })
+  }
+})
+
+// shell
+// https://github.com/atom/electron/blob/master/docs/api/shell.md
+
+shell.openExternal('https://github.com')
+
+// <webview>
+// https://github.com/atom/electron/blob/master/docs/api/web-view-tag.md
+
+const webview = document.createElement('webview')
+webview.loadURL('https://github.com')
+
+webview.addEventListener('console-message', function (e) {
+  console.log('Guest page logged a message:', e.message)
+})
+
+webview.addEventListener('found-in-page', function (e) {
+  if (e.result.finalUpdate) {
+    webview.stopFindInPage('keepSelection')
+  }
+})
+
+const requestId = webview.findInPage('test')
+
+webview.addEventListener('new-window', function (e) {
+  require('electron').shell.openExternal(e.url)
+})
+
+webview.addEventListener('close', function () {
+  webview.src = 'about:blank'
+})
+
+// In embedder page.
+webview.addEventListener('ipc-message', function (event) {
+  console.log(event.channel) // Prints "pong"
+})
+webview.send('ping')
+webview.capturePage((image) => { console.log(image) })
+
+{
+  const opened: boolean = webview.isDevToolsOpened()
+  const focused: boolean = webview.isDevToolsFocused()
+  const focused2: boolean = webview.getWebContents().isFocused()
+}
+
+// In guest page.
+ipcRenderer.on('ping', function () {
+  ipcRenderer.sendToHost('pong')
+})

+ 18 - 0
spec/ts-smoke/runner.js

@@ -0,0 +1,18 @@
+const path = require('path')
+const childProcess = require('child_process')
+
+const typeCheck = () => {
+  const tscExec = path.resolve(require.resolve('typescript'), '../../bin/tsc')
+  const tscChild = childProcess.spawn(process.execPath, [tscExec, '--project', './ts-smoke/tsconfig.json'], {
+    cwd: path.resolve(__dirname, '../')
+  })
+  tscChild.stdout.on('data', d => console.log(d.toString()))
+  tscChild.stderr.on('data', d => console.error(d.toString()))
+  tscChild.on('exit', (tscStatus) => {
+    if (tscStatus !== 0) {
+      process.exit(tscStatus)
+    }
+  })
+}
+
+typeCheck()

+ 19 - 0
spec/ts-smoke/tsconfig.json

@@ -0,0 +1,19 @@
+{
+  "compilerOptions": {
+      "module": "commonjs",
+      "lib": [
+          "es6",
+          "dom"
+      ],
+      "noImplicitAny": true,
+      "noImplicitThis": true,
+      "strictNullChecks": false,
+      "noEmit": true,
+      "forceConsistentCasingInFileNames": true
+  },
+  "files": [
+    "electron/main.ts",
+    "electron/renderer.ts",
+    "../../electron.d.ts"
+  ]
+}