Browse Source

spec: initial spike of main-process based tests

Samuel Attard 6 years ago
parent
commit
ca701bb9af

+ 7 - 1
.circleci/config.yml

@@ -810,7 +810,13 @@ steps-tests: &steps-tests
     - run:
         name: Check test results existence
         command: |
-          MOCHA_FILE='src/junit/test-results.xml'
+          MOCHA_FILE='src/junit/test-results-remote.xml'
+          # Check if it exists and not empty.
+          if [ ! -s "$MOCHA_FILE" ]; then
+            exit 1
+          fi
+
+          MOCHA_FILE='src/junit/test-results-main.xml'
           # Check if it exists and not empty.
           if [ ! -s "$MOCHA_FILE" ]; then
             exit 1

+ 172 - 97
package-lock.json

@@ -155,12 +155,52 @@
         "any-observable": "^0.3.0"
       }
     },
+    "@types/chai": {
+      "version": "4.1.7",
+      "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz",
+      "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==",
+      "dev": true
+    },
+    "@types/chai-as-promised": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.0.tgz",
+      "integrity": "sha512-MFiW54UOSt+f2bRw8J7LgQeIvE/9b4oGvwU7XW30S9QGAiHGnU/fmiOprsyMkdmH2rl8xSPc0/yrQw8juXU6bQ==",
+      "dev": true,
+      "requires": {
+        "@types/chai": "*"
+      }
+    },
+    "@types/mocha": {
+      "version": "5.2.6",
+      "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.6.tgz",
+      "integrity": "sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw==",
+      "dev": true
+    },
     "@types/node": {
       "version": "10.12.21",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.21.tgz",
       "integrity": "sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ==",
       "dev": true
     },
+    "@types/split": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@types/split/-/split-1.0.0.tgz",
+      "integrity": "sha512-pm9S1mkr+av0j7D6pFyqhBxXDbnbO9gqj4nb8DtGtCewvj0XhIv089SSwXrjrIizT1UquO8/h83hCut0pa3u8A==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*",
+        "@types/through": "*"
+      }
+    },
+    "@types/through": {
+      "version": "0.0.29",
+      "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.29.tgz",
+      "integrity": "sha512-9a7C5VHh+1BKblaYiq+7Tfc+EOmjMdZaD1MYtkQjSoxgB69tBjW98ry6SKsi4zEIWztLOMRuL87A3bdT/Fc/4w==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*"
+      }
+    },
     "@typescript-eslint/eslint-plugin": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.4.2.tgz",
@@ -4677,28 +4717,27 @@
       "dependencies": {
         "abbrev": {
           "version": "1.1.1",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
           "dev": true,
           "optional": true
         },
         "ansi-regex": {
           "version": "2.1.1",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "aproba": {
           "version": "1.2.0",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
           "dev": true,
           "optional": true
         },
         "are-we-there-yet": {
           "version": "1.1.4",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
           "dev": true,
           "optional": true,
@@ -4709,17 +4748,15 @@
         },
         "balanced-match": {
           "version": "1.0.0",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "brace-expansion": {
           "version": "1.1.11",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
           "dev": true,
-          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -4727,42 +4764,39 @@
         },
         "chownr": {
           "version": "1.0.1",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=",
           "dev": true,
           "optional": true
         },
         "code-point-at": {
           "version": "1.1.0",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "concat-map": {
           "version": "0.0.1",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "console-control-strings": {
           "version": "1.1.0",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "core-util-is": {
           "version": "1.0.2",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
           "dev": true,
           "optional": true
         },
         "debug": {
           "version": "2.6.9",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
           "dev": true,
           "optional": true,
@@ -4772,28 +4806,28 @@
         },
         "deep-extend": {
           "version": "0.5.1",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==",
           "dev": true,
           "optional": true
         },
         "delegates": {
           "version": "1.0.0",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
           "dev": true,
           "optional": true
         },
         "detect-libc": {
           "version": "1.0.3",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
           "dev": true,
           "optional": true
         },
         "fs-minipass": {
           "version": "1.2.5",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==",
           "dev": true,
           "optional": true,
@@ -4803,14 +4837,14 @@
         },
         "fs.realpath": {
           "version": "1.0.0",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
           "dev": true,
           "optional": true
         },
         "gauge": {
           "version": "2.7.4",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
           "dev": true,
           "optional": true,
@@ -4827,7 +4861,7 @@
         },
         "glob": {
           "version": "7.1.2",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
           "dev": true,
           "optional": true,
@@ -4842,14 +4876,14 @@
         },
         "has-unicode": {
           "version": "2.0.1",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
           "dev": true,
           "optional": true
         },
         "iconv-lite": {
           "version": "0.4.21",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==",
           "dev": true,
           "optional": true,
@@ -4859,7 +4893,7 @@
         },
         "ignore-walk": {
           "version": "3.0.1",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==",
           "dev": true,
           "optional": true,
@@ -4869,7 +4903,7 @@
         },
         "inflight": {
           "version": "1.0.6",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
           "dev": true,
           "optional": true,
@@ -4880,58 +4914,53 @@
         },
         "inherits": {
           "version": "2.0.3",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "ini": {
           "version": "1.3.5",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
           "dev": true,
           "optional": true
         },
         "is-fullwidth-code-point": {
           "version": "1.0.0",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
           "dev": true,
-          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
         },
         "isarray": {
           "version": "1.0.0",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
           "dev": true,
           "optional": true
         },
         "minimatch": {
           "version": "3.0.4",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
           "dev": true,
-          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
         },
         "minimist": {
           "version": "0.0.8",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "minipass": {
           "version": "2.2.4",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==",
           "dev": true,
-          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.1",
             "yallist": "^3.0.0"
@@ -4939,7 +4968,7 @@
         },
         "minizlib": {
           "version": "1.1.0",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==",
           "dev": true,
           "optional": true,
@@ -4949,24 +4978,23 @@
         },
         "mkdirp": {
           "version": "0.5.1",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
           "dev": true,
-          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
         },
         "ms": {
           "version": "2.0.0",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
           "dev": true,
           "optional": true
         },
         "needle": {
           "version": "2.2.0",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==",
           "dev": true,
           "optional": true,
@@ -4978,7 +5006,7 @@
         },
         "node-pre-gyp": {
           "version": "0.10.0",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==",
           "dev": true,
           "optional": true,
@@ -4997,7 +5025,7 @@
         },
         "nopt": {
           "version": "4.0.1",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
           "dev": true,
           "optional": true,
@@ -5008,14 +5036,14 @@
         },
         "npm-bundled": {
           "version": "1.0.3",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==",
           "dev": true,
           "optional": true
         },
         "npm-packlist": {
           "version": "1.1.10",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==",
           "dev": true,
           "optional": true,
@@ -5026,7 +5054,7 @@
         },
         "npmlog": {
           "version": "4.1.2",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
           "dev": true,
           "optional": true,
@@ -5039,45 +5067,43 @@
         },
         "number-is-nan": {
           "version": "1.0.1",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "object-assign": {
           "version": "4.1.1",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
           "dev": true,
           "optional": true
         },
         "once": {
           "version": "1.4.0",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
           "dev": true,
-          "optional": true,
           "requires": {
             "wrappy": "1"
           }
         },
         "os-homedir": {
           "version": "1.0.2",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
           "dev": true,
           "optional": true
         },
         "os-tmpdir": {
           "version": "1.0.2",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
           "dev": true,
           "optional": true
         },
         "osenv": {
           "version": "0.1.5",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
           "dev": true,
           "optional": true,
@@ -5088,21 +5114,21 @@
         },
         "path-is-absolute": {
           "version": "1.0.1",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
           "dev": true,
           "optional": true
         },
         "process-nextick-args": {
           "version": "2.0.0",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
           "dev": true,
           "optional": true
         },
         "rc": {
           "version": "1.2.7",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-LdLD8xD4zzLsAT5xyushXDNscEjB7+2ulnl8+r1pnESlYtlJtVSoCMBGr30eDRJ3+2Gq89jK9P9e4tCEH1+ywA==",
           "dev": true,
           "optional": true,
@@ -5115,7 +5141,7 @@
           "dependencies": {
             "minimist": {
               "version": "1.2.0",
-              "resolved": false,
+              "resolved": "",
               "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
               "dev": true,
               "optional": true
@@ -5124,7 +5150,7 @@
         },
         "readable-stream": {
           "version": "2.3.6",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "dev": true,
           "optional": true,
@@ -5140,7 +5166,7 @@
         },
         "rimraf": {
           "version": "2.6.2",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
           "dev": true,
           "optional": true,
@@ -5150,52 +5176,50 @@
         },
         "safe-buffer": {
           "version": "5.1.1",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "safer-buffer": {
           "version": "2.1.2",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
           "dev": true,
           "optional": true
         },
         "sax": {
           "version": "1.2.4",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
           "dev": true,
           "optional": true
         },
         "semver": {
           "version": "5.5.0",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
           "dev": true,
           "optional": true
         },
         "set-blocking": {
           "version": "2.0.0",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
           "dev": true,
           "optional": true
         },
         "signal-exit": {
           "version": "3.0.2",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
           "dev": true,
           "optional": true
         },
         "string-width": {
           "version": "1.0.2",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
           "dev": true,
-          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -5204,7 +5228,7 @@
         },
         "string_decoder": {
           "version": "1.1.1",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
           "dev": true,
           "optional": true,
@@ -5214,24 +5238,23 @@
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "dev": true,
-          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
         },
         "strip-json-comments": {
           "version": "2.0.1",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
           "dev": true,
           "optional": true
         },
         "tar": {
           "version": "4.4.1",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==",
           "dev": true,
           "optional": true,
@@ -5247,14 +5270,14 @@
         },
         "util-deprecate": {
           "version": "1.0.2",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
           "dev": true,
           "optional": true
         },
         "wide-align": {
           "version": "1.1.2",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
           "dev": true,
           "optional": true,
@@ -5264,17 +5287,15 @@
         },
         "wrappy": {
           "version": "1.0.2",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "yallist": {
           "version": "3.0.2",
-          "resolved": false,
+          "resolved": "",
           "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=",
-          "dev": true,
-          "optional": true
+          "dev": true
         }
       }
     },
@@ -7787,6 +7808,12 @@
         }
       }
     },
+    "make-error": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
+      "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==",
+      "dev": true
+    },
     "map-cache": {
       "version": "0.2.2",
       "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
@@ -11197,6 +11224,24 @@
         "urix": "^0.1.0"
       }
     },
+    "source-map-support": {
+      "version": "0.5.10",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz",
+      "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==",
+      "dev": true,
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "source-map": "^0.6.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
     "source-map-url": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
@@ -12584,6 +12629,30 @@
       "integrity": "sha512-FHkoUZvG6Egrv9XZAyYGKEyb1JMsFphgPjoczkZC2y6W93U1jswcVURB8MUvtsahEPEVACyxD47JAL63vF4JsQ==",
       "dev": true
     },
+    "ts-node": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-6.2.0.tgz",
+      "integrity": "sha512-ZNT+OEGfUNVMGkpIaDJJ44Zq3Yr0bkU/ugN1PHbU+/01Z7UV1fsELRiTx1KuQNvQ1A3pGh3y25iYF6jXgxV21A==",
+      "dev": true,
+      "requires": {
+        "arrify": "^1.0.0",
+        "buffer-from": "^1.1.0",
+        "diff": "^3.1.0",
+        "make-error": "^1.1.1",
+        "minimist": "^1.2.0",
+        "mkdirp": "^0.5.1",
+        "source-map-support": "^0.5.6",
+        "yn": "^2.0.0"
+      },
+      "dependencies": {
+        "buffer-from": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+          "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+          "dev": true
+        }
+      }
+    },
     "tsconfig": {
       "version": "5.0.3",
       "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-5.0.3.tgz",
@@ -13614,6 +13683,12 @@
       "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz",
       "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=",
       "dev": true
+    },
+    "yn": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz",
+      "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=",
+      "dev": true
     }
   }
-}
+}

+ 7 - 2
package.json

@@ -5,7 +5,11 @@
   "description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
   "devDependencies": {
     "@octokit/rest": "^16.3.2",
+    "@types/chai": "^4.1.7",
+    "@types/chai-as-promised": "^7.1.0",
+    "@types/mocha": "^5.2.6",
     "@types/node": "^10.12.21",
+    "@types/split": "^1.0.0",
     "@typescript-eslint/eslint-plugin": "^1.4.2",
     "@typescript-eslint/parser": "^1.4.2",
     "aliasify": "^2.1.0",
@@ -42,6 +46,7 @@
     "standard-markdown": "^5.0.0",
     "sumchecker": "^2.0.2",
     "temp": "^0.8.3",
+    "ts-node": "^6.0.3",
     "tsify": "^4.0.1",
     "typescript": "~3.3.3333"
   },
@@ -71,7 +76,7 @@
     "prepush": "check-for-leaks",
     "repl": "node ./script/start.js --interactive",
     "start": "node ./script/start.js",
-    "test": "node ./script/spec-runner.js electron/spec",
+    "test": "node ./script/spec-runner.js",
     "tsc": "tsc"
   },
   "license": "MIT",
@@ -115,4 +120,4 @@
       "git add filenames.auto.gni"
     ]
   }
-}
+}

+ 59 - 2
script/spec-runner.js

@@ -13,6 +13,15 @@ const NPM_CMD = process.platform === 'win32' ? 'npm.cmd' : 'npm'
 
 const specHashPath = path.resolve(__dirname, '../spec/.hash')
 
+let only = null
+const onlyArg = process.argv.find(arg => arg.startsWith('--only='))
+if (onlyArg) {
+  only = onlyArg.substr(7).split(',')
+  console.log('Only running:', only)
+} else {
+  console.log('Will trigger all spec runners')
+}
+
 async function main () {
   const [lastSpecHash, lastSpecInstallHash] = loadLastSpecHash()
   const [currentSpecHash, currentSpecInstallHash] = await getSpecHash()
@@ -38,14 +47,62 @@ function saveSpecHash ([newSpecHash, newSpecInstallHash]) {
 }
 
 async function runElectronTests () {
+  const errors = []
+  const runners = [
+    ['Remote based specs', 'remote', runRemoteBasedElectronTests],
+    ['Main process specs', 'main', runMainProcessElectronTests]
+  ]
+
+  const mochaFile = process.env.MOCHA_FILE
+  for (const runner of runners) {
+    if (only && !only.includes(runner[1])) {
+      console.info('\nSkipping:', runner[0])
+      continue
+    }
+    try {
+      console.info('\nRunning:', runner[0])
+      if (mochaFile) {
+        process.env.MOCHA_FILE = mochaFile.replace('.xml', `-${runner[1]}.xml`)
+      }
+      await runner[2]()
+    } catch (err) {
+      errors.push([runner[0], err])
+    }
+  }
+
+  if (errors.length !== 0) {
+    for (const err of errors) {
+      console.error('\n\nRunner Failed:', err[0])
+      console.error(err[1])
+    }
+    throw new Error('Electron test runners have failed')
+  }
+}
+
+async function runRemoteBasedElectronTests () {
   let exe = path.resolve(BASE, utils.getElectronExec())
-  const args = process.argv.slice(2)
+  const args = process.argv.slice(2).filter(arg => !arg.startsWith('--only='))
   if (process.platform === 'linux') {
     args.unshift(path.resolve(__dirname, 'dbus_mock.py'), exe)
     exe = 'python'
   }
 
-  const { status } = childProcess.spawnSync(exe, args, {
+  const { status } = childProcess.spawnSync(exe, ['electron/spec', ...args], {
+    cwd: path.resolve(__dirname, '../..'),
+    stdio: 'inherit'
+  })
+  if (status !== 0) {
+    throw new Error(`Electron tests failed with code ${status}.`)
+  }
+}
+
+async function runMainProcessElectronTests () {
+  const exe = path.resolve(BASE, utils.getElectronExec())
+  const args = process.argv.slice(2).filter(arg => !arg.startsWith('--only='))
+
+  console.log(exe, args)
+
+  const { status } = childProcess.spawnSync(exe, ['electron/spec-main', ...args], {
     cwd: path.resolve(__dirname, '../..'),
     stdio: 'inherit'
   })

+ 20 - 0
spec-main/.eslintrc

@@ -0,0 +1,20 @@
+{
+  "env": {
+    "browser": true,
+    "mocha": true,
+    "jquery": true,
+    "serviceworker": true
+  },
+  "globals": {
+    "Bindings": true,
+    "Components": true,
+    "UI": true,
+    "WebView": true
+  },
+  "plugins": [
+    "mocha"
+  ],
+  "rules": {
+    "mocha/no-exclusive-tests": "error"
+  }
+}

+ 1 - 0
spec-main/ambient.d.ts

@@ -0,0 +1 @@
+declare var isCI: boolean;

+ 195 - 210
spec/api-app-spec.js → spec-main/api-app-spec.ts

@@ -1,24 +1,20 @@
-const chai = require('chai')
-const chaiAsPromised = require('chai-as-promised')
-const dirtyChai = require('dirty-chai')
-const ChildProcess = require('child_process')
-const https = require('https')
-const net = require('net')
-const fs = require('fs')
-const path = require('path')
-const cp = require('child_process')
-const split = require('split')
-const { ipcRenderer, remote } = require('electron')
-const { emittedOnce } = require('./events-helpers')
-const { closeWindow } = require('./window-helpers')
+import * as chai from 'chai'
+import * as chaiAsPromised from 'chai-as-promised'
+import * as cp from 'child_process'
+import * as https from 'https'
+import * as net from 'net'
+import * as fs from 'fs'
+import * as path from 'path'
+import split = require('split')
+import { app, BrowserWindow, Menu } from 'electron'
+import { emittedOnce } from './events-helpers';
+import { closeWindow } from './window-helpers';
 
 const { expect } = chai
-const { app, BrowserWindow, Menu, ipcMain } = remote
-
-const isCI = remote.getGlobal('isCi')
 
 chai.use(chaiAsPromised)
-chai.use(dirtyChai)
+
+const fixturesPath = path.resolve(__dirname, '../spec/fixtures')
 
 describe('electron module', () => {
   it('does not expose internal modules to require', () => {
@@ -28,33 +24,16 @@ describe('electron module', () => {
   })
 
   describe('require("electron")', () => {
-    let window = null
-
-    beforeEach(() => {
-      window = new BrowserWindow({
-        show: false,
-        width: 400,
-        height: 400,
-        webPreferences: {
-          nodeIntegration: true
-        }
-      })
-    })
-
-    afterEach(() => {
-      return closeWindow(window).then(() => { window = null })
-    })
-
-    it('always returns the internal electron module', (done) => {
-      ipcMain.once('answer', () => done())
-      window.loadFile(path.join(__dirname, 'fixtures', 'api', 'electron-module-app', 'index.html'))
+    it('always returns the internal electron module', () => {
+      require('electron')
     })
   })
 })
 
 describe('app module', () => {
-  let server, secureUrl
-  const certPath = path.join(__dirname, 'fixtures', 'certificates')
+  let server: https.Server
+  let secureUrl: string
+  const certPath = path.join(fixturesPath, 'certificates')
 
   before((done) => {
     const options = {
@@ -69,7 +48,7 @@ describe('app module', () => {
     }
 
     server = https.createServer(options, (req, res) => {
-      if (req.client.authorized) {
+      if ((req as any).client.authorized) {
         res.writeHead(200)
         res.end('<title>authorized</title>')
       } else {
@@ -79,7 +58,7 @@ describe('app module', () => {
     })
 
     server.listen(0, '127.0.0.1', () => {
-      const port = server.address().port
+      const port = (server.address() as net.AddressInfo).port
       secureUrl = `https://127.0.0.1:${port}`
       done()
     })
@@ -107,13 +86,13 @@ describe('app module', () => {
 
   describe('app.getName()', () => {
     it('returns the name field of package.json', () => {
-      expect(app.getName()).to.equal('Electron Test')
+      expect(app.getName()).to.equal('Electron Test Main')
     })
   })
 
   describe('app.setName(name)', () => {
     it('overrides the name', () => {
-      expect(app.getName()).to.equal('Electron Test')
+      expect(app.getName()).to.equal('Electron Test Main')
       app.setName('test-name')
 
       expect(app.getName()).to.equal('test-name')
@@ -123,7 +102,7 @@ describe('app module', () => {
 
   describe('app.getLocale()', () => {
     it('should not be empty', () => {
-      expect(app.getLocale()).to.not.be.empty()
+      expect(app.getLocale()).to.not.equal('')
     })
   })
 
@@ -140,7 +119,7 @@ describe('app module', () => {
 
   describe('app.isPackaged', () => {
     it('should be false durings tests', () => {
-      expect(app.isPackaged).to.be.false()
+      expect(app.isPackaged).equal(false)
     })
   })
 
@@ -152,23 +131,23 @@ describe('app module', () => {
     })
 
     it('should be false during tests', () => {
-      expect(app.isInApplicationsFolder()).to.be.false()
+      expect(app.isInApplicationsFolder()).to.equal(false)
     })
   })
 
   describe('app.exit(exitCode)', () => {
-    let appProcess = null
+    let appProcess: cp.ChildProcess | null = null
 
     afterEach(() => {
-      if (appProcess != null) appProcess.kill()
+      if (appProcess) appProcess.kill()
     })
 
     it('emits a process exit event with the code', async () => {
-      const appPath = path.join(__dirname, 'fixtures', 'api', 'quit-app')
-      const electronPath = remote.getGlobal('process').execPath
+      const appPath = path.join(fixturesPath, 'api', 'quit-app')
+      const electronPath = process.execPath
       let output = ''
 
-      appProcess = ChildProcess.spawn(electronPath, [appPath])
+      appProcess = cp.spawn(electronPath, [appPath])
       appProcess.stdout.on('data', data => { output += data })
       const [code] = await emittedOnce(appProcess, 'close')
 
@@ -179,10 +158,10 @@ describe('app module', () => {
     })
 
     it('closes all windows', async function () {
-      const appPath = path.join(__dirname, 'fixtures', 'api', 'exit-closes-all-windows-app')
-      const electronPath = remote.getGlobal('process').execPath
+      const appPath = path.join(fixturesPath, 'api', 'exit-closes-all-windows-app')
+      const electronPath = process.execPath
 
-      appProcess = ChildProcess.spawn(electronPath, [appPath])
+      appProcess = cp.spawn(electronPath, [appPath])
       const [code, signal] = await emittedOnce(appProcess, 'close')
 
       expect(signal).to.equal(null, 'exit signal should be null, if you see this please tag @MarshallOfSound')
@@ -195,32 +174,32 @@ describe('app module', () => {
         return
       }
 
-      const electronPath = remote.getGlobal('process').execPath
-      const appPath = path.join(__dirname, 'fixtures', 'api', 'singleton')
-      appProcess = ChildProcess.spawn(electronPath, [appPath])
+      const electronPath = process.execPath
+      const appPath = path.join(fixturesPath, 'api', 'singleton')
+      appProcess = cp.spawn(electronPath, [appPath])
 
       // Singleton will send us greeting data to let us know it's running.
       // After that, ask it to exit gracefully and confirm that it does.
-      appProcess.stdout.on('data', data => appProcess.kill())
+      appProcess.stdout.on('data', data => appProcess && appProcess.kill())
       const [code, signal] = await emittedOnce(appProcess, 'close')
 
       const message = `code:\n${code}\nsignal:\n${signal}`
       expect(code).to.equal(0, message)
-      expect(signal).to.be.null(message)
+      expect(signal).to.equal(null, message)
     })
   })
 
   describe('app.requestSingleInstanceLock', () => {
     it('prevents the second launch of app', function (done) {
       this.timeout(120000)
-      const appPath = path.join(__dirname, 'fixtures', 'api', 'singleton')
-      const first = ChildProcess.spawn(remote.process.execPath, [appPath])
+      const appPath = path.join(fixturesPath, 'api', 'singleton')
+      const first = cp.spawn(process.execPath, [appPath])
       first.once('exit', code => {
         expect(code).to.equal(0)
       })
       // Start second app when received output.
       first.stdout.once('data', () => {
-        const second = ChildProcess.spawn(remote.process.execPath, [appPath])
+        const second = cp.spawn(process.execPath, [appPath])
         second.once('exit', code => {
           expect(code).to.equal(1)
           done()
@@ -257,7 +236,7 @@ describe('app module', () => {
   })
 
   describe('app.relaunch', () => {
-    let server = null
+    let server: net.Server | null = null
     const socketPath = process.platform === 'win32' ? '\\\\.\\pipe\\electron-app-relaunch' : '/tmp/electron-app-relaunch'
 
     beforeEach(done => {
@@ -269,7 +248,7 @@ describe('app module', () => {
     })
 
     afterEach((done) => {
-      server.close(() => {
+      server!.close(() => {
         if (process.platform === 'win32') {
           done()
         } else {
@@ -282,8 +261,8 @@ describe('app module', () => {
       this.timeout(120000)
 
       let state = 'none'
-      server.once('error', error => done(error))
-      server.on('connection', client => {
+      server!.once('error', error => done(error))
+      server!.on('connection', client => {
         client.once('data', data => {
           if (String(data) === 'false' && state === 'none') {
             state = 'first-launch'
@@ -295,8 +274,8 @@ describe('app module', () => {
         })
       })
 
-      const appPath = path.join(__dirname, 'fixtures', 'api', 'relaunch')
-      ChildProcess.spawn(remote.process.execPath, [appPath])
+      const appPath = path.join(fixturesPath, 'api', 'relaunch')
+      cp.spawn(process.execPath, [appPath])
     })
   })
 
@@ -313,61 +292,61 @@ describe('app module', () => {
     })
   })
 
-  xdescribe('app.importCertificate', () => {
-    let w = null
-
-    before(function () {
-      if (process.platform !== 'linux') {
-        this.skip()
-      }
-    })
-
-    afterEach(() => closeWindow(w).then(() => { w = null }))
-
-    it('can import certificate into platform cert store', done => {
-      const options = {
-        certificate: path.join(certPath, 'client.p12'),
-        password: 'electron'
-      }
-
-      w = new BrowserWindow({
-        show: false,
-        webPreferences: {
-          nodeIntegration: true
-        }
-      })
-
-      w.webContents.on('did-finish-load', () => {
-        expect(w.webContents.getTitle()).to.equal('authorized')
-        done()
-      })
-
-      ipcRenderer.once('select-client-certificate', (event, webContentsId, list) => {
-        expect(webContentsId).to.equal(w.webContents.id)
-        expect(list).to.have.lengthOf(1)
-
-        expect(list[0]).to.deep.equal({
-          issuerName: 'Intermediate CA',
-          subjectName: 'Client Cert',
-          issuer: { commonName: 'Intermediate CA' },
-          subject: { commonName: 'Client Cert' }
-        })
-
-        event.sender.send('client-certificate-response', list[0])
-      })
-
-      app.importCertificate(options, result => {
-        expect(result).toNotExist()
-        ipcRenderer.sendSync('set-client-certificate-option', false)
-        w.loadURL(secureUrl)
-      })
-    })
-  })
+  // xdescribe('app.importCertificate', () => {
+  //   let w = null
+
+  //   before(function () {
+  //     if (process.platform !== 'linux') {
+  //       this.skip()
+  //     }
+  //   })
+
+  //   afterEach(() => closeWindow(w).then(() => { w = null }))
+
+  //   it('can import certificate into platform cert store', done => {
+  //     const options = {
+  //       certificate: path.join(certPath, 'client.p12'),
+  //       password: 'electron'
+  //     }
+
+  //     w = new BrowserWindow({
+  //       show: false,
+  //       webPreferences: {
+  //         nodeIntegration: true
+  //       }
+  //     })
+
+  //     w.webContents.on('did-finish-load', () => {
+  //       expect(w.webContents.getTitle()).to.equal('authorized')
+  //       done()
+  //     })
+
+  //     ipcRenderer.once('select-client-certificate', (event, webContentsId, list) => {
+  //       expect(webContentsId).to.equal(w.webContents.id)
+  //       expect(list).to.have.lengthOf(1)
+
+  //       expect(list[0]).to.deep.equal({
+  //         issuerName: 'Intermediate CA',
+  //         subjectName: 'Client Cert',
+  //         issuer: { commonName: 'Intermediate CA' },
+  //         subject: { commonName: 'Client Cert' }
+  //       })
+
+  //       event.sender.send('client-certificate-response', list[0])
+  //     })
+
+  //     app.importCertificate(options, result => {
+  //       expect(result).toNotExist()
+  //       ipcRenderer.sendSync('set-client-certificate-option', false)
+  //       w.loadURL(secureUrl)
+  //     })
+  //   })
+  // })
 
   describe('BrowserWindow events', () => {
-    let w = null
+    let w: BrowserWindow = null as any
 
-    afterEach(() => closeWindow(w).then(() => { w = null }))
+    afterEach(() => closeWindow(w).then(() => { w = null as any }))
 
     it('should emit browser-window-focus event when window is focused', (done) => {
       app.once('browser-window-focus', (e, window) => {
@@ -530,7 +509,7 @@ describe('app module', () => {
     const platformIsSupported = !platformIsNotSupported
 
     const expectedBadgeCount = 42
-    let returnValue = null
+    let returnValue: boolean | null = null
 
     beforeEach(() => { returnValue = app.setBadgeCount(expectedBadgeCount) })
 
@@ -547,7 +526,7 @@ describe('app module', () => {
       })
 
       it('returns true', () => {
-        expect(returnValue).to.be.true()
+        expect(returnValue).to.equal(true)
       })
 
       it('sets a badge count', () => {
@@ -563,7 +542,7 @@ describe('app module', () => {
       })
 
       it('returns false', () => {
-        expect(returnValue).to.be.false()
+        expect(returnValue).to.equal(false)
       })
 
       it('does not set a badge count', () => {
@@ -618,28 +597,28 @@ describe('app module', () => {
     })
 
     it('correctly sets and unsets the LoginItem', function () {
-      expect(app.getLoginItemSettings().openAtLogin).to.be.false()
+      expect(app.getLoginItemSettings().openAtLogin).to.equal(false)
 
       app.setLoginItemSettings({ openAtLogin: true })
-      expect(app.getLoginItemSettings().openAtLogin).to.be.true()
+      expect(app.getLoginItemSettings().openAtLogin).to.equal(true)
 
       app.setLoginItemSettings({ openAtLogin: false })
-      expect(app.getLoginItemSettings().openAtLogin).to.be.false()
+      expect(app.getLoginItemSettings().openAtLogin).to.equal(false)
     })
 
     it('correctly sets and unsets the LoginItem as hidden', function () {
       if (process.platform !== 'darwin') this.skip()
 
-      expect(app.getLoginItemSettings().openAtLogin).to.be.false()
-      expect(app.getLoginItemSettings().openAsHidden).to.be.false()
+      expect(app.getLoginItemSettings().openAtLogin).to.equal(false)
+      expect(app.getLoginItemSettings().openAsHidden).to.equal(false)
 
       app.setLoginItemSettings({ openAtLogin: true, openAsHidden: true })
-      expect(app.getLoginItemSettings().openAtLogin).to.be.true()
-      expect(app.getLoginItemSettings().openAsHidden).to.be.true()
+      expect(app.getLoginItemSettings().openAtLogin).to.equal(true)
+      expect(app.getLoginItemSettings().openAsHidden).to.equal(true)
 
       app.setLoginItemSettings({ openAtLogin: true, openAsHidden: false })
-      expect(app.getLoginItemSettings().openAtLogin).to.be.true()
-      expect(app.getLoginItemSettings().openAsHidden).to.be.false()
+      expect(app.getLoginItemSettings().openAtLogin).to.equal(true)
+      expect(app.getLoginItemSettings().openAsHidden).to.equal(false)
     })
 
     it('allows you to pass a custom executable and arguments', function () {
@@ -647,11 +626,11 @@ describe('app module', () => {
 
       app.setLoginItemSettings({ openAtLogin: true, path: updateExe, args: processStartArgs })
 
-      expect(app.getLoginItemSettings().openAtLogin).to.be.false()
+      expect(app.getLoginItemSettings().openAtLogin).to.equal(false)
       expect(app.getLoginItemSettings({
         path: updateExe,
         args: processStartArgs
-      }).openAtLogin).to.be.true()
+      }).openAtLogin).to.equal(true)
     })
   })
 
@@ -684,7 +663,7 @@ describe('app module', () => {
   })
 
   describe('select-client-certificate event', () => {
-    let w = null
+    let w: BrowserWindow
 
     before(function () {
       if (process.platform === 'linux') {
@@ -702,16 +681,16 @@ describe('app module', () => {
       })
     })
 
-    afterEach(() => closeWindow(w).then(() => { w = null }))
+    afterEach(() => closeWindow(w).then(() => { w = null as any }))
 
-    it('can respond with empty certificate list', done => {
-      w.webContents.on('did-finish-load', () => {
-        expect(w.webContents.getTitle()).to.equal('denied')
-        done()
+    it('can respond with empty certificate list', async () => {
+      app.once('select-client-certificate', function (event, webContents, url, list, callback) {
+        console.log('select-client-certificate emitted')
+        event.preventDefault()
+        callback()
       })
-
-      ipcRenderer.sendSync('set-client-certificate-option', true)
-      w.webContents.loadURL(secureUrl)
+      await w.webContents.loadURL(secureUrl)
+      expect(w.webContents.getTitle()).to.equal('denied')
     })
   })
 
@@ -723,8 +702,8 @@ describe('app module', () => {
       '--process-start-args', `"--hidden"`
     ]
 
-    let Winreg
-    let classesKey
+    let Winreg: any
+    let classesKey: any
 
     before(function () {
       if (process.platform !== 'win32') {
@@ -761,34 +740,34 @@ describe('app module', () => {
 
     afterEach(() => {
       app.removeAsDefaultProtocolClient(protocol)
-      expect(app.isDefaultProtocolClient(protocol)).to.be.false()
+      expect(app.isDefaultProtocolClient(protocol)).to.equal(false)
 
       app.removeAsDefaultProtocolClient(protocol, updateExe, processStartArgs)
-      expect(app.isDefaultProtocolClient(protocol, updateExe, processStartArgs)).to.be.false()
+      expect(app.isDefaultProtocolClient(protocol, updateExe, processStartArgs)).to.equal(false)
     })
 
     it('sets the app as the default protocol client', () => {
-      expect(app.isDefaultProtocolClient(protocol)).to.be.false()
+      expect(app.isDefaultProtocolClient(protocol)).to.equal(false)
       app.setAsDefaultProtocolClient(protocol)
-      expect(app.isDefaultProtocolClient(protocol)).to.be.true()
+      expect(app.isDefaultProtocolClient(protocol)).to.equal(true)
     })
 
     it('allows a custom path and args to be specified', () => {
-      expect(app.isDefaultProtocolClient(protocol, updateExe, processStartArgs)).to.be.false()
+      expect(app.isDefaultProtocolClient(protocol, updateExe, processStartArgs)).to.equal(false)
       app.setAsDefaultProtocolClient(protocol, updateExe, processStartArgs)
 
-      expect(app.isDefaultProtocolClient(protocol, updateExe, processStartArgs)).to.be.true()
-      expect(app.isDefaultProtocolClient(protocol)).to.be.false()
+      expect(app.isDefaultProtocolClient(protocol, updateExe, processStartArgs)).to.equal(true)
+      expect(app.isDefaultProtocolClient(protocol)).to.equal(false)
     })
 
     it('creates a registry entry for the protocol class', (done) => {
       app.setAsDefaultProtocolClient(protocol)
 
-      classesKey.keys((error, keys) => {
+      classesKey.keys((error: Error, keys: any[]) => {
         if (error) throw error
 
         const exists = !!keys.find(key => key.key.includes(protocol))
-        expect(exists).to.be.true()
+        expect(exists).to.equal(true)
 
         done()
       })
@@ -798,11 +777,11 @@ describe('app module', () => {
       app.setAsDefaultProtocolClient(protocol)
       app.removeAsDefaultProtocolClient(protocol)
 
-      classesKey.keys((error, keys) => {
+      classesKey.keys((error: Error, keys: any[]) => {
         if (error) throw error
 
         const exists = !!keys.find(key => key.key.includes(protocol))
-        expect(exists).to.be.false()
+        expect(exists).to.equal(false)
 
         done()
       })
@@ -819,11 +798,11 @@ describe('app module', () => {
       protocolKey.set('test-value', 'REG_BINARY', '123', () => {
         app.removeAsDefaultProtocolClient(protocol)
 
-        classesKey.keys((error, keys) => {
+        classesKey.keys((error: Error, keys: any[]) => {
           if (error) throw error
 
           const exists = !!keys.find(key => key.key.includes(protocol))
-          expect(exists).to.be.true()
+          expect(exists).to.equal(true)
 
           done()
         })
@@ -839,9 +818,9 @@ describe('app module', () => {
     })
 
     it('does not launch for argument following a URL', done => {
-      const appPath = path.join(__dirname, 'fixtures', 'api', 'quit-app')
+      const appPath = path.join(fixturesPath, 'api', 'quit-app')
       // App should exit with non 123 code.
-      const first = ChildProcess.spawn(remote.process.execPath, [appPath, 'electron-test:?', 'abc'])
+      const first = cp.spawn(process.execPath, [appPath, 'electron-test:?', 'abc'])
       first.once('exit', code => {
         expect(code).to.not.equal(123)
         done()
@@ -849,9 +828,9 @@ describe('app module', () => {
     })
 
     it('launches successfully for argument following a file path', done => {
-      const appPath = path.join(__dirname, 'fixtures', 'api', 'quit-app')
+      const appPath = path.join(fixturesPath, 'api', 'quit-app')
       // App should exit with code 123.
-      const first = ChildProcess.spawn(remote.process.execPath, [appPath, 'e:\\abc', 'abc'])
+      const first = cp.spawn(process.execPath, [appPath, 'e:\\abc', 'abc'])
       first.once('exit', code => {
         expect(code).to.equal(123)
         done()
@@ -859,9 +838,9 @@ describe('app module', () => {
     })
 
     it('launches successfully for multiple URIs following --', done => {
-      const appPath = path.join(__dirname, 'fixtures', 'api', 'quit-app')
+      const appPath = path.join(fixturesPath, 'api', 'quit-app')
       // App should exit with code 123.
-      const first = ChildProcess.spawn(remote.process.execPath, [appPath, '--', 'http://electronjs.org', 'electron-test://testdata'])
+      const first = cp.spawn(process.execPath, [appPath, '--', 'http://electronjs.org', 'electron-test://testdata'])
       first.once('exit', code => {
         expect(code).to.equal(123)
         done()
@@ -869,7 +848,7 @@ describe('app module', () => {
     })
   })
 
-  describe('getFileIcon() API', (done) => {
+  describe('getFileIcon() API', () => {
     const iconPath = path.join(__dirname, 'fixtures/assets/icon.ico')
     const sizes = {
       small: 16,
@@ -888,13 +867,14 @@ describe('app module', () => {
 
     it('fetches a non-empty icon', async () => {
       const icon = await app.getFileIcon(iconPath)
-      expect(icon.isEmpty()).to.be.false()
+      expect(icon.isEmpty()).to.equal(false)
     })
 
     // TODO(codebytere): remove when promisification is complete
     it('fetches a non-empty icon (callback)', (done) => {
-      app.getFileIcon(iconPath, (icon) => {
-        expect(icon.isEmpty()).to.be.false()
+      app.getFileIcon(iconPath, (error, icon) => {
+        expect(error).to.equal(null)
+        expect(icon.isEmpty()).to.equal(false)
         done()
       })
     })
@@ -909,7 +889,8 @@ describe('app module', () => {
 
     // TODO(codebytere): remove when promisification is complete
     it('fetches normal icon size by default (callback)', (done) => {
-      app.getFileIcon(iconPath, (icon) => {
+      app.getFileIcon(iconPath, (error, icon) => {
+        expect(error).to.equal(null)
         const size = icon.getSize()
 
         expect(size.height).to.equal(sizes.normal)
@@ -937,7 +918,8 @@ describe('app module', () => {
 
       // TODO(codebytere): remove when promisification is complete
       it('fetches a normal icon (callback)', (done) => {
-        app.getFileIcon(iconPath, { size: 'normal' }, (icon) => {
+        app.getFileIcon(iconPath, { size: 'normal' }, (error, icon) => {
+          expect(error).to.equal(null)
           const size = icon.getSize()
 
           expect(size.height).to.equal(sizes.normal)
@@ -967,11 +949,11 @@ describe('app module', () => {
       const types = []
       for (const { pid, type, cpu } of appMetrics) {
         expect(pid).to.be.above(0, 'pid is not > 0')
-        expect(type).to.be.a('string').that.is.not.empty()
+        expect(type).to.be.a('string').that.does.not.equal('')
 
         types.push(type)
-        expect(cpu).to.have.own.property('percentCPUUsage').that.is.a('number')
-        expect(cpu).to.have.own.property('idleWakeupsPerSecond').that.is.a('number')
+        expect(cpu).to.have.ownProperty('percentCPUUsage').that.is.a('number')
+        expect(cpu).to.have.ownProperty('idleWakeupsPerSecond').that.is.a('number')
       }
 
       if (process.platform === 'darwin') {
@@ -979,23 +961,22 @@ describe('app module', () => {
       }
 
       expect(types).to.include('Browser')
-      expect(types).to.include('Tab')
     })
   })
 
   describe('getGPUFeatureStatus() API', () => {
     it('returns the graphic features statuses', () => {
       const features = app.getGPUFeatureStatus()
-      expect(features).to.have.own.property('webgl').that.is.a('string')
-      expect(features).to.have.own.property('gpu_compositing').that.is.a('string')
+      expect(features).to.have.ownProperty('webgl').that.is.a('string')
+      expect(features).to.have.ownProperty('gpu_compositing').that.is.a('string')
     })
   })
 
   describe('getGPUInfo() API', () => {
-    const appPath = path.join(__dirname, 'fixtures', 'api', 'gpu-info.js')
+    const appPath = path.join(fixturesPath, 'api', 'gpu-info.js')
 
-    const getGPUInfo = async (type) => {
-      const appProcess = ChildProcess.spawn(remote.process.execPath, [appPath, type])
+    const getGPUInfo = async (type: string) => {
+      const appProcess = cp.spawn(process.execPath, [appPath, type])
       let gpuInfoData = ''
       let errorData = ''
       appProcess.stdout.on('data', (data) => {
@@ -1013,11 +994,11 @@ describe('app module', () => {
         return Promise.reject(new Error(errorData))
       }
     }
-    const verifyBasicGPUInfo = async (gpuInfo) => {
+    const verifyBasicGPUInfo = async (gpuInfo: any) => {
       // Devices information is always present in the available info.
-      expect(gpuInfo).to.have.own.property('gpuDevice')
+      expect(gpuInfo).to.have.ownProperty('gpuDevice')
         .that.is.an('array')
-        .and.is.not.empty()
+        .and.does.not.equal([])
 
       const device = gpuInfo.gpuDevice[0]
       expect(device).to.be.an('object')
@@ -1040,11 +1021,11 @@ describe('app module', () => {
         expect(completeInfo).to.deep.equal(basicInfo)
       } else {
         // Gl version is present in the complete info.
-        expect(completeInfo).to.have.own.property('auxAttributes')
+        expect(completeInfo).to.have.ownProperty('auxAttributes')
           .that.is.an('object')
-        expect(completeInfo.auxAttributes).to.have.own.property('glVersion')
+        expect(completeInfo.auxAttributes).to.have.ownProperty('glVersion')
           .that.is.a('string')
-          .and.not.empty()
+          .and.does.not.equal([])
       }
     })
 
@@ -1056,8 +1037,8 @@ describe('app module', () => {
   })
 
   describe('sandbox options', () => {
-    let appProcess = null
-    let server = null
+    let appProcess: cp.ChildProcess = null as any
+    let server: net.Server = null as any
     const socketPath = process.platform === 'win32' ? '\\\\.\\pipe\\electron-mixed-sandbox' : '/tmp/electron-mixed-sandbox'
 
     beforeEach(function (done) {
@@ -1097,22 +1078,22 @@ describe('app module', () => {
 
     describe('when app.enableSandbox() is called', () => {
       it('adds --enable-sandbox to all renderer processes', done => {
-        const appPath = path.join(__dirname, 'fixtures', 'api', 'mixed-sandbox-app')
-        appProcess = ChildProcess.spawn(remote.process.execPath, [appPath, '--app-enable-sandbox'])
+        const appPath = path.join(fixturesPath, 'api', 'mixed-sandbox-app')
+        appProcess = cp.spawn(process.execPath, [appPath, '--app-enable-sandbox'])
 
         server.once('error', error => { done(error) })
 
         server.on('connection', client => {
-          client.once('data', data => {
-            const argv = JSON.parse(data)
+          client.once('data', (data) => {
+            const argv = JSON.parse(data.toString())
             expect(argv.sandbox).to.include('--enable-sandbox')
             expect(argv.sandbox).to.not.include('--no-sandbox')
 
             expect(argv.noSandbox).to.include('--enable-sandbox')
             expect(argv.noSandbox).to.not.include('--no-sandbox')
 
-            expect(argv.noSandboxDevtools).to.be.true()
-            expect(argv.sandboxDevtools).to.be.true()
+            expect(argv.noSandboxDevtools).to.equal(true)
+            expect(argv.sandboxDevtools).to.equal(true)
 
             done()
           })
@@ -1122,22 +1103,22 @@ describe('app module', () => {
 
     describe('when the app is launched with --enable-sandbox', () => {
       it('adds --enable-sandbox to all renderer processes', done => {
-        const appPath = path.join(__dirname, 'fixtures', 'api', 'mixed-sandbox-app')
-        appProcess = ChildProcess.spawn(remote.process.execPath, [appPath, '--enable-sandbox'])
+        const appPath = path.join(fixturesPath, 'api', 'mixed-sandbox-app')
+        appProcess = cp.spawn(process.execPath, [appPath, '--enable-sandbox'])
 
         server.once('error', error => { done(error) })
 
         server.on('connection', client => {
           client.once('data', data => {
-            const argv = JSON.parse(data)
+            const argv = JSON.parse(data.toString())
             expect(argv.sandbox).to.include('--enable-sandbox')
             expect(argv.sandbox).to.not.include('--no-sandbox')
 
             expect(argv.noSandbox).to.include('--enable-sandbox')
             expect(argv.noSandbox).to.not.include('--no-sandbox')
 
-            expect(argv.noSandboxDevtools).to.be.true()
-            expect(argv.sandboxDevtools).to.be.true()
+            expect(argv.noSandboxDevtools).to.equal(true)
+            expect(argv.sandboxDevtools).to.equal(true)
 
             done()
           })
@@ -1156,6 +1137,10 @@ describe('app module', () => {
 
   const dockDescribe = process.platform === 'darwin' ? describe : describe.skip
   dockDescribe('dock APIs', () => {
+    after(async () => {
+      await app.dock.show()
+    })
+
     describe('dock.setMenu', () => {
       it('can be retrieved via dock.getMenu', () => {
         expect(app.dock.getMenu()).to.equal(null)
@@ -1173,7 +1158,7 @@ describe('app module', () => {
 
     describe('dock.bounce', () => {
       it('should return -1 for unknown bounce type', () => {
-        expect(app.dock.bounce('bad type')).to.equal(-1)
+        expect(app.dock.bounce('bad type' as any)).to.equal(-1)
       })
 
       it('should return a positive number for informational type', () => {
@@ -1223,8 +1208,8 @@ describe('app module', () => {
         expect(app.dock.show()).to.be.a('promise')
       })
 
-      it('eventually fulfills', () => {
-        expect(app.dock.show()).to.be.eventually.fulfilled()
+      it('eventually fulfills', async () => {
+        await expect(app.dock.show()).to.eventually.be.fulfilled.equal(undefined)
       })
     })
 
@@ -1242,31 +1227,31 @@ describe('app module', () => {
     })
 
     it('becomes fulfilled if the app is already ready', () => {
-      expect(app.isReady()).to.be.true()
-      expect(app.whenReady()).to.be.eventually.fulfilled()
+      expect(app.isReady()).to.equal(true)
+      expect(app.whenReady()).to.be.eventually.fulfilled.equal(undefined)
     })
   })
 
   describe('commandLine.hasSwitch', () => {
     it('returns true when present', () => {
       app.commandLine.appendSwitch('foobar1')
-      expect(app.commandLine.hasSwitch('foobar1')).to.be.true()
+      expect(app.commandLine.hasSwitch('foobar1')).to.equal(true)
     })
 
     it('returns false when not present', () => {
-      expect(app.commandLine.hasSwitch('foobar2')).to.be.false()
+      expect(app.commandLine.hasSwitch('foobar2')).to.equal(false)
     })
   })
 
   describe('commandLine.hasSwitch (existing argv)', () => {
     it('returns true when present', async () => {
       const { hasSwitch } = await runTestApp('command-line', '--foobar')
-      expect(hasSwitch).to.be.true()
+      expect(hasSwitch).to.equal(true)
     })
 
     it('returns false when not present', async () => {
       const { hasSwitch } = await runTestApp('command-line')
-      expect(hasSwitch).to.be.false()
+      expect(hasSwitch).to.equal(false)
     })
   })
 
@@ -1335,9 +1320,9 @@ describe('default behavior', () => {
   })
 })
 
-async function runTestApp (name, ...args) {
-  const appPath = path.join(__dirname, 'fixtures', 'api', name)
-  const electronPath = remote.getGlobal('process').execPath
+async function runTestApp (name: string, ...args: any[]) {
+  const appPath = path.join(fixturesPath, 'api', name)
+  const electronPath = process.execPath
   const appProcess = cp.spawn(electronPath, [appPath, ...args])
 
   let output = ''

+ 40 - 0
spec-main/events-helpers.ts

@@ -0,0 +1,40 @@
+import { EventEmitter } from "electron";
+
+/**
+ * @fileoverview A set of helper functions to make it easier to work
+ * with events in async/await manner.
+ */
+
+/**
+ * @param {!EventTarget} target
+ * @param {string} eventName
+ * @return {!Promise<!Event>}
+ */
+export const waitForEvent = (target: EventTarget, eventName: string) => {
+  return new Promise(resolve => {
+    target.addEventListener(eventName, resolve, { once: true })
+  })
+}
+
+/**
+ * @param {!EventEmitter} emitter
+ * @param {string} eventName
+ * @return {!Promise<!Array>} With Event as the first item.
+ */
+export const emittedOnce = (emitter: EventEmitter, eventName: string) => {
+  return emittedNTimes(emitter, eventName, 1).then(([result]) => result)
+}
+
+export const emittedNTimes = (emitter: EventEmitter, eventName: string, times: number) => {
+  const events: any[][] = []
+  return new Promise<any[][]>(resolve => {
+    const handler = (...args: any[]) => {
+      events.push(args)
+      if (events.length === times) {
+        emitter.removeListener(eventName, handler)
+        resolve(events)
+      }
+    }
+    emitter.on(eventName, handler)
+  })
+}

+ 90 - 0
spec-main/index.js

@@ -0,0 +1,90 @@
+const Module = require('module')
+const path = require('path')
+const v8 = require('v8')
+
+Module.globalPaths.push(path.resolve(__dirname, '../spec/node_modules'))
+
+// We want to terminate on errors, not throw up a dialog
+process.on('uncaughtException', (err) => {
+  console.error('Unhandled exception in main spec runner:', err)
+  process.exit(1)
+})
+
+// Tell ts-node which tsconfig to use
+process.env.TS_NODE_PROJECT = path.resolve(__dirname, '../tsconfig.spec.json')
+
+const { app } = require('electron')
+
+v8.setFlagsFromString('--expose_gc')
+app.commandLine.appendSwitch('js-flags', '--expose_gc')
+// Prevent the spec runner quiting when the first window closes
+app.on('window-all-closed', () => null)
+// TODO: This API should _probably_ only be enabled for the specific test that needs it
+// not the entire test suite
+app.commandLine.appendSwitch('ignore-certificate-errors')
+
+app.whenReady().then(() => {
+  require('ts-node/register')
+
+  const argv = require('yargs')
+    .boolean('ci')
+    .string('g').alias('g', 'grep')
+    .boolean('i').alias('i', 'invert')
+    .argv
+
+  const isCi = !!argv.ci
+  global.isCI = isCi
+
+  const Mocha = require('mocha')
+  const mochaOptions = {}
+  if (process.env.MOCHA_REPORTER) {
+    mochaOptions.reporter = process.env.MOCHA_REPORTER
+  }
+  if (process.env.MOCHA_MULTI_REPORTERS) {
+    mochaOptions.reporterOptions = {
+      reporterEnabled: process.env.MOCHA_MULTI_REPORTERS
+    }
+  }
+  const mocha = new Mocha(mochaOptions)
+
+  if (!process.env.MOCHA_REPORTER) {
+    mocha.ui('bdd').reporter('tap')
+  }
+  mocha.timeout(isCi ? 30000 : 10000)
+
+  if (argv.grep) mocha.grep(argv.grep)
+  if (argv.invert) mocha.invert()
+
+  // Read all test files.
+  const walker = require('walkdir').walk(__dirname, {
+    no_recurse: true
+  })
+
+  // This allows you to run specific modules only:
+  // npm run test -match=menu
+  const moduleMatch = process.env.npm_config_match
+    ? new RegExp(process.env.npm_config_match, 'g')
+    : null
+
+  walker.on('file', (file) => {
+    if (/-spec\.[tj]s$/.test(file) &&
+        (!moduleMatch || moduleMatch.test(file))) {
+      mocha.addFile(file)
+    }
+  })
+
+  walker.on('end', () => {
+    const runner = mocha.run(() => {
+      if (isCi && runner.hasOnly) {
+        try {
+          throw new Error('A spec contains a call to it.only or describe.only and should be reverted.')
+        } catch (error) {
+          console.error(error.stack || error)
+        }
+        process.exit(1)
+      }
+
+      process.exit(runner.failures)
+    })
+  })
+})

+ 6 - 0
spec-main/package.json

@@ -0,0 +1,6 @@
+{
+  "name": "electron-test-main",
+  "productName": "Electron Test Main",
+  "main": "index.js",
+  "version": "0.1.0"
+}

+ 26 - 0
spec-main/window-helpers.ts

@@ -0,0 +1,26 @@
+import { expect } from 'chai'
+import { BrowserWindow, WebContents } from 'electron'
+import { emittedOnce } from './events-helpers';
+
+export const closeWindow = async (
+  window: BrowserWindow | null = null,
+  { assertNotWindows } = { assertNotWindows: true }
+) => {
+  if (window && !window.isDestroyed()) {
+    const isClosed = emittedOnce(window, 'closed')
+    window.setClosable(true)
+    window.close()
+    await isClosed
+  }
+
+  if (assertNotWindows) {
+    expect(BrowserWindow.getAllWindows()).to.have.lengthOf(0)
+  }
+}
+
+exports.waitForWebContentsToLoad = async (webContents: WebContents) => {
+  const didFinishLoadPromise = emittedOnce(webContents, 'did-finish-load')
+  if (webContents.isLoadingMainFrame()) {
+    await didFinishLoadPromise
+  }
+}

+ 31 - 29
spec/package-lock.json

@@ -8,7 +8,6 @@
       "version": "github:nornagon/node-abstractsocket#7d9c770f9ffef14373349034f8820ff059879845",
       "from": "github:nornagon/node-abstractsocket#v8-compat",
       "dev": true,
-      "optional": true,
       "requires": {
         "bindings": "^1.2.1",
         "nan": "^2.0.9"
@@ -67,7 +66,6 @@
       "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.4.0.tgz",
       "integrity": "sha512-7znEVX22Djn+nYjxCWKDne0RRloa9XfYa84yk3s+HkE3LpDYZmhArYr9O9huBoHY3/oXispx5LorIX7Sl2CgSQ==",
       "dev": true,
-      "optional": true,
       "requires": {
         "file-uri-to-path": "1.0.0"
       }
@@ -230,7 +228,7 @@
     },
     "commander": {
       "version": "2.15.1",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
+      "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
       "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
       "dev": true
     },
@@ -243,14 +241,12 @@
     "console-control-strings": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
-      "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
-      "optional": true
+      "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
     },
     "core-util-is": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
-      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
-      "optional": true
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
     },
     "cross-spawn": {
       "version": "6.0.5",
@@ -285,6 +281,19 @@
         "put": "0.0.6",
         "safe-buffer": "^5.1.1",
         "xml2js": "^0.4.17"
+      },
+      "dependencies": {
+        "abstract-socket": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/abstract-socket/-/abstract-socket-2.0.0.tgz",
+          "integrity": "sha1-2DyT598w0n4j8+gqdj5/XnjZFvk=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "bindings": "^1.2.1",
+            "nan": "^2.0.9"
+          }
+        }
       }
     },
     "debug": {
@@ -364,7 +373,7 @@
     },
     "duplexer": {
       "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
+      "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
       "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
       "dev": true
     },
@@ -459,8 +468,7 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
       "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
-      "dev": true,
-      "optional": true
+      "dev": true
     },
     "find-up": {
       "version": "3.0.0",
@@ -672,8 +680,7 @@
     "isarray": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
-      "optional": true
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
     },
     "isexe": {
       "version": "2.0.0",
@@ -778,13 +785,12 @@
     },
     "minimist": {
       "version": "1.2.0",
-      "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
-      "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
-      "optional": true
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+      "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
     },
     "mkdirp": {
       "version": "0.5.1",
-      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
       "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
       "requires": {
         "minimist": "0.0.8"
@@ -792,7 +798,7 @@
       "dependencies": {
         "minimist": {
           "version": "0.0.8",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
           "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
         }
       }
@@ -1043,7 +1049,7 @@
     },
     "path-is-absolute": {
       "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
       "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
       "dev": true
     },
@@ -1061,7 +1067,7 @@
     },
     "pause-stream": {
       "version": "0.0.11",
-      "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
+      "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
       "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=",
       "dev": true,
       "requires": {
@@ -1100,8 +1106,7 @@
     "process-nextick-args": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
-      "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
-      "optional": true
+      "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
     },
     "pump": {
       "version": "2.0.1",
@@ -1151,9 +1156,8 @@
     },
     "readable-stream": {
       "version": "2.3.6",
-      "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
       "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
-      "optional": true,
       "requires": {
         "core-util-is": "~1.0.0",
         "inherits": "~2.0.3",
@@ -1363,7 +1367,6 @@
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
       "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-      "optional": true,
       "requires": {
         "safe-buffer": "~5.1.0"
       }
@@ -1378,7 +1381,7 @@
     },
     "strip-eof": {
       "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+      "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
       "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
       "dev": true
     },
@@ -1447,7 +1450,7 @@
     },
     "through": {
       "version": "2.3.8",
-      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
       "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
       "dev": true
     },
@@ -1490,8 +1493,7 @@
     "util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
-      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
-      "optional": true
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
     },
     "walkdir": {
       "version": "0.3.2",
@@ -1543,7 +1545,7 @@
     },
     "wrap-ansi": {
       "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+      "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
       "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
       "dev": true,
       "requires": {

+ 10 - 0
tsconfig.spec.json

@@ -0,0 +1,10 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "rootDir": "spec-main"
+  },
+  "include": [
+    "spec-main",
+    "typings"
+  ]
+}

+ 1 - 0
typings/internal-ambient.d.ts

@@ -13,6 +13,7 @@ declare namespace NodeJS {
   interface V8UtilBinding {
     getHiddenValue<T>(obj: any, key: string): T;
     setHiddenValue<T>(obj: any, key: string, value: T): void;
+    requestGarbageCollectionForTesting(): void;
   }
   interface Process {
     /**