[
  {
    "path": ".all-contributorsrc",
    "content": "{\n  \"files\": [\n    \"README.md\"\n  ],\n  \"imageSize\": 100,\n  \"commit\": false,\n  \"contributors\": [\n    {\n      \"login\": \"duncanbeevers\",\n      \"name\": \"Duncan Beevers\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/7367?v=4\",\n      \"profile\": \"http://www.duncanbeevers.com\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"M4cs\",\n      \"name\": \"Max Bridgland\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/34947910?v=4\",\n      \"profile\": \"https://github.com/M4cs\",\n      \"contributions\": [\n        \"doc\",\n        \"ideas\",\n        \"bug\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"yurm04\",\n      \"name\": \"Yuraima Estevez\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/4642404?v=4\",\n      \"profile\": \"https://github.com/yurm04\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"jake-nz\",\n      \"name\": \"Jake Crosby\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/437471?v=4\",\n      \"profile\": \"http://jake.nz\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"gavinhenderson\",\n      \"name\": \"Gavin Henderson\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/1359202?v=4\",\n      \"profile\": \"http://gavinhenderson.me\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"briwa\",\n      \"name\": \"briwa\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/8046636?v=4\",\n      \"profile\": \"https://briwa.github.io\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Luanf\",\n      \"name\": \"Luan Ferreira\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/9099705?v=4\",\n      \"profile\": \"https://github.com/Luanf\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"cse-tushar\",\n      \"name\": \"Tushar Gupta\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/12570521?v=4\",\n      \"profile\": \"https://github.com/cse-tushar\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"agustif\",\n      \"name\": \"Agusti Fernandez\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/6601142?v=4\",\n      \"profile\": \"https://agu.st/\",\n      \"contributions\": [\n        \"code\",\n        \"ideas\"\n      ]\n    },\n    {\n      \"login\": \"moos\",\n      \"name\": \"Moos\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/233047?v=4\",\n      \"profile\": \"http://blog.42at.com\",\n      \"contributions\": [\n        \"bug\",\n        \"code\",\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"MacZel\",\n      \"name\": \"MacZel\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/25805810?v=4\",\n      \"profile\": \"http://maciejzelek.space\",\n      \"contributions\": [\n        \"code\",\n        \"ideas\"\n      ]\n    },\n    {\n      \"login\": \"krazylegz\",\n      \"name\": \"Vikram Dighe\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/36250?v=4\",\n      \"profile\": \"https://github.com/krazylegz\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"jsmey\",\n      \"name\": \"John Smey\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/10177710?v=4\",\n      \"profile\": \"https://github.com/jsmey\",\n      \"contributions\": [\n        \"code\",\n        \"ideas\",\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"BuckAMayzing\",\n      \"name\": \"BuckAMayzing\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/19292614?v=4\",\n      \"profile\": \"https://github.com/BuckAMayzing\",\n      \"contributions\": [\n        \"code\",\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"rahulakrishna\",\n      \"name\": \"Rahul A. Krishna\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/10240002?v=4\",\n      \"profile\": \"http://rahulakrishna.github.io\",\n      \"contributions\": [\n        \"code\",\n        \"ideas\",\n        \"tool\"\n      ]\n    },\n    {\n      \"login\": \"amilajack\",\n      \"name\": \"Amila Welihinda\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/6374832?v=4\",\n      \"profile\": \"https://amilajack.com\",\n      \"contributions\": [\n        \"infra\"\n      ]\n    },\n    {\n      \"login\": \"gregveres\",\n      \"name\": \"gregveres\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/12899823?v=4\",\n      \"profile\": \"https://github.com/gregveres\",\n      \"contributions\": [\n        \"bug\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"adamkleingit\",\n      \"name\": \"adam klein\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/889418?v=4\",\n      \"profile\": \"http://adamklein.dev\",\n      \"contributions\": [\n        \"test\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"rbarbazz\",\n      \"name\": \"Raphaël Barbazza\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/42906704?v=4\",\n      \"profile\": \"http://www.raphaelbarbazza.com\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"philals\",\n      \"name\": \"Phil Alsford\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/8849355?v=4\",\n      \"profile\": \"https://philalsford.com\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    }\n  ],\n  \"contributorsPerLine\": 7,\n  \"projectName\": \"majestic\",\n  \"projectOwner\": \"Raathigesh\",\n  \"repoType\": \"github\",\n  \"repoHost\": \"https://github.com\",\n  \"skipCi\": true\n}\n"
  },
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\n    \"@babel/preset-react\",\n    [\n      \"@babel/preset-typescript\",\n      {\n        \"isTSX\": true,\n        \"allExtensions\": true\n      }\n    ],\n    \"@babel/preset-env\"\n  ],\n  \"plugins\": [\n    [\"@babel/plugin-proposal-decorators\", { \"legacy\": true }],\n    [\"@babel/plugin-proposal-class-properties\", { \"loose\": true }],\n    \"@babel/plugin-proposal-object-rest-spread\",\n    \"babel-plugin-styled-components\"\n  ]\n}\n"
  },
  {
    "path": ".github/issue_template.md",
    "content": "## Is this a bug report or a feature request?\n\nPlease specify whether this is a feature request or a bug.\n\n## Version Info\n\n- Version of Majestic:\n- Version of Jest:\n- Version of Node:\n- Operating System:\n\n## Reproduction Repo\n\nIf this is a bug report, please provide a minimal github repository where this mentioned issue is reproducible. Majestic makes certain\nassumptions regarding the test setup and it's very hard to guess the issue without looking at the exact configurations that you are using.\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "daysUntilStale: 30\ndaysUntilClose: 7\nonlyLabels: [🦄 Need more info]\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. Thank you\n  for your contributions.\ncloseComment: false\n"
  },
  {
    "path": ".github/workflows/nodejs.yml",
    "content": "name: Node CI\n\non: [push]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [12.x]\n\n    steps:\n      - uses: actions/checkout@v1\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v1\n        with:\n          node-version: ${{ matrix.node-version }}\n      - name: npm install, build, and test\n        run: |\n          yarn install\n          yarn prod\n          yarn integration\n        env:\n          CI: true\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\ndist"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\"\n}\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n  // Use IntelliSense to learn about possible attributes.\n  // Hover to view descriptions of existing attributes.\n  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Launch Program\",\n      \"runtimeArgs\": [\"-r\", \"./node_modules/ts-node/register\"],\n      \"args\": [\"${workspaceFolder}/server/index.ts\", \"--noOpen\"],\n      \"console\": \"integratedTerminal\",\n      \"env\": {\n        \"TS_NODE_PROJECT\": \"./tsconfig.server.json\",\n        \"ROOT\": \"\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "CONTRIBUTING.MD",
    "content": "### Preparing Majestic\n\n- Clone this repository\n- Install dependencies with `yarn install`\n\n### Running Majestic\n\nMajestic has 2 main components as follows\n\n- The UI written in React JS and GraphQL\n  - The UI source is in `./ui` - Running `yarn ui` will start the webpack dev server\n- A Node JS GraphQL server\n  - The server source is in `./server`\n  - Create a sample projector use one of your project with Jest so you can test your changes\n  - If you are using VSCode, edit the `\\.vscode\\launch.json` file and change the `ROOT` directory to your sample project and then you can press `F5` to run the server.\n  - If you are not using VSCode, edit the `\\server\\services\\cli.ts` file and change the root path so you test with your sample project and then running `yarn watch-server` will start the server in watch mode\n\n## Running integration test\n\nWe have a couple of integration tests written using [Cypress](https://www.cypress.io/) available in the `./integration` folder.\n\nTo run the integration test\n\n- Do a production build by running `yarn prod`\n- `cd ./integration`\n- Run `yarn prepare-packages` to install required packages\n- Run `yarn run-integration` to run the integration tests\n\n### Building Production Bundle\n\nThe UI is built by Webpack and the server is also built by Webpack to decrease install times.\n\nRun `yarn prod` to build a production bundle and the artifacts would be available in `dist` folder.\n\n### Publishing a new release\n\nRunning `yarn ship` will perform a production build and will publish a new version to npm.\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Raathigeshan Kugarajan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<div  align=\"center\">\n<img src=\"./image.png\" />\n<br />\n<br />\n<a href=\"https://github.com/Raathigesh/majestic/actions\">\n  <img src=\"https://img.shields.io/github/workflow/status/Raathigesh/majestic/Node%20CI?style=flat-square\" />\n</a>\n<img src=\"https://img.shields.io/github/license/Raathigesh/majestic.svg?style=flat-square\" />\n<img src=\"https://img.shields.io/npm/v/majestic.svg?style=flat-square\" />\n<a href=\"https://spectrum.chat/majestic\">\n  <img alt=\"Join the community on Spectrum\" src=\"https://withspectrum.github.io/badge/badge.svg\" />\n</a>\n</div>\n\n<br />\n\nMajestic is a GUI for [Jest](https://jestjs.io/)\n\n- ✅ Run all the tests or a single file\n- ⏱ Toggle watch mode\n- 📸 Update snapshots\n- ❌ Examine test failures as they happen\n- ⏲ Console.log() to the UI for debugging\n- 🚔 Built-in coverage report\n- 🔍 Search tests\n- 💎 Works with flow and typescript projects\n- 📦 Works with Create react app\n\n> Majestic supports Jest 20 and above\n\n### Get started\n\nRun majestic via `npx` in a project directory\n\n```bash\ncd ./my-jest-project # go into a project with Jest\nnpx majestic # execute majestic\n```\n\nor install Majestic globally via Yarn and run majestic\n\n```bash\nyarn global add majestic # install majestic globally\ncd ./my-jest-project # go into a project with Jest\nmajestic # execute majestic\n```\n\nor install Majestic globally via Npm and run majestic\n\n```bash\nnpm install majestic -g # install majestic globally\ncd ./my-jest-project # go into a project with Jest\nmajestic # execute majestic\n```\n\n### Running as an app\n\nRunning with the `--app` flag will launch Majestic as a chrome app.\n\n### Optional configuration\n\nYou can configure Majestic by adding `majestic` key to `package.json`.\n\n```javascript\n// package.json\n{\n    \"majestic\": {\n        // if majestic fails to find the Jest package, you can provide it here. Should be relative to the package.json\n        \"jestScriptPath\": \"../node_modules/jest/bin/jest.js\",\n        // if you want to pass additional arguments to Jest, do it here\n        \"args\": ['--config=./path/to/config/file/jest.config.js'],\n        // environment variables to pass to the process\n        \"env\": {\n          \"CI\": \"true\"\n        }\n    }\n}\n```\n\n#### Optional configuration in project with multiple Jest configuration files\n\n```javascript\n{\n    \"majestic\": {\n        \"jestScriptPath\": \"../node_modules/jest/bin/jest.js\",\n        \"configs\": {\n          \"config1\": {\n            \"args\": [],\n            \"env\": {}\n          },\n          \"config2\": {\n            \"args\": [],\n            \"env\": {}\n          }\n        }\n    }\n}\n```\n\n### Arguments\n\n`--config` - Will use this config from the list supplied in optional configuration.\n\n`--debug` - Will output extra debug info to console. Helps with debugging.\n\n`--noOpen` - Will prevent from automatically opening the UI url in the browser.\n\n`--port` - Will use this port if available, else Majestic will pick another free port.\n\n`--version` - Will print the version of Majestic and will exit.\n\n### Shortcut keys\n\n`alt+t` - run all tests\n\n`alt+enter` - run selected file\n\n`alt+w` - watch\n\n`alt+s` - search\n\n`escape` - close search\n\n### Troubleshooting\n\nHave a look at some of the [common workarounds](./Troubleshooting.md).\n\n### Contribute\n\nHave a look at the [contribution guide](./CONTRIBUTING.MD).\n\n## Contributors\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"http://www.duncanbeevers.com\"><img src=\"https://avatars0.githubusercontent.com/u/7367?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Duncan Beevers</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/commits?author=duncanbeevers\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://github.com/M4cs\"><img src=\"https://avatars3.githubusercontent.com/u/34947910?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Max Bridgland</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/commits?author=M4cs\" title=\"Documentation\">📖</a> <a href=\"#ideas-M4cs\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"https://github.com/Raathigesh/majestic/issues?q=author%3AM4cs\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/Raathigesh/majestic/commits?author=M4cs\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/commits?author=yurm04\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jake.nz\"><img src=\"https://avatars2.githubusercontent.com/u/437471?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jake Crosby</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/commits?author=jake-nz\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://gavinhenderson.me\"><img src=\"https://avatars1.githubusercontent.com/u/1359202?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Gavin Henderson</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/commits?author=gavinhenderson\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://briwa.github.io\"><img src=\"https://avatars1.githubusercontent.com/u/8046636?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>briwa</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/commits?author=briwa\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://github.com/Luanf\"><img src=\"https://avatars0.githubusercontent.com/u/9099705?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Luan Ferreira</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/commits?author=Luanf\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/cse-tushar\"><img src=\"https://avatars3.githubusercontent.com/u/12570521?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tushar Gupta</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/commits?author=cse-tushar\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://agu.st/\"><img src=\"https://avatars3.githubusercontent.com/u/6601142?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Agusti Fernandez</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/commits?author=agustif\" title=\"Code\">💻</a> <a href=\"#ideas-agustif\" title=\"Ideas, Planning, & Feedback\">🤔</a></td>\n    <td align=\"center\"><a href=\"http://blog.42at.com\"><img src=\"https://avatars2.githubusercontent.com/u/233047?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Moos</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/issues?q=author%3Amoos\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/Raathigesh/majestic/commits?author=moos\" title=\"Code\">💻</a> <a href=\"https://github.com/Raathigesh/majestic/commits?author=moos\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"http://maciejzelek.space\"><img src=\"https://avatars3.githubusercontent.com/u/25805810?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>MacZel</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/commits?author=MacZel\" title=\"Code\">💻</a> <a href=\"#ideas-MacZel\" title=\"Ideas, Planning, & Feedback\">🤔</a></td>\n    <td align=\"center\"><a href=\"https://github.com/krazylegz\"><img src=\"https://avatars2.githubusercontent.com/u/36250?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Vikram Dighe</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/commits?author=krazylegz\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://github.com/jsmey\"><img src=\"https://avatars2.githubusercontent.com/u/10177710?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>John Smey</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/commits?author=jsmey\" title=\"Code\">💻</a> <a href=\"#ideas-jsmey\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"https://github.com/Raathigesh/majestic/issues?q=author%3Ajsmey\" title=\"Bug reports\">🐛</a></td>\n    <td align=\"center\"><a href=\"https://github.com/BuckAMayzing\"><img src=\"https://avatars2.githubusercontent.com/u/19292614?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>BuckAMayzing</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/commits?author=BuckAMayzing\" title=\"Code\">💻</a> <a href=\"https://github.com/Raathigesh/majestic/issues?q=author%3ABuckAMayzing\" title=\"Bug reports\">🐛</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"http://rahulakrishna.github.io\"><img src=\"https://avatars2.githubusercontent.com/u/10240002?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Rahul A. Krishna</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/commits?author=rahulakrishna\" title=\"Code\">💻</a> <a href=\"#ideas-rahulakrishna\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#tool-rahulakrishna\" title=\"Tools\">🔧</a></td>\n    <td align=\"center\"><a href=\"https://amilajack.com\"><img src=\"https://avatars1.githubusercontent.com/u/6374832?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Amila Welihinda</b></sub></a><br /><a href=\"#infra-amilajack\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a></td>\n    <td align=\"center\"><a href=\"https://github.com/gregveres\"><img src=\"https://avatars2.githubusercontent.com/u/12899823?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>gregveres</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/issues?q=author%3Agregveres\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/Raathigesh/majestic/commits?author=gregveres\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://adamklein.dev\"><img src=\"https://avatars3.githubusercontent.com/u/889418?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>adam klein</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/commits?author=adamkleingit\" title=\"Tests\">⚠️</a> <a href=\"https://github.com/Raathigesh/majestic/commits?author=adamkleingit\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://www.raphaelbarbazza.com\"><img src=\"https://avatars1.githubusercontent.com/u/42906704?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Raphaël Barbazza</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/commits?author=rbarbazz\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://philalsford.com\"><img src=\"https://avatars3.githubusercontent.com/u/8849355?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Phil Alsford</b></sub></a><br /><a href=\"https://github.com/Raathigesh/majestic/commits?author=philals\" title=\"Documentation\">📖</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n"
  },
  {
    "path": "Troubleshooting.md",
    "content": "#### Custom react-scripts\n\nIf you're using a custom [react-scripts](https://www.npmjs.com/package/react-scripts) in your CRA app, set `jestScriptPath` to your script path. e.g.:\n\n```\n  \"jestScriptPath\": \"./node_modules/my-react-scripts/scripts/test.js\"\n```\n\n#### Absolute import paths\n\nSet `NODE_PATH` in the majestic env config:\n\n```\n  \"env\": {\n    \"NODE_PATH\": \"./src\"\n  }\n```\n\n#### Mocked networks\n\nWhen using [nock](https://github.com/nock/nock) (or other mock proxies) and get an error:\n\n> (node:50245) UnhandledPromiseRejectionWarning: FetchError: request to http://localhost:4000/test-result failed, reason: Nock: Not allow net connect for \"localhost:4000/test-result\"\n\nmake sure to re-enable net connection after the test completes, e.g. (in a setup file) :\n\n```\nbeforeAll(() => {\n  nock.disableNetConnect();\n});\n\nafterAll(() => {\n  nock.enableNetConnect();\n});\n```\n"
  },
  {
    "path": "integration/cypress/fixtures/example.json",
    "content": "{\n  \"name\": \"Using fixtures to represent data\",\n  \"email\": \"hello@cypress.io\",\n  \"body\": \"Fixtures are a great way to mock data for responses to routes\"\n}"
  },
  {
    "path": "integration/cypress/integration/basic/basic-functionality.js",
    "content": "/// <reference types=\"Cypress\" />\n\ncontext('basic', () => {\n  beforeEach(() => {\n    cy.visit('http://localhost:9000', {\n      timeout: 9000,\n    });\n  });\n\n  after(() => {\n    cy.exec('yarn kill-app');\n  });\n\n  it('should display passing test count', () => {\n    cy.wait(2000);\n    cy.getByText('test-all-good.spec.js').click({ force: true });\n    cy.getByText('Run').click();\n    cy.wait(5000);\n    cy.queryByText('6 Passing tests').should('exist');\n  });\n\n  it('should display failure tests', () => {\n    cy.wait(2000);\n    cy.getByText('test-few-failure.spec.js').click({ force: true });\n    cy.wait(2000);\n    cy.getByText('Run').click();\n    cy.wait(5000);\n    cy.queryByText('5 Passing tests').should('exist');\n  });\n\n  it('should show update snapshot button', () => {\n    cy.wait(2000);\n    cy.getByText('test-snapshot-failure.spec.js').click({ force: true });\n    cy.wait(2000);\n    cy.getByText('Run').click();\n    cy.wait(5000);\n    cy.queryByText('Update Snapshot').should('exist');\n  });\n\n  it('should not show update snapshot button', () => {\n    cy.wait(2000);\n    cy.getByText('test-snapshot-text.spec.js').click({ force: true });\n    cy.wait(2000);\n    cy.getByText('Run').click();\n    cy.wait(5000);\n    cy.queryByText('Update Snapshot').should('not.exist');\n  });\n});\n"
  },
  {
    "path": "integration/cypress/plugins/index.js",
    "content": "// ***********************************************************\n// This example plugins/index.js can be used to load plugins\n//\n// You can change the location of this file or turn off loading\n// the plugins file with the 'pluginsFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/plugins-guide\n// ***********************************************************\n\n// This function is called when a project is opened or re-opened (e.g. due to\n// the project's config changing)\n\nmodule.exports = (on, config) => {\n  // `on` is used to hook into various events Cypress emits\n  // `config` is the resolved Cypress config\n};\n"
  },
  {
    "path": "integration/cypress/support/commands.js",
    "content": "// ***********************************************\n// This example commands.js shows you how to\n// create various custom commands and overwrite\n// existing commands.\n//\n// For more comprehensive examples of custom\n// commands please read more here:\n// https://on.cypress.io/custom-commands\n// ***********************************************\n//\n//\n// -- This is a parent command --\n// Cypress.Commands.add(\"login\", (email, password) => { ... })\n//\n//\n// -- This is a child command --\n// Cypress.Commands.add(\"drag\", { prevSubject: 'element'}, (subject, options) => { ... })\n//\n//\n// -- This is a dual command --\n// Cypress.Commands.add(\"dismiss\", { prevSubject: 'optional'}, (subject, options) => { ... })\n//\n//\n// -- This is will overwrite an existing command --\n// Cypress.Commands.overwrite(\"visit\", (originalFn, url, options) => { ... })\nimport 'cypress-testing-library/add-commands';\n"
  },
  {
    "path": "integration/cypress/support/index.js",
    "content": "// ***********************************************************\n// This example support/index.js is processed and\n// loaded automatically before your test files.\n//\n// This is a great place to put global configuration and\n// behavior that modifies Cypress.\n//\n// You can change the location of this file or turn off\n// automatically serving support files with the\n// 'supportFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/configuration\n// ***********************************************************\n\n// Import commands.js using ES2015 syntax:\nimport './commands';\n\n// Alternatively you can use CommonJS syntax:\n// require('./commands')\n"
  },
  {
    "path": "integration/cypress.json",
    "content": "{\n  \"projectId\": \"q19erz\"\n}\n"
  },
  {
    "path": "integration/kill.js",
    "content": "const fkill = require('fkill');\n\nfkill(':9000', {\n  force: true,\n  tree: true,\n})\n  .then(() => {\n    console.log('Killed process');\n  })\n  .catch(e => {\n    console.log(\"Couldn't kill process: \", e);\n  });\n"
  },
  {
    "path": "integration/package.json",
    "content": "{\n  \"name\": \"integration\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"prepare-packages\": \"yarn && cd ./projects/basic && yarn\",\n    \"open-tests\": \"cypress open\",\n    \"run-tests\": \"wait-on http://localhost:9000 && cypress run --record --key a7d33ff7-5893-4158-9ec2-f71b32138c8b\",\n    \"kill-app\": \"node ./kill.js\",\n    \"prepare-basic-app\": \"node ./kill.js && cd ./projects/basic && node ../../../dist/server/index.js --port=9000 --debug\",\n    \"integration-app\": \"concurrently --success=last \\\"yarn prepare-basic-app\\\"  \\\"yarn run-tests\\\"\",\n    \"run-integration\": \"yarn integration-app\",\n    \"run-in-ci\": \"yarn prepare-packages && yarn run-integration\"\n  },\n  \"dependencies\": {\n    \"concurrently\": \"^4.1.0\",\n    \"cypress\": \"^3.2.0\",\n    \"cypress-testing-library\": \"^2.3.6\",\n    \"fkill\": \"^6.0.0\",\n    \"wait-on\": \"^3.2.0\"\n  }\n}\n"
  },
  {
    "path": "integration/projects/basic/__snapshots__/test-snapshot-failure.spec.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`test Snapsh0t test 1`] = `\n<div\n  className=\"App\"\n>\n  Hello world\n</div>\n`;\n"
  },
  {
    "path": "integration/projects/basic/app.js",
    "content": "import React, { Component } from 'react';\n\nclass App extends Component {\n  render() {\n    return <div className=\"App\">Hello world 123</div>;\n  }\n}\n\nexport default App;\n"
  },
  {
    "path": "integration/projects/basic/babel.config.js",
    "content": "module.exports = {\n  presets: [\n    [\n      '@babel/preset-env',\n      {\n        targets: {\n          node: 'current',\n        },\n      },\n    ],\n    '@babel/preset-react',\n  ],\n};\n"
  },
  {
    "path": "integration/projects/basic/package.json",
    "content": "{\n  \"name\": \"simple-jest\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"test\": \"jest\"\n  },\n  \"dependencies\": {\n    \"@babel/preset-react\": \"^7.0.0\",\n    \"jest\": \"^25.3.0\",\n    \"react\": \"^16.8.4\",\n    \"react-dom\": \"^16.8.4\",\n    \"react-test-renderer\": \"^16.8.4\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.3.4\",\n    \"@babel/preset-env\": \"^7.3.4\",\n    \"babel-jest\": \"^25.3.0\"\n  }\n}\n"
  },
  {
    "path": "integration/projects/basic/test-all-good.spec.js",
    "content": "describe('test', () => {\n  it('should add third', () => {\n    expect(5).toBe(5);\n  });\n\n  it('should add 1', () => {\n    expect(5).toBe(5);\n  });\n\n  it('should add 2', () => {\n    expect(5).toBe(5);\n  });\n\n  it('should add 3', () => {\n    expect(5).toBe(5);\n  });\n\n  it('should add 4', () => {\n    expect(5).toBe(5);\n  });\n\n  it('should add 5', () => {\n    expect(5).toBe(5);\n  });\n});\n"
  },
  {
    "path": "integration/projects/basic/test-few-failure.spec.js",
    "content": "describe('test', () => {\n  it('should add third', () => {\n    expect(5).toBe(6);\n  });\n\n  it('should add 1', () => {\n    expect(5).toBe(5);\n  });\n\n  it('should add 2', () => {\n    expect(5).toBe(5);\n  });\n\n  it('should add 3', () => {\n    expect(5).toBe(5);\n  });\n\n  it('should add 4', () => {\n    expect(5).toBe(5);\n  });\n\n  it('should add 5', () => {\n    expect(5).toBe(5);\n  });\n});\n"
  },
  {
    "path": "integration/projects/basic/test-only.spec.js",
    "content": "describe('describe', () => {\n  it('it', () => {});\n  test('test', () => {});\n});\n\ndescribe.only('describe.only', () => {\n  it('it', () => {});\n  test('test', () => {});\n  it.only('it.only', () => {});\n  test.only('test.only', () => {});\n});\n\nfdescribe('fdescribe', () => {\n  it('it', () => {});\n  fit('fit', () => {});\n});\n"
  },
  {
    "path": "integration/projects/basic/test-snapshot-failure.spec.js",
    "content": "import renderer from 'react-test-renderer';\nimport React from 'react';\nimport App from './app';\n\ndescribe('test', () => {\n  it('Snapsh0t test', () => {\n    // Make sure we don't use 'snapshot' because it fools the snapshot button\n    const tree = renderer.create(<App />).toJSON();\n    expect(tree).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "integration/projects/basic/test-snapshot-text.spec.js",
    "content": "describe('test', () => {\n  it('Should not show snapshot button', () => {\n    expect('snapshot').toBe('a snapshot');\n  });\n});\n"
  },
  {
    "path": "nodemon.json",
    "content": "{\n  \"ignore\": [\".git\", \"node_modules\"],\n  \"watch\": [\"server\"],\n  \"exec\": \"ts-node --project ./tsconfig.server.json ./server/index.ts\",\n  \"ext\": \"ts\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"majestic\",\n  \"version\": \"1.8.1\",\n  \"engines\": {\n    \"node\": \">=7.10.1\"\n  },\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"ui\": \"webpack-dev-server --env.development --config ./scripts/webpack.ui.config.js\",\n    \"server\": \"ts-node  --project ./tsconfig.server.json ./server/index.ts\",\n    \"build-server\": \"cross-env BABEL_ENV='production' webpack --env.production --config ./scripts/webpack.server.config.js\",\n    \"build-ui\": \"cross-env BABEL_ENV='production' rimraf dist && webpack --env.production --config ./scripts/webpack.ui.config.js\",\n    \"prod\": \"npm run build-ui && npm run build-server\",\n    \"watch-server\": \"nodemon\",\n    \"ship\": \"npm run prod && np --yolo\",\n    \"integration\": \"cd ./integration && yarn run-in-ci\"\n  },\n  \"dependencies\": {\n    \"node-fetch\": \"^2.3.0\",\n    \"open\": \"^6.0.0\",\n    \"read-pkg-up\": \"^4.0.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.1.2\",\n    \"@babel/parser\": \"^7.2.3\",\n    \"@babel/plugin-proposal-class-properties\": \"^7.0.0\",\n    \"@babel/plugin-proposal-decorators\": \"^7.1.2\",\n    \"@babel/plugin-proposal-object-rest-spread\": \"^7.0.0\",\n    \"@babel/plugin-proposal-optional-chaining\": \"^7.8.3\",\n    \"@babel/polyfill\": \"^7.0.0\",\n    \"@babel/preset-env\": \"^7.0.0\",\n    \"@babel/preset-react\": \"^7.0.0\",\n    \"@babel/preset-typescript\": \"^7.0.0\",\n    \"@babel/traverse\": \"^7.2.3\",\n    \"@types/babel-traverse\": \"^6.25.4\",\n    \"@types/chokidar\": \"^1.7.5\",\n    \"@types/express\": \"^4.16.0\",\n    \"@types/istanbul-lib-coverage\": \"^2.0.0\",\n    \"@types/istanbul-lib-source-maps\": \"^1.2.1\",\n    \"@types/react\": \"^16.8.6\",\n    \"@types/react-dom\": \"^16.8.2\",\n    \"@types/react-split-pane\": \"^0.1.67\",\n    \"@types/styled-components\": \"^4.1.4\",\n    \"@types/styled-system\": \"^3.1.0\",\n    \"ansi-to-html\": \"^0.6.10\",\n    \"apollo-client\": \"^2.3.8\",\n    \"apollo-client-preset\": \"^1.0.8\",\n    \"apollo-link\": \"^1.2.3\",\n    \"apollo-link-ws\": \"^1.0.9\",\n    \"apollo-utilities\": \"^1.0.21\",\n    \"awesome-typescript-loader\": \"^5.2.1\",\n    \"babel-loader\": \"^8.0.2\",\n    \"babel-plugin-styled-components\": \"^1.10.6\",\n    \"body-parser\": \"^1.18.3\",\n    \"chokidar\": \"^2.0.4\",\n    \"chrome-launcher\": \"^0.10.5\",\n    \"consola\": \"^2.5.7\",\n    \"copy-webpack-plugin\": \"^5.0.1\",\n    \"cross-env\": \"^5.2.0\",\n    \"css-loader\": \"^1.0.0\",\n    \"file-loader\": \"^3.0.1\",\n    \"get-port\": \"^4.2.0\",\n    \"graphql-tag\": \"^2.9.2\",\n    \"graphql-yoga\": \"^1.16.1\",\n    \"html-webpack-include-assets-plugin\": \"^1.0.5\",\n    \"html-webpack-plugin\": \"^3.2.0\",\n    \"html-webpack-template\": \"^6.2.0\",\n    \"istanbul-lib-coverage\": \"^2.0.3\",\n    \"istanbul-lib-source-maps\": \"^3.0.2\",\n    \"launch-editor\": \"^2.2.1\",\n    \"lodash.throttle\": \"^4.1.1\",\n    \"minimist\": \"^1.2.0\",\n    \"nanoid\": \"^2.0.0\",\n    \"nodemon\": \"^1.18.3\",\n    \"np\": \"^4.0.2\",\n    \"react\": \"^16.8.3\",\n    \"react-apollo\": \"^2.1.11\",\n    \"react-apollo-hooks\": \"^0.2.1\",\n    \"react-dom\": \"^16.8.3\",\n    \"react-feather\": \"^1.1.4\",\n    \"react-inspector\": \"^3.0.0\",\n    \"react-split-pane\": \"^0.1.84\",\n    \"react-spring\": \"^8.0.9\",\n    \"react-tippy\": \"^1.2.3\",\n    \"react-virtualized-auto-sizer\": \"^1.0.2\",\n    \"react-window\": \"^1.6.2\",\n    \"reflect-metadata\": \"^0.1.12\",\n    \"resolve-pkg\": \"^1.0.0\",\n    \"rimraf\": \"^2.6.2\",\n    \"style-loader\": \"^0.23.0\",\n    \"styled-components\": \"^4.1.3\",\n    \"styled-system\": \"^3.1.11\",\n    \"svg-inline-loader\": \"^0.8.0\",\n    \"svg-react-loader\": \"^0.4.5\",\n    \"ts-node\": \"^7.0.1\",\n    \"type-graphql\": \"^0.14.0\",\n    \"typeface-open-sans\": \"^0.0.54\",\n    \"typescript\": \"^3.0.1\",\n    \"uglifyjs-webpack-plugin\": \"^2.0.0\",\n    \"url-loader\": \"^1.1.1\",\n    \"webpack\": \"^4.17.1\",\n    \"webpack-cli\": \"^3.1.0\",\n    \"webpack-dev-server\": \"^3.2.1\"\n  },\n  \"resolutions\": {\n    \"graphql\": \"^0.13.0\"\n  },\n  \"bin\": {\n    \"majestic\": \"./dist/server/index.js\"\n  },\n  \"files\": [\n    \"/dist/**\",\n    \"/yarn.lock\"\n  ]\n}\n"
  },
  {
    "path": "scripts/webpack.server.config.js",
    "content": "const CopyPlugin = require('copy-webpack-plugin');\nconst webpack = require('webpack');\nconst path = require('path');\n\nmodule.exports = env => ({\n  entry: './server/index.ts',\n  mode: 'production',\n  target: 'node',\n  output: {\n    path: path.resolve(__dirname, '../dist/server'),\n    filename: 'index.js',\n    libraryTarget: 'commonjs2',\n  },\n  resolve: {\n    mainFields: ['main'],\n    extensions: ['.ts', '.js', '.jsx'],\n  },\n  optimization: {\n    minimize: false,\n  },\n  devtool: 'source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        exclude: /(node_modules)/,\n        loader: 'babel-loader',\n      },\n      {\n        test: /\\.ts$/,\n        exclude: /(node_modules)/,\n        loader: 'awesome-typescript-loader',\n        options: {\n          transpileOnly: true,\n          configFileName: './tsconfig.server.json',\n        },\n      },\n    ],\n  },\n  plugins: [\n    new webpack.DefinePlugin({\n      PRODUCTION: env.production === 'production',\n    }),\n    new CopyPlugin([\n      { from: './server/services/jest-manager/scripts', to: './scripts' },\n    ]),\n    new webpack.BannerPlugin({\n      banner: '#!/usr/bin/env node',\n      raw: true,\n    }),\n  ],\n  externals: ['read-pkg-up', 'open'],\n  node: {\n    __dirname: false,\n  },\n});\n"
  },
  {
    "path": "scripts/webpack.ui.config.js",
    "content": "const HtmlWebpackPlugin = require('html-webpack-plugin');\nconst webpack = require('webpack');\nconst path = require('path');\n\nmodule.exports = env => ({\n  entry: './ui/index.tsx',\n  mode: env.production ? 'production' : 'development',\n  output: {\n    path: path.resolve(__dirname, '../dist/ui'),\n    filename: 'ui.bundle.js',\n  },\n  resolve: {\n    extensions: ['.ts', '.tsx', '.js', '.jsx'],\n  },\n  devServer: {\n    contentBase: path.resolve(__dirname, '../dist/ui'),\n    hot: true,\n    port: 9000,\n  },\n  devtool: 'source-map',\n  module: {\n    rules: [\n      {\n        test: /\\.(js|jsx|ts|tsx)$/,\n        exclude: /(node_modules)/,\n        loader: 'babel-loader',\n      },\n      {\n        test: /\\.css$/,\n        use: ['style-loader', 'css-loader'],\n      },\n      {\n        test: /\\.(woff|woff2)(\\?v=\\d+\\.\\d+\\.\\d+)?$/,\n        use: {\n          loader: 'url-loader',\n          options: {\n            limit: 50000,\n          },\n        },\n      },\n      {\n        test: /\\.(graphql|gql)$/,\n        exclude: /node_modules/,\n        loader: 'graphql-tag/loader',\n      },\n      {\n        test: /\\.(png|svg|jpg|gif)$/,\n        use: [\n          {\n            loader: 'file-loader',\n            options: {\n              name: '[name].[ext]',\n            },\n          },\n        ],\n      },\n    ],\n  },\n  plugins: [\n    new HtmlWebpackPlugin({\n      title: 'Majestic',\n      template: require('html-webpack-template'),\n      appMountId: 'root',\n      inject: false,\n      favicon: './ui/assets/favicon.ico',\n    }),\n    new webpack.HotModuleReplacementPlugin(),\n    new webpack.DefinePlugin({\n      PRODUCTION: env.production === true,\n    }),\n  ],\n});\n"
  },
  {
    "path": "server/api/app/app.ts",
    "content": "import { ObjectType, Field } from \"type-graphql\";\n\n@ObjectType()\nexport class App {\n  @Field({ nullable: true })\n  selectedFile: string;\n}\n"
  },
  {
    "path": "server/api/app/resolver.ts",
    "content": "import { Resolver, Mutation, Arg, Query } from \"type-graphql\";\nimport * as launch from \"launch-editor\";\nimport { App } from \"./app\";\nimport FileWatcher, { WatcherEvents } from \"../../services/file-watcher\";\nimport { pubsub } from \"../../event-emitter\";\nimport { dirname, basename } from \"path\";\n\n@Resolver(App)\nexport default class AppResolver {\n  private appInstance: App;\n  private fileWatcher: FileWatcher;\n\n  constructor() {\n    this.fileWatcher = new FileWatcher();\n    this.appInstance = new App();\n  }\n\n  @Query(returns => App)\n  app() {\n    return this.appInstance;\n  }\n\n  @Mutation(returns => App)\n  setSelectedFile(@Arg(\"path\", { nullable: true }) path: string) {\n    this.appInstance.selectedFile = path;\n\n    if (path) {\n      this.fileWatcher.watch(path);\n      pubsub.publish(WatcherEvents.FILE_CHANGE, {\n        id: WatcherEvents.FILE_CHANGE,\n        payload: {\n          path\n        }\n      });\n    }\n\n    return this.appInstance;\n  }\n\n  @Mutation(returns => String)\n  openInEditor(@Arg(\"path\") path: string) {\n    launch(path, process.env.EDITOR || \"code\", (path: string, err: any) => {\n      console.log(\"Failed to open file in editor. You may need to install the code command to your PATH if you are using VSCode: \", err);\n    });\n\n    return \"\";\n  }\n\n @Mutation(returns => String)\n  openSnapInEditor(@Arg(\"path\") path: string) {\n    var dir = dirname(path)\n    var file = basename(path);\n\n    var snap = dir + '/__snapshots__/' + file + '.snap'\n    console.log(\"opening the snapshot:\", snap);\n    this.openInEditor(snap);\n\n    return \"\";\n  }\n\n  @Mutation(returns => String)\n\n  openFailure(@Arg(\"failure\") failure: string) {\n    // The following regex matches the first line of the form: \\w at <some text> (<file path>)\n    // it captures <file path> and returns that in the second position of the match array\n    let re = new RegExp('^\\\\s+at.*?\\\\((.*?)\\\\)$', 'm');\n    let match = failure.match(re);\n    if (match && match.length === 2) {\n      const path = match[1];\n      launch(path, process.env.EDITOR || \"code\", (path: string, err: any) => {\n        console.log(\"Failed to open file in editor. You may need to install the code command to your PATH if you are using VSCode: \", err);\n      });\n    }\n    else {\n      console.log(\"Failed to find a path to a file to load in the failure string.\");\n    }\n    return \"\";\n  }\n}\n"
  },
  {
    "path": "server/api/index.ts",
    "content": "import { buildSchema } from \"type-graphql\";\nimport { pubsub } from \"../event-emitter\";\nimport Workspace from \"./workspace/resolver\";\nimport Runner from \"./runner/resolver\";\nimport App from \"./app/resolver\";\n\nexport async function getSchema() {\n  return await buildSchema({\n    resolvers: [Workspace, Runner, App],\n    pubSub: pubsub as any\n  });\n}\n"
  },
  {
    "path": "server/api/runner/resolver.ts",
    "content": "import {\n  Resolver,\n  Mutation,\n  Arg,\n  Query,\n  Subscription,\n  Root\n} from \"type-graphql\";\nimport { Runner } from \"./type\";\nimport JestManager, {\n  RunnerEvents,\n  RunnerEvent\n} from \"../../services/jest-manager\";\nimport Workspace from \"../../services/project\";\nimport { root } from \"../../services/cli\";\nimport { RunnerStatus } from \"./status\";\nimport { pubsub } from \"../../event-emitter\";\nimport ConfigResolver from \"../../services/config-resolver\";\n\n@Resolver(Runner)\nexport default class RunnerResolver {\n  private jestManager: JestManager;\n  private workspace: Workspace;\n  private isRunning: boolean;\n  private activeFile: string;\n  private isWatching: boolean = false;\n  private collectCoverage: boolean = false;\n\n  constructor() {\n    this.workspace = new Workspace(root);\n    const configResolver = new ConfigResolver();\n    const majesticConfig = configResolver.getConfig(root);\n    this.jestManager = new JestManager(this.workspace, majesticConfig);\n  }\n\n  @Query(returns => RunnerStatus)\n  runnerStatus() {\n    const status = new RunnerStatus();\n    status.activeFile = this.activeFile;\n    status.running = this.isRunning;\n    status.watching = this.isWatching;\n    return status;\n  }\n\n  @Query(returns => Boolean)\n  shouldCollectCoverage() {\n    return this.collectCoverage;\n  }\n\n  @Subscription(returns => RunnerStatus, {\n    topics: [\n      RunnerEvents.RUNNER_STARTED,\n      RunnerEvents.RUNNER_STOPPED,\n      RunnerEvents.RUNNER_WATCH_MODE_CHANGE,\n      RunnerEvents.RUNNER_ACTIVE_FILE_CHANGE\n    ]\n  })\n  runnerStatusChange(@Root() event: RunnerEvent) {\n    this.isRunning =\n      event.payload.isRunning !== undefined\n        ? event.payload.isRunning\n        : this.isRunning;\n\n    const status = new RunnerStatus();\n    status.activeFile = this.activeFile;\n    status.running = this.isRunning;\n    status.watching = this.isWatching;\n    return status;\n  }\n\n  @Mutation(returns => String, { nullable: true })\n  runFile(@Arg(\"path\") path: string) {\n    this.activeFile = path;\n\n    if (this.isWatching && this.isRunning) {\n      pubsub.publish(RunnerEvents.RUNNER_ACTIVE_FILE_CHANGE, {\n        id: RunnerEvents.RUNNER_ACTIVE_FILE_CHANGE,\n        payload: {}\n      });\n\n      return this.jestManager.switchToAnotherFile(path);\n    }\n\n    return this.jestManager.runSingleFile(\n      path,\n      this.isWatching,\n      this.collectCoverage\n    );\n  }\n\n  @Mutation(returns => String, { nullable: true })\n  run() {\n    this.activeFile = \"\";\n    this.isRunning = true;\n    return this.jestManager.run(this.isWatching, this.collectCoverage);\n  }\n\n  @Mutation(returns => String, { nullable: true })\n  stop() {\n    return this.jestManager.stop();\n  }\n\n  @Mutation(returns => String, { nullable: true })\n  updateSnapshot(@Arg(\"path\") path: string) {\n    this.activeFile = path;\n    return this.jestManager.updateSnapshotToFile(path);\n  }\n\n  @Mutation(returns => RunnerStatus, { nullable: true })\n  toggleWatch(@Arg(\"watch\") watch: boolean) {\n    this.isWatching = watch;\n\n    pubsub.publish(RunnerEvents.RUNNER_WATCH_MODE_CHANGE, {\n      id: RunnerEvents.RUNNER_WATCH_MODE_CHANGE,\n      payload: {}\n    });\n  }\n\n  @Mutation(returns => Boolean)\n  setCollectCoverage(@Arg(\"collect\") collect: boolean) {\n    this.collectCoverage = collect;\n    return this.collectCoverage;\n  }\n}\n"
  },
  {
    "path": "server/api/runner/status.ts",
    "content": "import { ObjectType, Field, ID } from \"type-graphql\";\n\n@ObjectType()\nexport class RunnerStatus {\n  @Field({ nullable: true })\n  running: boolean;\n\n  @Field({ nullable: true })\n  activeFile: string;\n\n  @Field({ nullable: true })\n  watching: boolean;\n}\n"
  },
  {
    "path": "server/api/runner/type.ts",
    "content": "import { ObjectType, Field, ID } from \"type-graphql\";\n\n@ObjectType()\nexport class Runner {\n  @Field()\n  status: string;\n\n  @Field()\n  config: string;\n}\n"
  },
  {
    "path": "server/api/workspace/coverage.ts",
    "content": "import { ObjectType, Field } from \"type-graphql\";\n\n@ObjectType()\nexport class CoverageSummary {\n  @Field({ nullable: true })\n  statement: number = 0;\n\n  @Field({ nullable: true })\n  function: number = 0;\n\n  @Field({ nullable: true })\n  branch: number = 0;\n\n  @Field({ nullable: true })\n  line: number = 0;\n}\n"
  },
  {
    "path": "server/api/workspace/resolver.ts",
    "content": "import {\n  Resolver,\n  Arg,\n  Query,\n  Subscription,\n  Root,\n  Mutation\n} from \"type-graphql\";\nimport * as throttle from \"lodash.throttle\";\nimport { Workspace } from \"./workspace\";\nimport Project from \"../../services/project\";\nimport { root } from \"../../services/cli\";\nimport { RunnerEvents } from \"../../services/jest-manager\";\nimport { TestFile } from \"./test-file\";\nimport { inspect } from \"../../services/ast/inspector\";\nimport { TestFileResult } from \"./test-result/file-result\";\nimport {\n  Events,\n  ResultEvent,\n  SummaryEvent\n} from \"../../services/result-handler-api\";\nimport Results from \"../../services/results\";\nimport { WatcherEvents, FileChangeEvent } from \"../../services/file-watcher\";\nimport { Summary } from \"./summary\";\nimport { pubsub } from \"../../event-emitter\";\nimport ConfigResolver from \"../../services/config-resolver\";\nimport { MajesticConfig } from \"../../services/types\";\n\nconst SummaryEvent: \"SummaryEvent\" = \"SummaryEvent\";\n\n@Resolver(Workspace)\nexport default class WorkspaceResolver {\n  private project: Project;\n  private results: Results;\n  private majesticConfig: MajesticConfig;\n\n  constructor() {\n    this.project = new Project(root);\n    const configResolver = new ConfigResolver();\n    this.majesticConfig = configResolver.getConfig(root);\n    this.results = new Results(root);\n    this.results.getCoverageReportPath(this.majesticConfig);\n\n    pubsub.publish(\"WorkspaceInitialized\", {\n      coverageDirectory: this.results.coverageDirectory\n    });\n\n    this.results.checkIfCoverageReportExists();\n\n    pubsub.subscribe(Events.TEST_RESULT, ({ payload }: any) => {\n      const result = new TestFileResult();\n      result.path = payload.path;\n      result.failureMessage = payload.failureMessage;\n      result.numPassingTests = payload.numPassingTests;\n      result.numFailingTests = payload.numFailingTests;\n      result.numPendingTests = payload.numPendingTests;\n      result.testResults = payload.testResults;\n      result.consoleLogs = payload.console;\n      this.results.setTestReport(payload.path, result);\n      this.notifySummaryChange();\n    });\n\n    pubsub.subscribe(Events.TEST_START, ({ payload }: any) => {\n      this.results.setTestStart(payload.path);\n      this.notifySummaryChange();\n    });\n\n    pubsub.subscribe(Events.RUN_SUMMARY, ({ payload }: any) => {\n      const {\n        numFailedTests,\n        numPassedTests,\n        numPassedTestSuites,\n        numFailedTestSuites\n      } = payload.summary;\n\n      this.results.setSummary(\n        numPassedTests,\n        numFailedTests,\n        numPassedTestSuites,\n        numFailedTestSuites\n      );\n      this.notifySummaryChange();\n    });\n\n    pubsub.subscribe(Events.RUN_COMPLETE, ({ payload }) => {\n      this.results.mapCoverage(payload.coverageMap);\n\n      setTimeout(() => {\n        this.results.checkIfCoverageReportExists();\n        this.notifySummaryChange();\n      }, 2000);\n    });\n\n    pubsub.subscribe(RunnerEvents.RUNNER_STOPPED, () => {\n      this.results.markExecutingAsStopped();\n    });\n  }\n\n  private notifySummaryChange = throttle(() => {\n    pubsub.publish(SummaryEvent, {});\n  }, 1000);\n\n  @Query(returns => Workspace)\n  workspace() {\n    const workspace = new Workspace();\n    workspace.projectRoot = this.project.projectRoot;\n    workspace.name = \"Jest project\";\n\n    const fileMap = this.project.getFilesList(this.majesticConfig);\n    workspace.files = Object.entries(fileMap).map(([key, value]: any) => ({\n      name: value.name,\n      path: value.path,\n      parent: value.parent,\n      type: value.type\n    }));\n\n    return workspace;\n  }\n\n  @Query(returns => TestFile)\n  async file(@Arg(\"path\") path: string) {\n    const file = new TestFile();\n    file.items = await inspect(path);\n    return file;\n  }\n\n  @Query(returns => TestFileResult, { nullable: true })\n  result(@Arg(\"path\") path: string) {\n    const result = this.results.getResult(path);\n    return result ? result : null;\n  }\n\n  @Subscription(returns => TestFile, {\n    topics: [WatcherEvents.FILE_CHANGE]\n  })\n  async fileChange(@Root() event: FileChangeEvent, @Arg(\"path\") path: string) {\n    const file = new TestFile();\n    file.items = await inspect(event.payload.path);\n    return file;\n  }\n\n  @Subscription(returns => TestFileResult, {\n    topics: [\n      Events.TEST_START,\n      Events.TEST_RESULT,\n      RunnerEvents.RUNNER_STOPPED\n    ],\n    filter: ({ payload: { payload }, args }) => {\n      return payload.path === args.path;\n    }\n  })\n  async changeToResult(\n    @Root() event: ResultEvent,\n    @Arg(\"path\") path: string\n  ): Promise<TestFileResult> {\n    const payload = event.payload;\n    const result = new TestFileResult();\n    if (event.id === Events.TEST_START) {\n      const existingResults = this.results.getResult(path);\n      if (existingResults) {\n        result.testResults =  existingResults.testResults;\n      }\n    }\n    else if (event.id === Events.TEST_RESULT) {\n      result.path = path;\n      result.failureMessage = payload.failureMessage;\n      result.numPassingTests = payload.numPassingTests;\n      result.numFailingTests = payload.numFailingTests;\n      result.numPendingTests = payload.numPendingTests;\n      result.testResults = payload.testResults;\n      result.consoleLogs = payload.console;\n    }\n    return result;\n  }\n\n  @Subscription(returns => Summary, {\n    topics: [SummaryEvent]\n  })\n  async changeToSummary(@Root() event: SummaryEvent): Promise<Summary> {\n    const {\n      numFailedTests,\n      numPassedTests,\n      numPassedTestSuites,\n      numFailedTestSuites\n    } = this.results.getSummary();\n\n    const summary = new Summary();\n    summary.numFailedTests = numFailedTests;\n    summary.numPassedTests = numPassedTests;\n    summary.numPassedTestSuites = numPassedTestSuites;\n    summary.numFailedTestSuites = numFailedTestSuites;\n    summary.failedTests = this.results.getFailedTests();\n    summary.executingTests = this.results.getExecutingTests();\n    summary.passingTests = this.results.getPassedTests();\n    summary.coverage = this.results.getCoverage();\n    summary.haveCoverageReport = this.results.doesHaveCoverageReport();\n    return summary;\n  }\n\n  @Query(returns => Summary, { nullable: true })\n  summary() {\n    const {\n      numFailedTests,\n      numPassedTests,\n      numPassedTestSuites,\n      numFailedTestSuites\n    } = this.results.getSummary();\n    const result = new Summary();\n    result.numFailedTests = numFailedTests;\n    result.numPassedTests = numPassedTests;\n    result.numPassedTestSuites = numPassedTestSuites;\n    result.numFailedTestSuites = numFailedTestSuites;\n    result.failedTests = this.results.getFailedTests();\n    result.executingTests = this.results.getExecutingTests();\n    result.passingTests = this.results.getPassedTests();\n    result.coverage = this.results.getCoverage();\n    result.haveCoverageReport = this.results.doesHaveCoverageReport();\n    return result;\n  }\n}\n"
  },
  {
    "path": "server/api/workspace/summary.ts",
    "content": "import { ObjectType, Field } from \"type-graphql\";\nimport { CoverageSummary } from \"./coverage\";\n\n@ObjectType()\nexport class Summary {\n  @Field({ nullable: true })\n  numPassedTests: number = 0;\n\n  @Field({ nullable: true })\n  numFailedTests: number = 0;\n\n  @Field({ nullable: true })\n  numPassedTestSuites: number = 0;\n\n  @Field({ nullable: true })\n  numFailedTestSuites: number = 0;\n\n  @Field(returns => [String])\n  passingTests: string[] = [];\n\n  @Field(returns => [String])\n  failedTests: string[] = [];\n\n  @Field(returns => [String])\n  executingTests: string[] = [];\n\n  @Field(returns => CoverageSummary, { nullable: true })\n  coverage: CoverageSummary;\n\n  @Field(returns => Boolean, { nullable: true })\n  haveCoverageReport: boolean;\n}\n"
  },
  {
    "path": "server/api/workspace/test-file.ts",
    "content": "import { ObjectType, Field } from \"type-graphql\";\nimport { TestItem } from \"./test-item\";\n\n@ObjectType()\nexport class TestFile {\n  @Field(returns => [TestItem])\n  items: TestItem[];\n}\n"
  },
  {
    "path": "server/api/workspace/test-item.ts",
    "content": "import { ObjectType, Field } from \"type-graphql\";\n\nexport type TestItemType = \"describe\" | \"it\" | \"todo\";\n\n@ObjectType()\nexport class TestItem {\n  @Field()\n  id: string;\n\n  @Field({ nullable: true })\n  name: string;\n\n  @Field()\n  type: TestItemType;\n\n  @Field({ nullable: true })\n  parent?: string;\n\n  @Field()\n  only: boolean;\n}\n"
  },
  {
    "path": "server/api/workspace/test-result/console-log.ts",
    "content": "import { ObjectType, Field } from \"type-graphql\";\n\n@ObjectType()\nexport class ConsoleLog {\n  @Field({ nullable: true })\n  message: string;\n\n  @Field({ nullable: true })\n  origin: string;\n\n  @Field({ nullable: true })\n  type: string;\n}\n"
  },
  {
    "path": "server/api/workspace/test-result/file-result.ts",
    "content": "import { ObjectType, Field } from \"type-graphql\";\nimport { TestItemResult } from \"./test-item-result\";\nimport { ConsoleLog } from \"./console-log\";\n\n@ObjectType()\nexport class TestFileResult {\n  @Field({ nullable: true })\n  path: string;\n\n  @Field({ nullable: true })\n  numFailingTests: number = 0;\n\n  @Field({ nullable: true })\n  numPassingTests: number = 0;\n\n  @Field({ nullable: true })\n  numPendingTests: number = 0;\n\n  @Field({ nullable: true })\n  failureMessage: string;\n\n  @Field(returns => TestItemResult, { nullable: true })\n  testResults: TestItemResult[] | null;\n\n  @Field(returns => ConsoleLog, { nullable: true })\n  consoleLogs: ConsoleLog[];\n}\n"
  },
  {
    "path": "server/api/workspace/test-result/test-item-result.ts",
    "content": "import { ObjectType, Field } from \"type-graphql\";\n\n@ObjectType()\nexport class TestItemResult {\n  @Field()\n  title: string;\n\n  @Field()\n  numPassingAsserts: number;\n\n  @Field()\n  status: string;\n\n  @Field(returns => [String])\n  failureMessages: string[] = [];\n\n  @Field(returns => [String])\n  ancestorTitles: string[] = [];\n\n  @Field()\n  duration: number;\n}\n"
  },
  {
    "path": "server/api/workspace/tree.ts",
    "content": "import { ObjectType, Field, ID } from \"type-graphql\";\n\n@ObjectType()\nexport class Item {\n  @Field()\n  path: string;\n\n  @Field()\n  name: string;\n\n  @Field()\n  type: \"directory\" | \"file\";\n\n  @Field({ nullable: true })\n  parent?: string;\n}\n"
  },
  {
    "path": "server/api/workspace/workspace.ts",
    "content": "import { ObjectType, Field, ID } from \"type-graphql\";\nimport { Item } from \"./tree\";\n\n@ObjectType()\nexport class Workspace {\n  @Field()\n  projectRoot: string;\n\n  @Field()\n  name: string;\n\n  @Field(type => [Item])\n  files: Item[];\n}\n"
  },
  {
    "path": "server/event-emitter/index.ts",
    "content": "import { PubSub } from \"graphql-yoga\";\n\nexport const pubsub = new PubSub();\n"
  },
  {
    "path": "server/index.ts",
    "content": "import { GraphQLServer } from \"graphql-yoga\";\nimport \"reflect-metadata\";\nimport { getSchema } from \"./api\";\nimport resultHandlerApi from \"./services/result-handler-api\";\nimport getPort from \"get-port\";\nimport * as parseArgs from \"minimist\";\nimport * as chromeLauncher from \"chrome-launcher\";\nimport * as opn from \"open\";\nimport \"consola\";\nimport { initializeStaticRoutes } from \"./static-files\";\nimport { root } from \"./services/cli\";\nimport * as readPkgUp from \"read-pkg-up\";\n\nconst pkg = readPkgUp.sync({\n  cwd: __dirname\n}).pkg;\ndeclare var consola: any;\n\nconst args = parseArgs(process.argv);\nconst defaultPort = args.port || 4000;\nprocess.env.DEBUG_LOG = args.debug ? \"log\" : \"\";\n\nif (args.root) {\n  process.env.ROOT = args.root;\n}\n\nif (args.version) {\n  console.log(`v${pkg.version}`);\n  process.exit();\n}\n\nasync function main() {\n  try {\n    const schema: any = await getSchema();\n    const server = new GraphQLServer({ schema });\n    initializeStaticRoutes(server.express, root);\n    resultHandlerApi(server.express);\n\n    const port = await getPort({ port: defaultPort });\n    // this will be used by the jest reporter\n    process.env.MAJESTIC_PORT = port.toString();\n\n    server.start(\n      {\n        port,\n        playground: \"/debug\"\n      },\n      async () => {\n        const url = `http://localhost:${port}`;\n        console.log(`⚡  Majestic v${pkg.version} is running at ${url} `);\n\n        if (args.app) {\n          await chromeLauncher.launch({\n            startingUrl: url,\n            chromeFlags: [`--app=${url}`]\n          });\n        } else if (!args.noOpen) {\n          opn(url);\n        }\n      }\n    );\n  } catch (e) {\n    consola.error(e);\n  }\n}\n\nmain();\n"
  },
  {
    "path": "server/logger.ts",
    "content": "declare var consola: any;\n\nexport function debugLog(tag: string, ...args: any) {\n  if (process.env.DEBUG_LOG !== \"\") {\n    consola.info({\n      tag,\n      args\n    });\n  }\n}\n\nexport function executeAndLog(\n  tag: string,\n  message: string,\n  execute: () => any\n) {\n  if (process.env.DEBUG_LOG !== \"\") {\n    consola.info({\n      tag,\n      args: [message, execute()]\n    });\n  }\n}\n\nexport function createLogger(tag: string) {\n  return (...args: any) => debugLog(tag, ...args);\n}\n"
  },
  {
    "path": "server/services/ast/inspector.ts",
    "content": "import traverse from \"@babel/traverse\";\nimport * as nanoid from \"nanoid\";\nimport { parse } from \"./parser\";\nimport { readFile } from \"fs\";\nimport { TestItem, TestItemType } from \"../../api/workspace/test-item\";\n\nexport async function inspect(path: string): Promise<TestItem[]> {\n  return new Promise((resolve, reject) => {\n    readFile(\n      path,\n      {\n        encoding: \"utf8\"\n      },\n      (err, code) => {\n        if (err) {\n          reject(err);\n        }\n\n        let ast;\n        try {\n          ast = parse(path, code);\n        } catch (e) {\n          reject(e);\n        }\n\n        const result: TestItem[] = [];\n\n        traverse(ast, {\n          CallExpression(path: any) {\n            if (path.scope.block.type === \"Program\") {\n              findItems(path, result);\n            }\n          }\n        });\n        resolve(result);\n      }\n    );\n  });\n}\n\nfunction getTemplateLiteralName(path: any) {\n  let currentExpressionIndex = 0;\n  const { expressions, quasis } = path.node.arguments[0];\n\n  return `\\`${quasis.reduce((finalText: String, q: any) => {\n    if (\n      expressions[currentExpressionIndex] &&\n      q.end === expressions[currentExpressionIndex].start - 2\n    ) {\n      const formattedExpression = `${q.value.raw}\\$\\{${expressions[currentExpressionIndex].name}\\}`;\n      currentExpressionIndex += 1;\n\n      return finalText.concat(formattedExpression);\n    } else {\n      return finalText.concat(q.value.raw);\n    }\n  }, '')}\\``;\n}\n\nfunction findItems(path: any, result: TestItem[], parentId?: any) {\n  let type: string;\n  let only: boolean = false;\n  if (path.node.callee.name === \"fdescribe\") {\n    type = \"describe\";\n    only = true;\n  } else if (path.node.callee.name === \"fit\") {\n    type = \"it\";\n    only = true;\n  } else if (\n    path.node.callee.property &&\n    path.node.callee.property.name === \"only\"\n  ) {\n    type = path.node.callee.object.name;\n    only = true;\n  } else if (path.node.callee.name === \"test\") {\n    type = \"it\";\n  } else if (\n    path.node.callee.property &&\n    path.node.callee.property.name === \"todo\"\n  ) {\n    type = \"todo\";\n  } else {\n    type = path.node.callee.name;\n  }\n\n  if (type === \"describe\") {\n    let describe: any;\n    if (path.node.arguments[0].type === \"TemplateLiteral\") {\n      describe = {\n        id: nanoid(),\n        type: \"describe\" as TestItemType,\n        name: getTemplateLiteralName(path),\n        only,\n        parent: parentId\n      };\n    } else {\n      describe = {\n        id: nanoid(),\n        type: \"describe\" as TestItemType,\n        name: path.node.arguments[0].value,\n        only,\n        parent: parentId\n      };\n    }\n    result.push(describe);\n    path.skip();\n    path.traverse({\n      CallExpression(itPath: any) {\n        findItems(itPath, result, describe.id);\n      }\n    });\n  } else if (type === \"it\") {\n    if (path.node.arguments[0].type === \"TemplateLiteral\") {\n      result.push({\n        id: nanoid(),\n        type: \"it\",\n        name: getTemplateLiteralName(path),\n        only,\n        parent: parentId\n      });\n    } else {\n      result.push({\n        id: nanoid(),\n        type: \"it\",\n        name: path.node.arguments[0].value,\n        only,\n        parent: parentId\n      });\n    }\n  } else if (type === \"todo\") {\n    if (path.node.arguments[0].type === \"TemplateLiteral\") {\n      result.push({\n        id: nanoid(),\n        type: \"todo\",\n        name: getTemplateLiteralName(path),\n        only,\n        parent: parentId\n      });\n    } else {\n      result.push({\n        id: nanoid(),\n        type: \"todo\",\n        name: path.node.arguments[0].value,\n        only,\n        parent: parentId\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "server/services/ast/parser.ts",
    "content": "import * as parser from \"@babel/parser\";\nimport { extname } from \"path\";\n\nexport function parse(path: string, code: string) {\n  const isTS = [\".ts\", \".tsx\"].indexOf(extname(path).toLowerCase()) > -1;\n  const additionalPlugin = isTS ? \"typescript\" : \"flow\";\n\n  return parser.parse(code, {\n    sourceType: \"module\",\n    plugins: [\"jsx\", \"classProperties\", \"optionalChaining\", additionalPlugin]\n  });\n}\n"
  },
  {
    "path": "server/services/cli.ts",
    "content": "export const root = process.env.ROOT || process.cwd();\n"
  },
  {
    "path": "server/services/config-resolver.ts",
    "content": "import * as parseArgs from \"minimist\";\nimport * as readPkgUp from \"read-pkg-up\";\nimport * as resolvePkg from \"resolve-pkg\";\nimport { MajesticConfig } from \"./types\";\nimport { platform } from \"os\";\nimport { join } from \"path\";\nimport { existsSync } from \"fs\";\nimport { createLogger } from \"../logger\";\n\ndeclare var consola: any;\nconst log = createLogger(\"Config Resolver\");\n\nexport default class ConfigResolver {\n  public getConfig(projectRoot: string): MajesticConfig {\n    let jestScriptPath = null;\n    let args: string[] = [];\n    let env: any = {};\n    const configFromPkgJson = this.getConfigFromPackageJson(projectRoot) || {};\n\n    const jestScriptPathFromPackage = configFromPkgJson.jestScriptPath\n      ? join(projectRoot, configFromPkgJson.jestScriptPath)\n      : null;\n\n    if (this.isBootstrappedWithCreateReactApp(projectRoot)) {\n      log(\"Project identified as Create react app\");\n\n      jestScriptPath =\n        jestScriptPathFromPackage ||\n        this.getJestScriptForCreateReactApp(projectRoot);\n      args = [\"--env=jsdom\"];\n      env = {\n        CI: \"true\"\n      };\n    } else {\n      log(\"Majestic configuration from Package.json: \", configFromPkgJson);\n\n      jestScriptPath =\n        jestScriptPathFromPackage || this.getJestScriptPath(projectRoot);\n    }\n\n    const configArg = parseArgs(process.argv).config;\n\n    if (configArg && configFromPkgJson.configs) {\n      args = [...args, ...(configFromPkgJson.configs[configArg].args || [])];\n      env = { ...env, ...(configFromPkgJson.configs[configArg].env || {}) };\n    } else {\n      args = [...args, ...(configFromPkgJson.args || [])];\n      env = { ...env, ...(configFromPkgJson.env || {}) };\n    }\n\n    const majesticConfig = {\n      jestScriptPath: `\"${jestScriptPath}\"`,\n      args,\n      env\n    };\n\n    log(\"Resolved Majestic config :\", majesticConfig);\n    return majesticConfig;\n  }\n\n  private getJestScriptPath(projectRoot: string) {\n    const path = resolvePkg(\"jest\", {\n      cwd: projectRoot\n    });\n    log(\"Path of resolved Jest script: \", path);\n\n    if (!path) {\n      consola.error(\n        \"🚨 Majestic was unable to find Jest package in node_modules folder. But you can provide the path manually. Please take a look at the documentation at https://github.com/Raathigesh/majestic.\"\n      );\n      process.exit();\n    }\n    return join(path, \"bin/jest.js\");\n  }\n\n  private getJestScriptForCreateReactApp(projectRoot: string) {\n    const path = resolvePkg(\"react-scripts\", {\n      cwd: projectRoot\n    });\n    return join(path, \"scripts/test.js\");\n  }\n\n  private getPackageJson(rootPath: string) {\n    return readPkgUp.sync({\n      cwd: rootPath\n    }).pkg;\n  }\n\n  private getConfigFromPackageJson(projectRoot: string) {\n    const packageJson = this.getPackageJson(projectRoot);\n    if (packageJson.majestic) {\n      return packageJson.majestic;\n    }\n    return null;\n  }\n\n  private isBootstrappedWithCreateReactApp(rootPath: string): boolean {\n    return (\n      this.hasExecutable(rootPath, \"node_modules/.bin/react-scripts\") ||\n      this.hasExecutable(\n        rootPath,\n        \"node_modules/react-scripts/node_modules/.bin/jest\"\n      ) ||\n      this.hasExecutable(rootPath, \"node_modules/react-native-scripts\")\n    );\n  }\n\n  private hasExecutable(rootPath: string, executablePath: string): boolean {\n    const ext = platform() === \"win32\" ? \".cmd\" : \"\";\n    const absolutePath = join(rootPath, executablePath + ext);\n    return existsSync(absolutePath);\n  }\n}\n"
  },
  {
    "path": "server/services/file-watcher/index.ts",
    "content": "import { pubsub } from \"../../event-emitter\";\nimport { watch } from \"fs\";\nimport { createLogger } from \"../../logger\";\n\nconst log = createLogger(\"File watcher\");\n\nexport const WatcherEvents = {\n  FILE_CHANGE: \"FILE_CHANGE\"\n};\n\nexport interface FileChangeEvent {\n  id: string;\n  payload: {\n    path: string;\n  };\n}\n\nexport default class FileWatcher {\n  private watcher: any;\n\n  watch(filePath: string) {\n    if (this.watcher) {\n      this.watcher.close();\n      log(\"Closed existing file watcher\");\n    }\n\n    log(\"Watching file :\", filePath);\n    this.watcher = watch(filePath, () => {\n      log(\"File changed\", filePath);\n      pubsub.publish(WatcherEvents.FILE_CHANGE, {\n        id: WatcherEvents.FILE_CHANGE,\n        payload: {\n          path: filePath\n        }\n      });\n    });\n  }\n}\n"
  },
  {
    "path": "server/services/jest-manager/cli-args.ts",
    "content": "export const ShowConfig = \"--showConfig\";\n"
  },
  {
    "path": "server/services/jest-manager/index.ts",
    "content": "import { spawn, ChildProcess, execSync } from \"child_process\";\nimport { join } from \"path\";\nimport Project from \"../project\";\nimport { pubsub } from \"../../event-emitter\";\nimport { MajesticConfig } from \"../types\";\nimport { createLogger } from \"../../logger\";\n\nconst log = createLogger(\"Jest Manager\");\n\nexport const RunnerEvents = {\n  RUNNER_STARTED: \"RunnerStarted\",\n  RUNNER_STOPPED: \"RunnerStopped\",\n  RUNNER_WATCH_MODE_CHANGE: \"WatchModeChanged\",\n  RUNNER_ACTIVE_FILE_CHANGE: \"RunnerActiveFileChange\"\n};\n\nexport interface RunnerEvent {\n  id: string;\n  payload: {\n    isRunning: boolean;\n  };\n}\n\nexport default class JestManager {\n  project: Project;\n  process: ChildProcess;\n  config: MajesticConfig;\n\n  constructor(project: Project, config: MajesticConfig) {\n    this.project = project;\n    this.config = config;\n  }\n\n  run(watch: boolean, collectCoverage: boolean) {\n    this.executeJest(\n      [\n        \"--reporters\",\n        this.getReporterPath(),\n        ...(watch ? [this.getWatchFlag()] : [])\n      ],\n      true,\n      true,\n      collectCoverage\n    );\n  }\n\n  runSingleFile(path: string, watch: boolean, collectCoverage: boolean) {\n    this.executeJest(\n      [\n        this.getPatternForPath(path),\n        ...(watch ? [this.getWatchFlag()] : []),\n        \"--reporters\",\n        \"default\",\n        this.getReporterPath(),\n        \"--verbose=false\" // this would allow jest to include console output in the result of reporter\n      ],\n      !watch, // while watching, can not inherit stdio because we want to write back and interact with the process\n      false,\n      collectCoverage\n    );\n  }\n\n  updateSnapshotToFile(path: string) {\n    this.executeJest(\n      [\n        this.getPatternForPath(path),\n        \"-u\",\n        \"--reporters\",\n        this.getReporterPath()\n      ],\n      false,\n      false,\n      false\n    );\n  }\n\n  switchToAnotherFile(path: string) {\n    this.executeInSequence([\n      {\n        fn: () => this.process.stdin && this.process.stdin.write(\"p\"),\n        delay: 0\n      },\n      {\n        fn: () =>\n          this.process.stdin &&\n          this.process.stdin.write(this.getPatternForPath(path)),\n        delay: 100\n      },\n      {\n        fn: () =>\n          this.process.stdin &&\n          this.process.stdin.write(new Buffer(\"0d\", \"hex\").toString()),\n        delay: 200\n      }\n    ]);\n  }\n\n  executeJest(\n    args: string[] = [],\n    inherit: boolean,\n    shouldReportSummary: boolean,\n    collectCoverage: boolean\n  ) {\n    if (!this.config.jestScriptPath) {\n      throw new Error(\"Jest script path is empty\");\n    }\n\n    this.reportStart();\n\n    const finalArgs = [\n      \"-r\",\n      this.getPatchFilePath(),\n      this.config.jestScriptPath,\n      ...(this.config.args || []),\n      \"--colors\",\n      ...(collectCoverage\n        ? [\"--collectCoverage=true\"]\n        : [\"--collectCoverage=false\"]),\n      ...args\n    ];\n\n    const finalEnv = {\n      ...(this.config.env || {}),\n      MAJESTIC_PORT: process.env.MAJESTIC_PORT,\n      REPORT_SUMMARY: shouldReportSummary ? \"report\" : \"\"\n    };\n\n    log(\"Executing Jest with :\", finalArgs, finalEnv);\n\n    this.process = spawn(\"node\", finalArgs, {\n      cwd: this.project.projectRoot,\n      shell: true,\n      stdio: inherit ? \"inherit\" : \"pipe\",\n      env: { ...(process.env || {}), ...finalEnv }\n    });\n\n    this.process.on(\"exit\", () => {\n      this.reportStop();\n    });\n\n    this.process.stdout &&\n      this.process.stdout.on(\"data\", (data: string) => {\n        console.log(data.toString().trim());\n      });\n\n    this.process.stderr &&\n      this.process.stderr.on(\"data\", (data: string) => {\n        console.log(data.toString().trim());\n      });\n  }\n\n  getReporterPath() {\n    return `\"${join(__dirname, \"./scripts/reporter.js\")}\"`;\n  }\n\n  getPatchFilePath() {\n    return `\"${join(__dirname, \"./scripts/patch.js\")}\"`;\n  }\n\n  getPatternForPath(path: string) {\n    let replacePattern = /\\//g;\n    if (process.platform === \"win32\") {\n      replacePattern = /\\\\/g;\n    }\n    return `^${path.replace(replacePattern, \".\")}$`;\n  }\n\n  reportStart() {\n    pubsub.publish(RunnerEvents.RUNNER_STARTED, {\n      id: RunnerEvents.RUNNER_STARTED,\n      payload: {\n        isRunning: true\n      }\n    });\n  }\n\n  stop() {\n    if (this.process) {\n      if (process.platform === \"win32\") {\n        // Windows doesn't exit the process when it should.\n        spawn(\"taskkill\", [\"/pid\", \"\" + this.process.pid, \"/T\", \"/F\"]);\n      } else {\n        this.process.kill();\n      }\n\n      this.reportStop();\n    }\n  }\n\n  reportStop() {\n    pubsub.publish(RunnerEvents.RUNNER_STOPPED, {\n      id: RunnerEvents.RUNNER_STOPPED,\n      payload: {\n        isRunning: false\n      }\n    });\n  }\n\n  async executeInSequence(\n    funcs: Array<{\n      fn: () => void;\n      delay: number;\n    }>\n  ) {\n    for (const { fn, delay } of funcs) {\n      await this.setTimeoutPromisify(fn, delay);\n    }\n  }\n\n  setTimeoutPromisify(fn: () => void, delay: number) {\n    return new Promise(resolve => {\n      setTimeout(() => {\n        fn();\n        resolve();\n      }, delay);\n    });\n  }\n\n  getWatchFlag() {\n    return this.isInGitRepository() ? \"--watch\" : \"--watchAll\";\n  }\n\n  isInGitRepository() {\n    try {\n      execSync(\"git rev-parse --is-inside-work-tree\", { stdio: \"ignore\" });\n      return true;\n    } catch (e) {\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "server/services/jest-manager/scripts/patch.js",
    "content": "// Monkey patch the stdin with setRawMode so jest would think it's running from a terminal\nprocess.stdin.setRawMode = () => {};\n"
  },
  {
    "path": "server/services/jest-manager/scripts/reporter.js",
    "content": "const fetch = require('node-fetch');\n\nfunction send(type, body) {\n  fetch('http://localhost:' + process.env.MAJESTIC_PORT + '/' + type, {\n    method: 'post',\n    body: JSON.stringify(body),\n    headers: { 'Content-Type': 'application/json' },\n  });\n}\n\nclass MyCustomReporter {\n  constructor(globalConfig, options) {\n    this._globalConfig = globalConfig;\n    this._options = options;\n  }\n\n  onTestStart(test) {\n    send('test-start', {\n      path: test.path,\n    });\n  }\n\n  onTestResult(test, testResult, aggregatedResult) {\n    send('test-result', {\n      path: testResult.testFilePath,\n      failureMessage: testResult.failureMessage,\n      numFailingTests: testResult.numFailingTests,\n      numPassingTests: testResult.numPassingTests,\n      numPendingTests: testResult.numPendingTests,\n      testResults: (testResult.testResults || []).map(result => ({\n        title: result.title,\n        numPassingAsserts: result.numPassingAsserts,\n        status: result.status,\n        failureMessages: result.failureMessages,\n        ancestorTitles: result.ancestorTitles,\n        duration: result.duration,\n      })),\n      aggregatedResult:\n        process.env.REPORT_SUMMARY === 'report'\n          ? {\n              numFailedTests: aggregatedResult.numFailedTests,\n              numPassedTests: aggregatedResult.numPassedTests,\n              numPassedTestSuites: aggregatedResult.numPassedTestSuites,\n              numFailedTestSuites: aggregatedResult.numFailedTestSuites,\n            }\n          : null,\n      console: testResult.console,\n    });\n  }\n\n  onRunStart(results) {}\n\n  onRunComplete(contexts, results) {\n    send('run-complete', {\n      coverageMap: results.coverageMap,\n    });\n  }\n}\n\nmodule.exports = MyCustomReporter;\n"
  },
  {
    "path": "server/services/project.ts",
    "content": "import { TreeMap, MajesticConfig } from \"./types\";\nimport { spawnSync } from \"child_process\";\nimport { sep, join, extname, normalize } from \"path\";\nimport { createLogger } from \"../logger\";\n\nconst log = createLogger(\"Project\");\n\nexport default class Project {\n  public projectRoot: string;\n\n  constructor(root: string) {\n    this.projectRoot = normalize(root);\n  }\n\n  getFilesList(config: MajesticConfig) {\n    const configProcess = spawnSync(\n      \"node\",\n      [config.jestScriptPath, ...(config.args || []), \"--listTests\", \"--json\"],\n      {\n        cwd: this.projectRoot,\n        shell: true,\n        stdio: \"pipe\",\n        env: {\n          CI: \"true\",\n          ...(config.env || {}),\n          ...process.env\n        }\n      }\n    );\n\n    const filesStr = configProcess.stdout.toString().trim();\n    const files: string[] = JSON.parse(filesStr);\n    log(\"Identified test files: \", files);\n\n    const relativeFiles = files.map(file => file.replace(this.projectRoot, \"\"));\n    const map: TreeMap = {\n      \"/\": {\n        name: this.projectRoot.split(sep).pop() || \"\",\n        type: \"directory\",\n        path: this.projectRoot,\n        parent: undefined\n      }\n    };\n\n    relativeFiles.forEach(path => {\n      const tokens = path.split(sep).filter(token => token.trim() !== \"\");\n      let currentPath = \"\";\n      let parentPath = \"\";\n      tokens.forEach((token, i) => {\n        currentPath = `${currentPath}${sep}${token}`;\n        const type = [\".jsx\", \".tsx\", \".ts\", \".js\"].includes(\n          extname(currentPath)\n        )\n          ? \"file\"\n          : \"directory\";\n        if (!map[currentPath]) {\n          map[currentPath] = {\n            name: token,\n            type,\n            path: join(this.projectRoot, currentPath),\n            parent: join(this.projectRoot, parentPath)\n          };\n        }\n        parentPath = currentPath;\n      });\n    });\n\n    return map;\n  }\n}\n"
  },
  {
    "path": "server/services/result-handler-api.ts",
    "content": "import { Application } from \"express\";\nimport * as bodyParser from \"body-parser\";\nimport { pubsub } from \"../event-emitter\";\nimport { createLogger } from \"../logger\";\n\nconst log = createLogger(\"Report API\");\n\nexport const Events = {\n  TEST_START: \"TEST_START\",\n  TEST_RESULT: \"TEST_RESULT\",\n  RUN_START: \"RUN_START\",\n  RUN_COMPLETE: \"RUN_COMPLETE\",\n  RUN_SUMMARY: \"RUN_SUMMARY\"\n};\n\nexport interface ResultEvent {\n  id: string;\n  payload: any;\n}\n\nexport interface SummaryEvent {\n  id: string;\n  payload: {\n    summary: {\n      numPassedTests: number;\n      numFailedTests: number;\n      numPassedTestSuites: number;\n      numFailedTestSuites: number;\n    };\n  };\n}\n\nexport default function handlerApi(expressApp: Application) {\n  expressApp.use(\n    bodyParser.json({\n      limit: \"50mb\"\n    })\n  );\n  expressApp.post(\"/test-start\", ({ body }, res) => {\n    log(\"File execution start reported \", body.path);\n\n    pubsub.publish(Events.TEST_START, {\n      id: Events.TEST_START,\n      payload: {\n        path: body.path\n      }\n    });\n    res.send(\"ok\");\n  });\n\n  expressApp.post(\"/test-result\", ({ body }, res) => {\n    log(\"File result reported \", body.path);\n\n    pubsub.publish(Events.TEST_RESULT, {\n      id: Events.TEST_RESULT,\n      payload: body\n    });\n\n    if (body.aggregatedResult) {\n      pubsub.publish(Events.RUN_SUMMARY, {\n        id: Events.RUN_SUMMARY,\n        payload: {\n          summary: body.aggregatedResult\n        }\n      });\n    }\n\n    res.send(\"ok\");\n  });\n\n  expressApp.post(\"/run-start\", (req, res) => {\n    pubsub.publish(Events.RUN_START, {\n      id: Events.RUN_START,\n      payload: req.body\n    });\n    res.send(\"ok\");\n  });\n\n  expressApp.post(\"/run-complete\", (req, res) => {\n    pubsub.publish(Events.RUN_COMPLETE, {\n      id: Events.RUN_COMPLETE,\n      payload: req.body\n    });\n    res.send(\"ok\");\n  });\n}\n"
  },
  {
    "path": "server/services/results.ts",
    "content": "import { createSourceMapStore, MapStore } from \"istanbul-lib-source-maps\";\nimport { createCoverageMap, CoverageMap } from \"istanbul-lib-coverage\";\nimport { existsSync } from \"fs\";\nimport { join } from \"path\";\nimport { MajesticConfig } from \"./types\";\nimport { spawnSync } from \"child_process\";\nimport { createLogger } from \"../logger\";\nimport { TestFileResult } from \"../api/workspace/test-result/file-result\";\n\nconst log = createLogger(\"Results\");\n\nexport type TestFileStatus = \"IDLE\" | \"EXECUTING\";\nexport interface CoverageSummary {\n  statement: number;\n  line: number;\n  function: number;\n  branch: number;\n}\nexport default class Results {\n  private projectRoot: string = \"\";\n  private results: {\n    [path: string]: TestFileResult;\n  } = {};\n\n  private testStatus: {\n    [path: string]: {\n      isExecuting: boolean;\n      containsFailure: boolean;\n    };\n  } = {};\n\n  private summary: {\n    numFailedTests: number;\n    numPassedTests: number;\n    numPassedTestSuites: number;\n    numFailedTestSuites: number;\n  };\n\n  private coverage: CoverageSummary = {\n    statement: 0,\n    line: 0,\n    function: 0,\n    branch: 0\n  };\n\n  private haveCoverageReport: boolean = false;\n\n  public coverageFilePath: string = \"\";\n  public coverageDirectory: string = \"\";\n\n  constructor(projectRoot: string) {\n    this.projectRoot = projectRoot;\n    this.results = {};\n    this.summary = {\n      numFailedTests: 0,\n      numPassedTests: 0,\n      numPassedTestSuites: 0,\n      numFailedTestSuites: 0\n    };\n\n    this.checkIfCoverageReportExists();\n  }\n\n  public setTestStart(path: string) {\n    if (!this.testStatus[path]) {\n      this.testStatus[path] = {\n        isExecuting: false,\n        containsFailure: false\n      };\n    }\n    this.testStatus[path].isExecuting = true;\n  }\n\n  public setTestReport(path: string, report: any) {\n    this.results[path] = report;\n    this.testStatus[path].isExecuting = false;\n\n    if (report.numFailingTests > 0) {\n      this.testStatus[path].containsFailure = true;\n    } else {\n      this.testStatus[path].containsFailure = false;\n    }\n  }\n\n  public getResult(path: string): TestFileResult | null {\n    return this.results[path] || null;\n  }\n\n  public setSummary(\n    passedTests: number,\n    failedTests: number,\n    numPassedTestSuites: number,\n    numFailedTestSuites: number\n  ) {\n    this.summary = {\n      numFailedTests: failedTests,\n      numPassedTests: passedTests,\n      numPassedTestSuites,\n      numFailedTestSuites\n    };\n  }\n\n  public markExecutingAsStopped() {\n    this.testStatus = Object.entries(this.testStatus).reduce(\n      (acc, [key, value]) => ({\n        [key]: {\n          ...value,\n          isExecuting: false\n        },\n        ...acc\n      }),\n      {}\n    );\n  }\n\n  public getSummary() {\n    return this.summary;\n  }\n\n  public getFailedTests() {\n    return Object.entries(this.testStatus)\n      .filter(([path, status]) => {\n        return status.containsFailure;\n      })\n      .map(([path]) => path);\n  }\n\n  public getPassedTests() {\n    return Object.entries(this.testStatus)\n      .filter(([path, status]) => {\n        return !status.containsFailure && !status.isExecuting;\n      })\n      .map(([path]) => path);\n  }\n\n  public getExecutingTests() {\n    return Object.entries(this.testStatus)\n      .filter(([path, status]) => {\n        return status.isExecuting === true;\n      })\n      .map(([path]) => path);\n  }\n\n  public mapCoverage(data: any) {\n    if (!data) {\n      this.coverage = {\n        statement: 0,\n        branch: 0,\n        function: 0,\n        line: 0\n      };\n\n      return;\n    }\n\n    const sourceMapStore = createSourceMapStore();\n    const coverageMap = createCoverageMap(data);\n    const transformed = sourceMapStore.transformCoverage(coverageMap);\n    const coverageSummary = transformed.map.getCoverageSummary();\n\n    const statementCoverage = coverageSummary.statements.pct as any;\n    const branchCoverage = coverageSummary.branches.pct as any;\n    const functionCoverage = coverageSummary.functions.pct as any;\n    const lineCoverage = coverageSummary.lines.pct as any;\n\n    this.coverage = {\n      statement: statementCoverage === \"Unknown\" ? 0 : statementCoverage,\n      branch: branchCoverage === \"Unknown\" ? 0 : branchCoverage,\n      function: functionCoverage === \"Unknown\" ? 0 : functionCoverage,\n      line: lineCoverage === \"Unknown\" ? 0 : lineCoverage\n    };\n  }\n\n  public checkIfCoverageReportExists() {\n    this.haveCoverageReport = existsSync(this.coverageFilePath);\n    return this.haveCoverageReport;\n  }\n\n  public getCoverage() {\n    return this.coverage;\n  }\n\n  public doesHaveCoverageReport() {\n    return this.haveCoverageReport;\n  }\n\n  public getCoverageReportPath(config: MajesticConfig) {\n    try {\n      const configProcess = spawnSync(\n        \"node\",\n        [\n          config.jestScriptPath,\n          ...(config.args || []),\n          \"--showConfig\",\n          \"--json\"\n        ],\n        {\n          cwd: this.projectRoot,\n          shell: true,\n          stdio: \"pipe\",\n          env: {\n            CI: \"true\",\n            ...(config.env || {}),\n            ...process.env\n          }\n        }\n      );\n\n      let filesStr = configProcess.stdout.toString().trim();\n      if (filesStr === \"\") {\n        filesStr = configProcess.stderr.toString().trim();\n      }\n\n      const defaultCoveragePath = join(this.projectRoot, \"coverage\");\n      const jestConfig = JSON.parse(filesStr);\n      this.coverageDirectory =\n        (jestConfig.globalConfig &&\n          jestConfig.globalConfig.coverageDirectory) ||\n        defaultCoveragePath;\n      this.coverageFilePath = join(\n        this.coverageDirectory,\n        \"/lcov-report/index.html\"\n      );\n    } catch (e) {\n      log(\n        `Error occured while obtaining Jest cofiguration for coverage report ${e.toString()}`\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "server/services/types.ts",
    "content": "export interface DirectoryItem {\n  name: string;\n  path: string;\n  type: \"directory\" | \"file\";\n  children?: DirectoryItem[];\n}\n\nexport interface TreeMap {\n  [path: string]: {\n    name: string;\n    path: string;\n    parent?: string;\n    type: \"directory\" | \"file\";\n  };\n}\n\nexport interface MajesticConfig {\n  jestScriptPath: string;\n  args?: string[];\n  env?: { [key: string]: string };\n}\n"
  },
  {
    "path": "server/static-files.ts",
    "content": "import * as exp from \"express\";\nimport { resolve, join } from \"path\";\nimport { pubsub } from \"./event-emitter\";\n\nexport function initializeStaticRoutes(express: exp.Application, root: string) {\n  express.get(\"/\", (req, res) =>\n    res.sendFile(\"./ui/index.html\", {\n      root: resolve(__dirname, \"..\")\n    })\n  );\n  express.get(\"/ui.bundle.js\", (req, res) =>\n    res.sendFile(\"./ui/ui.bundle.js\", {\n      root: resolve(__dirname, \"..\")\n    })\n  );\n  express.get(\"/favicon.ico\", (req, res) =>\n    res.sendFile(\"./ui/favicon.ico\", {\n      root: resolve(__dirname, \"..\")\n    })\n  );\n  express.get(\"/logo.png\", (req, res) =>\n    res.sendFile(\"./ui/logo.png\", {\n      root: resolve(__dirname, \"..\")\n    })\n  );\n\n  pubsub.subscribe(\"WorkspaceInitialized\", ({ coverageDirectory }) => {\n    if (coverageDirectory && coverageDirectory.trim() !== \"\") {\n      express.use(\"/coverage\", exp.static(coverageDirectory));\n    }\n  });\n}\n"
  },
  {
    "path": "server/typings.d.ts",
    "content": "declare module \"directory-tree\";\ndeclare module \"micromatch\";\ndeclare module \"@babel/traverse\";\ndeclare module \"nanoid\";\ndeclare module \"read-pkg-up\";\ndeclare module \"open\";\ndeclare module \"launch-editor\";\ndeclare module \"*.json\";\ndeclare module \"lodash.throttle\";\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"es2015\",\n    \"allowSyntheticDefaultImports\": true,\n    \"target\": \"es5\",\n    \"lib\": [\"es6\", \"dom\", \"es2017.object\"],\n    \"sourceMap\": true,\n    \"allowJs\": true,\n    \"jsx\": \"react\",\n    \"moduleResolution\": \"node\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"noImplicitReturns\": false,\n    \"noImplicitThis\": true,\n    \"noImplicitAny\": true,\n    \"strictNullChecks\": true,\n    \"suppressImplicitAnyIndexErrors\": true,\n    \"noUnusedLocals\": false,\n    \"experimentalDecorators\": true,\n    \"skipLibCheck\": true,\n    \"emitDecoratorMetadata\": true\n  },\n  \"exclude\": []\n}\n"
  },
  {
    "path": "tsconfig.server.json",
    "content": "{\n  \"compilerOptions\": {\n    \"rootDir\": \"./server\",\n    \"outDir\": \"./dist/server\",\n    \"module\": \"commonjs\",\n    \"target\": \"es2016\",\n    \"lib\": [\"es6\", \"dom\", \"es2017.object\", \"esnext.asynciterable\"],\n    \"sourceMap\": true,\n    \"allowJs\": true,\n    \"jsx\": \"react\",\n    \"moduleResolution\": \"node\",\n    \"forceConsistentCasingInFileNames\": false,\n    \"noImplicitReturns\": false,\n    \"noImplicitThis\": true,\n    \"noImplicitAny\": false,\n    \"strictNullChecks\": false,\n    \"suppressImplicitAnyIndexErrors\": true,\n    \"noUnusedLocals\": false,\n    \"experimentalDecorators\": true,\n    \"emitDecoratorMetadata\": true,\n    \"skipLibCheck\": true,\n    \"newLine\": \"LF\",\n    \"resolveJsonModule\": true\n  },\n  \"include\": [\"server\"]\n}\n"
  },
  {
    "path": "ui/apollo-client.ts",
    "content": "import { ApolloClient, HttpLink, InMemoryCache } from \"apollo-client-preset\";\nimport { WebSocketLink } from \"apollo-link-ws\";\nimport { getMainDefinition } from \"apollo-utilities\";\nimport { split } from \"apollo-link\";\n\ndeclare var PRODUCTION: boolean;\n\nlet WS_URL = \"ws://localhost:4000\";\nlet HTTP_URL = \"http://localhost:4000\";\nif (PRODUCTION) {\n  const WS_PROTOCOL = window.location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n  WS_URL = `${WS_PROTOCOL}//${window.location.host}`;\n  HTTP_URL = `${window.location.protocol}//${window.location.host}`;\n}\n\nexport function getAPIUrl() {\n  return HTTP_URL;\n}\n\nconst wsLink = new WebSocketLink({\n  uri: WS_URL,\n  options: {\n    reconnect: true\n  }\n});\n\nconst httpLink = new HttpLink({ uri: HTTP_URL });\n\nconst link = split(\n  ({ query }: any) => {\n    const { kind, operation } = getMainDefinition(query);\n    return kind === \"OperationDefinition\" && operation === \"subscription\";\n  },\n  wsLink,\n  httpLink\n);\n\nconst client = new ApolloClient({\n  link,\n  cache: new InMemoryCache()\n});\n\nexport default client;\n"
  },
  {
    "path": "ui/app.gql",
    "content": "{\n  app {\n    selectedFile\n  }\n}\n"
  },
  {
    "path": "ui/app.tsx",
    "content": "import React, { useState } from \"react\";\nimport styled from \"styled-components\";\nimport SplitPane from \"react-split-pane\";\nimport { useQuery, useMutation } from \"react-apollo-hooks\";\nimport Sidebar from \"./sidebar\";\nimport TestFile from \"./test-file\";\nimport APP from \"./app.gql\";\nimport WORKSPACE from \"./query.gql\";\nimport useKeys from \"./hooks/use-keys\";\nimport useSubscription from \"./test-file/use-subscription\";\nimport SUMMARY_QUERY from \"./summary-query.gql\";\nimport SUMMARY_SUBS from \"./summary-subscription.gql\";\nimport RUNNER_STATUS_QUERY from \"./runner-status-query.gql\";\nimport RUNNER_STATUS_SUBS from \"./runner-status-subs.gql\";\nimport STOP_RUNNER from \"./stop-runner.gql\";\nimport { Search } from \"./search\";\nimport SET_SELECTED_FILE from \"./set-selected-file.gql\";\nimport { Workspace } from \"../server/api/workspace/workspace\";\nimport { color } from \"styled-system\";\nimport { RunnerStatus } from \"../server/api/runner/status\";\nimport { Summary } from \"../server/api/workspace/summary\";\nimport CoveragePanel from \"./coverage-panel\";\n\nconst ContainerDiv = styled.div`\n  display: flex;\n  flex-direction: row;\n  width: 100%;\n`;\n\nconst PlaceHolder = styled.div<any>`\n  display: flex;\n  height: 100%;\n  ${color}\n`;\n\ninterface AppResult {\n  app: { selectedFile: string };\n}\n\ninterface WorkspaceResult {\n  workspace: Workspace;\n}\n\nexport default function App() {\n  const {\n    data: {\n      app: { selectedFile }\n    },\n    refetch\n  } = useQuery<AppResult>(APP);\n\n  const {\n    data: { workspace },\n    refetch: refetchFiles\n  } = useQuery<WorkspaceResult>(WORKSPACE);\n\n  const { data: summary }: { data: Summary } = useSubscription(\n    SUMMARY_QUERY,\n    SUMMARY_SUBS,\n    {},\n    result => result.summary,\n    result => result.changeToSummary,\n    \"Summary Sub\"\n  );\n\n  const { data: runnerStatus }: { data: RunnerStatus } = useSubscription(\n    RUNNER_STATUS_QUERY,\n    RUNNER_STATUS_SUBS,\n    {},\n    result => result.runnerStatus,\n    result => result.runnerStatusChange,\n    \"Runner subs\"\n  );\n\n  const setSelectedFile = useMutation(SET_SELECTED_FILE);\n  const handleFileSelection = (path: string | null) => {\n    if (path !== null) {\n      setShowCoverage(false);\n    }\n\n    setSelectedFile({\n      variables: {\n        path\n      }\n    });\n    refetch();\n  };\n\n  const stopRunner = useMutation(STOP_RUNNER);\n\n  const [isSearchOpen, setSearchOpen] = useState(false);\n  const keys = useKeys();\n  if (isSearchOpen && keys.has(\"Escape\")) {\n    setSearchOpen(false);\n  }\n\n  const [showCoverage, setShowCoverage] = useState(false);\n\n  return (\n    <ContainerDiv>\n      <SplitPane\n        defaultSize={\"calc(100% - 300px)\"}\n        split=\"vertical\"\n        primary=\"second\"\n        pane1Style={{ minWidth: \"300px\" }}\n        pane2Style={{ maxWidth: \"calc(100% - 300px)\" }}\n      >\n        <Sidebar\n          workspace={workspace}\n          selectedFile={selectedFile}\n          onSelectedFileChange={handleFileSelection}\n          summary={summary}\n          runnerStatus={runnerStatus}\n          showCoverage={showCoverage}\n          onSearchOpen={() => {\n            setSearchOpen(true);\n          }}\n          onRefreshFiles={() => {\n            refetchFiles();\n          }}\n          onStop={() => {\n            stopRunner();\n          }}\n          onShowCoverage={() => {\n            setShowCoverage(!showCoverage);            \n          }}\n        />\n        {showCoverage && <CoveragePanel />}\n        {selectedFile ? (\n          <TestFile\n            projectRoot={workspace.projectRoot}\n            selectedFilePath={selectedFile}\n            isRunning={\n              (runnerStatus.running &&\n                runnerStatus.activeFile === selectedFile) ||\n              ((summary && summary.executingTests) || []).includes(selectedFile)\n            }\n            onStop={() => {\n              stopRunner();\n            }}\n          />\n        ) : (\n          <PlaceHolder bg=\"dark\" />\n        )}\n      </SplitPane>\n      <Search\n        projectRoot={workspace.projectRoot}\n        show={isSearchOpen}\n        files={workspace.files}\n        onClose={() => setSearchOpen(false)}\n        onItemClick={path => {\n          handleFileSelection(path);\n          setSearchOpen(false);\n        }}\n      />\n    </ContainerDiv>\n  );\n}\n"
  },
  {
    "path": "ui/components/button.tsx",
    "content": "import React from \"react\";\nimport styled from \"styled-components\";\nimport { space, color, fontSize } from \"styled-system\";\n\nconst StyledButton = styled.button<any>`\n  display: flex;\n  align-items: center;\n  color: ${props => (props.minimal ? \"#ffffff\" : \"#242326\")};\n  text-align: center;\n  transition: all 0.5s;\n  border: 1px solid #ffd062;\n  border-radius: 3px;\n  background-color: ${props => (props.minimal ? \"transparent\" : \"#FFD062\")};\n  cursor: pointer;\n  margin-right: 5px;\n  padding: 6px;\n  ${color};\n  ${fontSize};\n  &:hover {\n    background-color: ${props => (props.bg ? props.bg : \"#ffd062\")};\n  }\n\n  &:focus {\n    outline: none;\n  }\n`;\n\nconst Spacer = styled.div`\n  width: 5px;\n`;\n\nexport default function Button(props: any) {\n  return (\n    <StyledButton fontSize={12} {...props}>\n      {props.icon}\n      {props.icon && props.children && <Spacer />}\n      {props.children}\n    </StyledButton>\n  );\n}\n"
  },
  {
    "path": "ui/container.tsx",
    "content": "import React, { Component, Suspense } from \"react\";\nimport { ApolloProvider as ApolloHooksProvider } from \"react-apollo-hooks\";\nimport { ApolloProvider } from \"react-apollo\";\nimport { ThemeProvider } from \"styled-components\";\nimport client from \"./apollo-client\";\nimport App from \"./app\";\nimport theme from \"./theme\";\nimport { createGlobalStyle } from \"styled-components\";\nimport splitPanelCSS from \"./split-panel-style\";\nimport \"typeface-open-sans\";\nimport Loading from \"./loading\";\nimport { ErrorBoundary } from \"./error\";\n\nconst GlobalStyle = createGlobalStyle`\n body { font-family: 'Open sans'; font-size: 13px; margin: 0px;}\n ${splitPanelCSS}\n`;\n\nexport default class Container extends Component {\n  render() {\n    return (\n      <React.Fragment>\n        <GlobalStyle />\n        <ThemeProvider theme={theme}>\n          <ApolloHooksProvider client={client}>\n            <ApolloProvider client={client}>\n              <Suspense fallback={<Loading />}>\n                <ErrorBoundary>\n                  <App />\n                </ErrorBoundary>\n              </Suspense>\n            </ApolloProvider>\n          </ApolloHooksProvider>\n        </ThemeProvider>\n      </React.Fragment>\n    );\n  }\n}\n"
  },
  {
    "path": "ui/coverage-panel/index.tsx",
    "content": "import React from \"react\";\nimport styled from \"styled-components\";\nimport { getAPIUrl } from \"../apollo-client\";\n\nconst Frame = styled.iframe`\n  width: 100%;\n  height: 100%;\n`;\n\nexport default function CoveragePanel() {\n  return (\n    <Frame\n      src={`${getAPIUrl()}/coverage/lcov-report/index.html`}\n      frameBorder=\"0\"\n    />\n  );\n}\n"
  },
  {
    "path": "ui/error.tsx",
    "content": "import React, { Component } from \"react\";\nimport styled from \"styled-components\";\n\nconst Container = styled.div`\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  height: 100vh;\n  align-items: center;\n  justify-content: center;\n  background-color: #262529;\n  color: #fdc055;\n  font-size: 25px;\n  font-weight: 500;\n`;\n\nconst Loader = styled.div`\n  margin-bottom: 20px;\n  svg {\n    text-align: center;\n    margin: auto;\n    width: 60px;\n    height: 60px;\n  }\n\n  #icon-stop-circle .stopping {\n    animation-name: stopping;\n    animation-duration: 5s;\n    animation-timing-function: ease-in-out;\n    animation-iteration-count: infinite;\n    transform-origin: center center;\n  }\n\n  @keyframes stopping {\n    from,\n    50%,\n    to {\n      opacity: 1;\n      fill: #ea3970;\n      stroke: none;\n    }\n\n    25%,\n    75% {\n      opacity: 0;\n    }\n  }\n`;\n\nconst Message = styled.div`\n  font-size: 15px;\n`;\n\nexport class ErrorBoundary extends Component {\n  state = {\n    didError: false\n  };\n\n  componentDidCatch() {\n    this.setState({\n      didError: true\n    });\n  }\n\n  render() {\n    if (!this.state.didError) {\n      return this.props.children;\n    }\n\n    return (\n      <Container>\n        <Loader>\n          <svg\n            id=\"icon-stop-circle\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"24\"\n            height=\"24\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            stroke=\"aliceblue\"\n            strokeWidth=\"2\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          >\n            <circle cx=\"12\" cy=\"12\" r=\"10\" />\n            <rect className=\"stopping\" x=\"9\" y=\"9\" width=\"6\" height=\"6\" />\n          </svg>\n        </Loader>\n        <Message>\n          Oops, Something went wrong. Check the terminal for exact error\n          message!\n        </Message>\n      </Container>\n    );\n  }\n}\n"
  },
  {
    "path": "ui/hooks/use-keys.ts",
    "content": "import { useEffect, useState } from \"react\";\n\nexport function hasKeys(expectedKeys: string[], pressedKeys: Map<String, boolean> ) {\n  return expectedKeys.every(k => pressedKeys.has(k));\n};\n\nexport default function useKeys() {\n  const [keys, setKeys] = useState(new Map());\n  const hotKeys = [\"Alt\", \"Enter\", \"Escape\", \"s\", \"t\", \"w\"];\n\n  function downHandler({ key }:KeyboardEvent) {\n    // only update state for keys we are watching\n    if (hotKeys.includes(key)) {\n      keys.set(key, true);\n      // create a new Map object to guarantee that state updates\n      setKeys(new Map(keys));\n    }\n  }\n\n  const upHandler = ({ key }:KeyboardEvent) => {\n    if (hotKeys.includes(key)) {\n      keys.delete(key);\n      setKeys(new Map(keys));\n    }\n  };\n\n  useEffect(() => {\n    window.addEventListener(\"keydown\", downHandler);\n    window.addEventListener(\"keyup\", upHandler);\n    return () => {\n      window.removeEventListener(\"keydown\", downHandler);\n      window.removeEventListener(\"keyup\", upHandler);\n    };\n  }, []);\n\n  return keys;\n}\n"
  },
  {
    "path": "ui/index.tsx",
    "content": "import React from \"react\";\nimport ReactDOM from \"react-dom\";\nimport \"@babel/polyfill\";\nimport Container from \"./container\";\nimport \"react-tippy/dist/tippy.css\";\n\nReactDOM.render(<Container />, document.getElementById(\"root\"));\n\nif ((module as any).hot) {\n  (module as any).hot.accept(\"./container\", () => {\n    const NextApp = require(\"./container\").default;\n    ReactDOM.render(<Container />, document.getElementById(\"root\"));\n  });\n}\n"
  },
  {
    "path": "ui/loading.tsx",
    "content": "import React from \"react\";\nimport styled from \"styled-components\";\n\nconst Container = styled.div`\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  height: 100vh;\n  align-items: center;\n  justify-content: center;\n  background-color: #262529;\n  color: #fdc055;\n  font-size: 25px;\n  font-weight: 500;\n`;\n\nconst Loader = styled.div`\n  margin-bottom: 20px;\n  svg {\n    text-align: center;\n    margin: auto;\n    width: 60px;\n    height: 60px;\n  }\n\n  #icon-crop-button {\n    animation: cropped 1s alternate infinite ease-in-out;\n    transform-origin: center;\n    fill: aliceblue;\n  }\n\n  @-webkit-keyframes cropped {\n    0% {\n      transform: rotate(0deg) scale(1);\n    }\n\n    50% {\n      transform: rotate(90deg) scale(0.9);\n    }\n\n    100% {\n      transform: rotate(180deg) scale(1);\n    }\n  }\n\n  @keyframes cropped {\n    0% {\n      transform: rotate(0deg) scale(1);\n    }\n\n    50% {\n      transform: rotate(90deg) scale(0.9);\n    }\n\n    100% {\n      transform: rotate(180deg) scale(1);\n    }\n  }\n`;\n\nconst Message = styled.div`\n  font-size: 15px;\n`;\n\nexport default function Loading() {\n  return (\n    <Container>\n      <Loader>\n        <svg\n          id=\"icon-crop-button\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n          viewBox=\"0 0 459 459\"\n        >\n          <path d=\"M0 51v102h51V51h102V0H51C23 0 0 23 0 51zm51 255H0v102c0 28 23 51 51 51h102v-51H51V306zm357 102H306v51h102c28 0 51-23 51-51V306h-51v102zm0-408H306v51h102v102h51V51c0-28-23-51-51-51z\" />\n        </svg>\n      </Loader>\n      <Message>Getting things ready for you</Message>\n    </Container>\n  );\n}\n"
  },
  {
    "path": "ui/query.gql",
    "content": "{\n  workspace {\n    projectRoot\n    name\n    files {\n      path\n      name\n      type\n      parent\n    }\n  }\n}\n"
  },
  {
    "path": "ui/runner-status-query.gql",
    "content": "{\n  runnerStatus {\n    running\n    activeFile\n    watching\n  }\n}\n"
  },
  {
    "path": "ui/runner-status-subs.gql",
    "content": "subscription {\n  runnerStatusChange {\n    running\n    activeFile\n    watching\n  }\n}\n"
  },
  {
    "path": "ui/search/index.tsx",
    "content": "import React, { useEffect, useRef, useState } from \"react\";\nimport styled from \"styled-components\";\nimport { Item } from \"../../server/api/workspace/tree\";\nimport { color } from \"styled-system\";\n\nconst Drop = styled.div`\n  position: absolute;\n  background-color: #444444;\n  opacity: 0.7;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0px;\n  z-index: 1;\n`;\n\nconst Container = styled.div<any>`\n  width: 700px;\n  max-height: 500px;\n  position: absolute;\n  z-index: 1;\n  margin-left: auto;\n  margin-right: auto;\n  left: 0;\n  right: 0;\n  top: 150px;\n  padding: 20px;\n  border-radius: 4px;\n  display: flex;\n  flex-direction: column;\n  ${color};\n`;\n\nconst ItemContainer = styled.div`\n  display: flex;\n  padding: 5px;\n  cursor: pointer;\n  color: #fefefe;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  border-radius: 1px;\n  min-height: 20px;\n\n  &:hover {\n    background-color: #404148;\n  }\n`;\n\nconst ResultContainer = styled.div`\n  display: flex;\n  flex-direction: column;\n  overflow: auto;\n`;\n\nconst SearchBox = styled.input`\n  padding: 5px;\n  border: none;\n  width: 99%;\n  margin-bottom: 15px;\n  padding: 5px;\n  font-size: 13px;\n  border-radius: 2px;\n\n  &:focus {\n    outline: none;\n  }\n`;\n\ninterface Props {\n  projectRoot: string;\n  show: boolean;\n  files: Item[];\n  onItemClick: (path: string) => void;\n  onClose: () => void;\n}\n\nexport function Search({\n  projectRoot,\n  files,\n  show,\n  onItemClick,\n  onClose\n}: Props) {\n  const onlyFiles = files.filter(file => file.type === \"file\");\n\n  const [query, setQuery] = useState(\"\");\n  const searchBoxRef = useRef<HTMLInputElement>(null);\n  useEffect(() => {\n    if (searchBoxRef && searchBoxRef.current) {\n      searchBoxRef.current.focus();\n    }\n  }, [show]);\n\n  if (!show) return null;\n\n  return (\n    <React.Fragment>\n      <Drop onClick={onClose} />\n      <Container bg=\"dark\">\n        <SearchBox\n          ref={searchBoxRef}\n          value={query}\n          placeholder=\"Start searching…\"\n          onChange={(event: any) => {\n            setQuery(event.target.value);\n          }}\n        />\n        <ResultContainer>\n          {onlyFiles\n            .filter(file =>\n              file.path.toLowerCase().includes(query.toLowerCase())\n            )\n            .map((file: any, index: number) => (\n              <ItemContainer\n                key={index}\n                onClick={() => {\n                  onItemClick(file.path);\n                }}\n              >\n                {file.path.toLowerCase().replace(projectRoot.toLowerCase(), \"\")}\n              </ItemContainer>\n            ))}\n        </ResultContainer>\n      </Container>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "ui/set-selected-file.gql",
    "content": "mutation SetSelectedFile($path: String) {\n  setSelectedFile(path: $path) {\n    selectedFile\n  }\n}\n"
  },
  {
    "path": "ui/sidebar/execution-indicator.tsx",
    "content": "import React from \"react\";\n\nexport default function ExecutionIndicator() {\n  return (\n    <svg\n      version=\"1.1\"\n      id=\"Layer_1\"\n      x=\"0px\"\n      y=\"0px\"\n      width=\"18px\"\n      height=\"12px\"\n      viewBox=\"0 0 24 30\"\n    >\n      <rect x=\"0\" y=\"0\" width=\"4\" height=\"15\" fill=\"#fcd101\">\n        <animate\n          attributeName=\"opacity\"\n          attributeType=\"XML\"\n          values=\"1; .2; 1\"\n          begin=\"0s\"\n          dur=\"0.6s\"\n          repeatCount=\"indefinite\"\n        />\n      </rect>\n      <rect x=\"7\" y=\"0\" width=\"4\" height=\"15\" fill=\"#DFBD39\">\n        <animate\n          attributeName=\"opacity\"\n          attributeType=\"XML\"\n          values=\"1; .2; 1\"\n          begin=\"0.2s\"\n          dur=\"0.6s\"\n          repeatCount=\"indefinite\"\n        />\n      </rect>\n      <rect x=\"14\" y=\"0\" width=\"4\" height=\"15\" fill=\"#fcd101\">\n        <animate\n          attributeName=\"opacity\"\n          attributeType=\"XML\"\n          values=\"1; .2; 1\"\n          begin=\"0.4s\"\n          dur=\"0.6s\"\n          repeatCount=\"indefinite\"\n        />\n      </rect>\n    </svg>\n  );\n}\n"
  },
  {
    "path": "ui/sidebar/file-item.tsx",
    "content": "import React, { memo } from \"react\";\nimport styled from \"styled-components\";\nimport {\n  File,\n  Folder,\n  ChevronRight,\n  ChevronDown,\n  Frown,\n  ZapOff,\n} from \"react-feather\";\nimport { color } from \"styled-system\";\nimport { TreeNode } from \"./transformer\";\nimport ExecutionIndicator from \"./execution-indicator\";\n\nconst Container = styled.div`\n  display: flex;\n  flex-direction: column;\n  margin-left: 20px;\n  ${color};\n`;\n\nconst Content = styled.div<any>`\n  display: flex;\n  align-items: center;\n  padding: 2.5px;\n  cursor: pointer;\n  color: ${(props) =>\n    props.failed ? \"#FE5339\" : props.passing ? \"#19E28D\" : null};\n  background-color: ${(props) => (props.selected ? \"#444444\" : null)};\n  border-radius: 3px;\n  margin-bottom: 2px;\n  font-weight: 600;\n\n  &:hover {\n    background-color: #444444;\n  }\n`;\n\nconst Label = styled.div`\n  margin-left: 5px;\n  font-size: 12px;\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n`;\n\nconst EmptyChevron = styled.div`\n  width: 5px;\n`;\n\nconst ExecutionWrapper = styled.div``;\n\ninterface Props {\n  item: TreeNode;\n  style: any;\n  selectedFile: string;\n  setSelectedFile: (path: string) => void;\n  onToggle: (path: string, isCollapsed: boolean) => void;\n}\n\nfunction FileItem({\n  item,\n  selectedFile,\n  setSelectedFile,\n  onToggle,\n  style,\n}: Props) {\n  const Icon =\n    item.type === \"directory\" ? Folder : item.haveFailure ? ZapOff : File;\n  let Chevron: any = EmptyChevron;\n  if (item.type === \"directory\") {\n    Chevron = item.isCollapsed ? ChevronRight : ChevronDown;\n  }\n\n  const handleClick = () => {\n    if (item.type === \"file\") {\n      setSelectedFile(item.path);\n    }\n\n    if (item.type === \"directory\") {\n      onToggle(item.path, !item.isCollapsed);\n    }\n  };\n\n  return (\n    <Container\n      style={{\n        ...style,\n        width: \"90%\",\n        marginLeft: `${(item.hierarchy + 1) * 15}px`,\n      }}\n    >\n      <Content\n        hierarchy={item.hierarchy}\n        passing={item.passing}\n        failed={item.haveFailure}\n        selected={selectedFile === item.path}\n        onClick={handleClick}\n      >\n        <Chevron size={11} />\n        {!item.isExecuting && <Icon size={11} />}\n        {item.isExecuting && (\n          <ExecutionWrapper>\n            <ExecutionIndicator />\n          </ExecutionWrapper>\n        )}\n        <Label>{item.name}</Label>\n      </Content>\n    </Container>\n  );\n}\n\nexport default memo(FileItem, (pre: Props, next: Props) => {\n  return (\n    pre.item.isExecuting === next.item.isExecuting &&\n    pre.item.isCollapsed === next.item.isCollapsed &&\n    pre.selectedFile === next.selectedFile\n  );\n});\n"
  },
  {
    "path": "ui/sidebar/index.tsx",
    "content": "import React, { useState } from \"react\";\nimport styled from \"styled-components\";\nimport { useMutation, useQuery } from \"react-apollo-hooks\";\nimport { space, color } from \"styled-system\";\nimport { Tooltip } from \"react-tippy\";\nimport SET_WATCH_MODE from \"./set-watch-mode.gql\";\nimport SHOULD_COLLECT_COVERAGE from \"./should-collect-coverage.gql\";\nimport SET_COLLECT_COVERAGE from \"./set-collect-coverage.gql\";\nimport { Workspace } from \"../../server/api/workspace/workspace\";\nimport { transform, filterFailure } from \"./transformer\";\nimport Summary from \"./summary\";\nimport { Summary as SummaryType } from \"../../server/api/workspace/summary\";\nimport RUN from \"./run.gql\";\nimport useKeys, { hasKeys } from \"../hooks/use-keys\";\nimport {\n  Play,\n  Eye,\n  Search,\n  RefreshCw,\n  ZapOff,\n  StopCircle,\n  FileText,\n  Layers,\n  ChevronDown,\n  ChevronRight\n} from \"react-feather\";\nimport Button from \"../components/button\";\nimport { RunnerStatus } from \"../../server/api/runner/status\";\nimport Tree from \"./tree\";\nimport Logo from \"./logo\";\n\nconst Container = styled.div<any>`\n  ${space};\n  ${color};\n  height: 100vh;\n`;\n\nconst ActionsPanel = styled.div<any>`\n  ${space}\n  display: flex;\n  justify-content: space-between;\n`;\n\nconst RightActionPanel = styled.div`\n  display: flex;\n`;\n\nconst FileHeader = styled.div<any>`\n  ${space}\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  font-size: 13px;\n`;\n\nconst FilesHeader = styled.div`\n  font-weight: 400;\n  font-size: 11px;\n`;\n\nconst RightFilesAction = styled.div`\n  display: flex;\n`;\n\ninterface Props {\n  selectedFile: string;\n  workspace: Workspace;\n  summary: SummaryType | undefined;\n  runnerStatus?: RunnerStatus;\n  showCoverage: boolean;\n  onSelectedFileChange: (path: string) => void;\n  onSearchOpen: () => void;\n  onRefreshFiles: () => void;\n  onStop: () => void;\n  onShowCoverage: () => void;\n}\n\nexport default function TestExplorer ({\n  selectedFile,\n  workspace,\n  onSelectedFileChange,\n  summary,\n  showCoverage,\n  runnerStatus,\n  onSearchOpen,\n  onRefreshFiles,\n  onStop,\n  onShowCoverage\n}: Props) {\n  const failedItems = (summary && summary.failedTests) || [];\n  const executingItems = (summary && summary.executingTests) || [];\n  const passingTests = (summary && summary.passingTests) || [];\n\n  const run = useMutation(RUN);\n\n  const [collapsedItems, setCollapsedItems] = useState({});\n  const handleFileToggle = (path: string, isCollapsed: boolean) => {\n    setCollapsedItems({\n      ...collapsedItems,\n      [path]: isCollapsed\n    });\n  };\n\n  const [showFailedTests, setShowFailedTests] = useState(false);\n\n  const items = workspace.files;\n  const root = items[0];\n  let files = transform(\n    root as any,\n    executingItems,\n    failedItems,\n    passingTests,\n    collapsedItems,\n    showFailedTests,\n    items\n  );\n\n  const onCollapseAll = () => {\n    const newCollapsedItems = {};\n    files.forEach(file => {\n      if (file.type === \"directory\" && file.parent) {\n        newCollapsedItems[file.path] = true;\n      }\n    });\n    setCollapsedItems(newCollapsedItems)\n  }\n  const onExpandAll = () => {\n    setCollapsedItems({})\n  }\n\n  if (showFailedTests && failedItems.length) {\n    files = filterFailure(files);\n  }\n\n  const {\n    data: { shouldCollectCoverage },\n    refetch: refetchCoverageFlag\n  } = useQuery<any>(SHOULD_COLLECT_COVERAGE);\n  const setCollectCoverage = useMutation(SET_COLLECT_COVERAGE);\n\n  const handleFileSelection = (path: string) => {\n    onSelectedFileChange(path);\n  };\n\n  const setWatchMode = useMutation(SET_WATCH_MODE);\n  const handleSetWatchModel = (watch: boolean) => {\n    setWatchMode({\n      variables: {\n        watch\n      }\n    });\n  };\n\n  const isRunning = runnerStatus && runnerStatus.running;\n  const keys = useKeys();\n  if (hasKeys([\"Alt\", \"t\"], keys)) {\n    run();\n  } else if (hasKeys([\"Alt\", \"w\"], keys)) {\n    if (runnerStatus) {\n      handleSetWatchModel(!runnerStatus.watching);\n    }\n  } else if (hasKeys([\"Alt\", \"s\"], keys)) {\n    onSearchOpen();\n  }\n\n  return (\n    <Container p={4} bg=\"veryDark\" color=\"text\">\n      <Logo />\n      <ActionsPanel mb={4}>\n        <Tooltip title=\"Run all tests\" position=\"bottom\" size=\"small\">\n          <Button\n            icon={isRunning ? <StopCircle size={15} /> : <Play size={15} />}\n            size=\"sm\"\n            onClick={() => {\n              if (isRunning) {\n                onStop();\n              } else {\n                run();\n              }\n            }}\n          >\n            {isRunning ? \"Stop\" : \"Run tests\"}\n          </Button>\n        </Tooltip>\n        <RightActionPanel>\n          <Tooltip title=\"Toggle watch mode\" position=\"bottom\" size=\"small\">\n            <Button\n              icon={<Eye size={14} />}\n              minimal\n              onClick={() => {\n                if (runnerStatus) {\n                  handleSetWatchModel(!runnerStatus.watching);\n                }\n              }}\n            >\n              {runnerStatus && runnerStatus.watching\n                ? \"Stop Watching\"\n                : \"Watch\"}\n            </Button>\n          </Tooltip>\n          <Tooltip title=\"Collect coverage\" position=\"bottom\" size=\"small\">\n            <Button\n              minimal={!shouldCollectCoverage}\n              onClick={() => {\n                setCollectCoverage({\n                  variables: {\n                    collect: !shouldCollectCoverage\n                  }\n                });\n                refetchCoverageFlag();\n              }}\n            >\n              <FileText size={14} />\n            </Button>\n          </Tooltip>\n          <Tooltip title=\"Search test files\" position=\"bottom\" size=\"small\">\n            <Button\n              minimal\n              onClick={() => {\n                onSearchOpen();\n              }}\n            >\n              <Search size={14} />\n            </Button>\n          </Tooltip>\n        </RightActionPanel>\n      </ActionsPanel>\n      <Summary summary={summary} />\n      <FileHeader mt={4} mb={3}>\n        <FilesHeader>Tests</FilesHeader>\n        <RightFilesAction>\n          {summary && summary.failedTests && summary.failedTests.length > 0 && (\n            <Tooltip\n              title=\"Show only failed tests\"\n              position=\"top\"\n              size=\"small\"\n            >\n              <Button\n                size=\"sm\"\n                minimal={!showFailedTests}\n                onClick={() => {\n                  setShowFailedTests(!showFailedTests);\n                }}\n              >\n                <ZapOff size={10} />\n              </Button>\n            </Tooltip>\n          )}\n          {!showFailedTests && (\n            <Tooltip title=\"Collapse All Tests\" position=\"top\" size=\"small\">\n              <Button\n                size=\"sm\"\n                minimal\n                onClick={onCollapseAll}\n              >\n                <ChevronRight size={10} />\n              </Button>\n            </Tooltip>\n          )}\n          {!showFailedTests && (\n            <Tooltip title=\"Expand All Tests\" position=\"top\" size=\"small\">\n              <Button\n                size=\"sm\"\n                minimal\n                onClick={onExpandAll}\n              >\n                <ChevronDown size={10} />\n              </Button>\n            </Tooltip>\n          )}\n          {summary && summary.haveCoverageReport && (\n            <Tooltip\n              title=\"Show coverage report\"\n              position=\"top\"\n              size=\"small\"\n            >\n              <Button\n                size=\"sm\"\n                minimal={!showCoverage}\n                onClick={() => {\n                  onShowCoverage();\n                }}\n              >\n                <Layers size={10} />\n              </Button>\n            </Tooltip>\n          )}\n          <Tooltip title=\"Refresh files\" position=\"top\" size=\"small\">\n            <Button\n              size=\"sm\"\n              minimal\n              onClick={() => {\n                onRefreshFiles();\n              }}\n            >\n              <RefreshCw size={10} />\n            </Button>\n          </Tooltip>\n        </RightFilesAction>\n      </FileHeader>\n      <Tree\n        results={files}\n        selectedFile={selectedFile}\n        onFileSelection={handleFileSelection}\n        onToggle={handleFileToggle}\n      />\n    </Container>\n  );\n}\n"
  },
  {
    "path": "ui/sidebar/logo.tsx",
    "content": "import React from \"react\";\nimport styled from \"styled-components\";\nimport logo from \"../assets/logo.png\";\n\nconst Container = styled.div`\n  font-size: 25px;\n  text-align: center;\n  margin-bottom: 15px;\n`;\n\nexport default function Logo() {\n  return (\n    <Container>\n      <img width={200} src={logo} />\n    </Container>\n  );\n}\n"
  },
  {
    "path": "ui/sidebar/run.gql",
    "content": "mutation {\n  run\n}\n"
  },
  {
    "path": "ui/sidebar/set-collect-coverage.gql",
    "content": "mutation SetCollectCoverage($collect: Boolean!) {\n  setCollectCoverage(collect: $collect)\n}\n"
  },
  {
    "path": "ui/sidebar/set-watch-mode.gql",
    "content": "mutation SetWatchMode($watch: Boolean!) {\n  toggleWatch(watch: $watch) {\n    watching\n  }\n}\n"
  },
  {
    "path": "ui/sidebar/should-collect-coverage.gql",
    "content": "{\n  shouldCollectCoverage\n}\n"
  },
  {
    "path": "ui/sidebar/summary/index.tsx",
    "content": "import React from \"react\";\nimport styled from \"styled-components\";\nimport { space } from \"styled-system\";\nimport { useSpring, animated } from \"react-spring\";\nimport { CheckCircle, ZapOff, Layers } from \"react-feather\";\nimport { Summary } from \"../../../server/api/workspace/summary\";\n\nconst Container = styled.div<any>`\n  ${space};\n`;\n\nconst Row = styled.div`\n  display: flex;\n  font-size: 16px;\n  margin-bottom: 5px;\n`;\n\nconst Cell = styled.div`\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n`;\n\nconst Label = styled.div`\n  font-size: 12px;\n  color: #dcdbdb;\n`;\n\nconst Value = styled.div<any>`\n  font-size: 20px;\n  color: ${props => (props.failed ? \"#FF4F56\" : \"#19E28D\")};\n`;\n\nconst CoverageLabel = styled.div`\n  font-size: 10px;\n  color: #dcdbdb;\n`;\n\nconst CoverageValue = styled.div<any>`\n  font-size: 14px;\n`;\n\nconst Coverage = styled.div`\n  margin-top: 10px;\n`;\n\ninterface Props {\n  summary: Summary | undefined;\n}\n\nexport default function SummaryPanel({ summary }: Props) {\n  const passedSuitesProps = useSpring({\n    number: summary && summary.numPassedTestSuites | 0,\n    from: { number: 0 }\n  } as any);\n\n  const failedSuitesProps = useSpring({\n    number: summary && summary.numFailedTestSuites | 0,\n    from: { number: 0 }\n  } as any);\n\n  const passedTestProps = useSpring({\n    number: summary && summary.numPassedTests | 0,\n    from: { number: 0 }\n  } as any);\n\n  const failedTestProps = useSpring({\n    number: summary && summary.numFailedTests | 0,\n    from: { number: 0 }\n  } as any);\n\n  const coverage = summary && summary.coverage;\n  const haveCoverage =\n    coverage &&\n    (coverage.branch ||\n      coverage.function ||\n      coverage.line ||\n      coverage.statement);\n\n  return (\n    <Container mt={3} mb={3}>\n      <Row>\n        <Cell>\n          <Value>\n            <animated.span>\n              {(passedSuitesProps as any).number.interpolate((value: any) =>\n                value.toFixed()\n              )}\n            </animated.span>\n          </Value>\n          <Label>\n            <CheckCircle size={11} /> Passing suites\n          </Label>\n        </Cell>\n        <Cell>\n          <Value failed>\n            <animated.span>\n              {(failedSuitesProps as any).number.interpolate((value: any) =>\n                value.toFixed()\n              )}\n            </animated.span>\n          </Value>\n          <Label>\n            <ZapOff size={11} /> Failing suites\n          </Label>\n        </Cell>\n      </Row>\n      <Row>\n        <Cell>\n          <Value>\n            <animated.span>\n              {(passedTestProps as any).number.interpolate((value: any) =>\n                value.toFixed()\n              )}\n            </animated.span>\n          </Value>\n          <Label>\n            <CheckCircle size={11} /> Passing tests\n          </Label>\n        </Cell>\n        <Cell>\n          <Value failed>\n            <animated.span>\n              {(failedTestProps as any).number.interpolate((value: any) =>\n                value.toFixed()\n              )}\n            </animated.span>\n          </Value>\n          <Label>\n            <ZapOff size={11} /> Failing tests\n          </Label>\n        </Cell>\n      </Row>\n      {!!haveCoverage && (\n        <Coverage>\n          <Row>\n            <Cell>\n              <CoverageValue>\n                {summary && summary.coverage && summary.coverage.statement}%\n              </CoverageValue>\n              <CoverageLabel>\n                <Layers size={9} /> Stmts\n              </CoverageLabel>\n            </Cell>\n            <Cell>\n              <CoverageValue>\n                {summary && summary.coverage && summary.coverage.branch}%\n              </CoverageValue>\n              <CoverageLabel>\n                <Layers size={9} /> Branch\n              </CoverageLabel>\n            </Cell>\n            <Cell>\n              <CoverageValue>\n                {summary && summary.coverage && summary.coverage.function}%\n              </CoverageValue>\n              <CoverageLabel>\n                <Layers size={9} /> Funcs\n              </CoverageLabel>\n            </Cell>\n            <Cell>\n              <CoverageValue>\n                {summary && summary.coverage && summary.coverage.line}%\n              </CoverageValue>\n              <CoverageLabel>\n                <Layers size={9} /> Lines\n              </CoverageLabel>\n            </Cell>\n          </Row>\n        </Coverage>\n      )}\n    </Container>\n  );\n}\n"
  },
  {
    "path": "ui/sidebar/transformer.ts",
    "content": "import { Item } from \"../../server/api/workspace/tree\";\n\nexport interface TreeNode extends Item {\n  name: string;\n  path: string;\n  isCollapsed: boolean;\n  haveFailure: boolean;\n  passing: boolean;\n  isExecuting: boolean;\n  hierarchy: number;\n}\n\nexport function transform(\n  item: TreeNode,\n  executingTests: string[],\n  failedFiles: string[],\n  passingTests: string[],\n  collapsedFiles: { [path: string]: boolean },\n  showFailedTests: boolean,\n  items: Item[],\n  results: TreeNode[] = [],\n  hierarchy = 0\n) {\n  const isCollapsed = collapsedFiles[item.path] && !showFailedTests; // when showing failed tests, keep all expanded\n  const haveFailure = failedFiles.indexOf(item.path) > -1;\n  const nextChildren = getChildren(item.path, items);\n\n  const treeItem = {\n    type: item.type,\n    name: item.name,\n    path: item.path,\n    parent: item.parent,\n    hierarchy: hierarchy,\n    isCollapsed: isCollapsed,\n    passing: passingTests.indexOf(item.path) > -1,\n    haveFailure,\n    isExecuting: executingTests.indexOf(item.path) > -1\n  };\n\n  results.push(treeItem);\n\n  if (!isCollapsed) {\n    nextChildren.forEach(item => {\n      transform(\n        item as any,\n        executingTests,\n        failedFiles,\n        passingTests,\n        collapsedFiles,\n        showFailedTests,\n        items,\n        results,\n        hierarchy + 1\n      );\n    });\n  }\n\n  return results;\n}\n\nexport const filterFailure = (results: TreeNode[]) => {\n  const finalResults = [];\n  for (let i = results.length - 1; i >= 0; i--) {\n    const item = results[i];\n    if (item.type === \"file\" && item.haveFailure === true) {\n      finalResults.push(item);\n    } else if (item.type === \"directory\") {\n      const hasFailedChildren = haveFailedChildren(item.path, finalResults);\n      if (hasFailedChildren) {\n        finalResults.push(item);\n      }\n    }\n  }\n  return finalResults.reverse();\n};\n\nfunction haveFailedChildren(path: string, results: TreeNode[]) {\n  return (\n    results.filter(\n      result =>\n        result.parent === path &&\n        (result.haveFailure === true || result.type === \"directory\")\n    ).length > 0\n  );\n}\n\nfunction sortAsc(a: Item, b: Item){\n  return a.name > b.name ? 1 : -1;\n}\n\nfunction getChildren(path: string, files: Item[]) {\n  const fileList = files.filter(file => file.parent === path);\n  return fileList.sort(sortAsc);\n}\n"
  },
  {
    "path": "ui/sidebar/tree.tsx",
    "content": "import React from \"react\";\nimport styled from \"styled-components\";\nimport { FixedSizeList as List } from \"react-window\";\nimport AutoResizer from \"react-virtualized-auto-sizer\";\nimport FileItem from \"./file-item\";\nimport { TreeNode } from \"./transformer\";\n\nconst FileTreeContainer = styled.div`\n  overflow: auto;\n  height: calc(100vh - 173px);\n  margin-left: -20px;\n`;\n\ninterface Props {\n  results: TreeNode[];\n  selectedFile: string;\n  onFileSelection: (path: string) => void;\n  onToggle: (path: string, isCollapsed: boolean) => void;\n}\n\nexport default function Tree({\n  results,\n  selectedFile,\n  onFileSelection,\n  onToggle\n}: Props) {\n  return (\n    <FileTreeContainer>\n      <AutoResizer>\n        {({ height, width }: any) => {\n          return (\n            <List\n              height={height - 90}\n              itemCount={results.length}\n              itemSize={20}\n              width={width}\n            >\n              {({ index, style }: any) => (\n                <FileItem\n                  style={style}\n                  key={results[index].path}\n                  item={results[index]}\n                  selectedFile={selectedFile}\n                  setSelectedFile={onFileSelection}\n                  onToggle={onToggle}\n                />\n              )}\n            </List>\n          );\n        }}\n      </AutoResizer>\n    </FileTreeContainer>\n  );\n}\n"
  },
  {
    "path": "ui/split-panel-style.ts",
    "content": "const splitPanelCSS = `\n.Resizer {\n    background: #404148;\n    opacity: .8;\n    z-index: 1;\n    box-sizing: border-box;\n    background-clip: padding-box;\n}\n\n .Resizer:hover {\n    transition: all 2s ease;\n}\n\n .Resizer.horizontal {\n    height: 11px;\n    margin: -5px 0;\n    border-top: 5px solid rgba(255, 255, 255, 0);\n    border-bottom: 5px solid rgba(255, 255, 255, 0);\n    cursor: row-resize;\n    width: 100%;\n}\n\n.Resizer.horizontal:hover {\n    border-top: 5px solid rgba(0, 0, 0, 0.5);\n    border-bottom: 5px solid rgba(0, 0, 0, 0.5);\n}\n\n.Resizer.vertical {\n    width: 11px;\n    margin: 0 -5px;\n    border-left: 3px solid rgba(255, 255, 255, 0.0);\n    border-right: 3px solid rgba(255, 255, 255, 0.0);\n    cursor: col-resize;\n}\n\n.Resizer.vertical:hover {\n    border-left: 3px solid rgba(0, 0, 0, 0.5);\n    border-right: 3px solid rgba(0, 0, 0, 0.5);\n}\n.Resizer.disabled {\n  cursor: not-allowed;\n}\n.Resizer.disabled:hover {\n  border-color: transparent;\n}\n`;\n\nexport default splitPanelCSS;\n"
  },
  {
    "path": "ui/stop-runner.gql",
    "content": "mutation {\n  stop\n}\n"
  },
  {
    "path": "ui/summary-query.gql",
    "content": "query {\n  summary {\n    numPassedTests\n    numFailedTests\n    numPassedTestSuites\n    numFailedTestSuites\n    failedTests\n    executingTests\n    passingTests\n    coverage {\n      statement\n      function\n      branch\n      line\n    }\n    haveCoverageReport\n  }\n}\n"
  },
  {
    "path": "ui/summary-subscription.gql",
    "content": "subscription {\n  changeToSummary {\n    numPassedTests\n    numFailedTests\n    numPassedTestSuites\n    numFailedTestSuites\n    failedTests\n    executingTests\n    passingTests\n    coverage {\n      statement\n      function\n      branch\n      line\n    }\n    haveCoverageReport\n  }\n}\n"
  },
  {
    "path": "ui/test-file/console-panel/index.tsx",
    "content": "import React from \"react\";\nimport styled from \"styled-components\";\nimport { ObjectInspector, chromeDark } from \"react-inspector\";\nimport { ConsoleLog } from \"../../../server/api/workspace/test-result/console-log\";\nimport { AlertCircle, XCircle, MessageSquare } from \"react-feather\";\n\nconst Container = styled.div`\n  display: flex;\n  flex-direction: column;\n  padding: 10px;\n  background-color: #404148;\n  border-radius: 5px;\n  margin-bottom: 10px;\n`;\n\nconst Header = styled.div`\n  font-size: 11px;\n  color: white;\n  margin-bottom: 5px;\n`;\n\nconst Content = styled.pre`\n  display: flex;\n  margin-bottom: 3px;\n  font-size: 12px;\n  border-radius: 3px;\n  padding: 4px;\n  font-family: monospace;\n`;\n\nconst Logs = styled.div`\n  display: flex;\n  flex-direction: column;\n  max-height: 300px;\n  overflow: auto;\n`;\n\nconst IconWrapper = styled.div`\n  margin-right: 5px;\n  margin-top: 1px;\n`;\n\nconst cleanAnsiCodes = (str: string) => str.replace(/\\x1B\\[(\\d+)m/g, \"\");\n\nfunction getIcon(type: String) {\n  let icon = null;\n  switch (type) {\n    case \"warn\":\n      icon = <AlertCircle size={11} color=\"#FDC055\" />;\n      break;\n    case \"error\":\n      icon = <XCircle size={11} color=\"#ff4f56\" />;\n      break;\n    case \"log\":\n      icon = <MessageSquare size={11} color=\"#19E28D\" />;\n      break;\n  }\n\n  return <IconWrapper>{icon}</IconWrapper>;\n}\n\ninterface Props {\n  consoleLogs: ConsoleLog[];\n}\n\n\nexport default function ConsolePanel({ consoleLogs }: Props) {\n  return (\n    <Container>\n      <Header>Console logs from the file</Header>\n      <Logs>\n        {consoleLogs.map((log, index) => {\n          let result = log.message;\n          try {\n            result = eval(\"(\" + log.message + \")\");\n          } catch (e) {\n            console.log(e);\n          }\n\n          if (typeof result === \"string\") {\n            return (\n              <Content key={index}>\n                {getIcon(log.type)}\n                {cleanAnsiCodes(result)}\n              </Content>\n            );\n          }\n\n          return (\n            <Content>\n              {getIcon(log.type)}\n              <ObjectInspector\n                data={result}\n                theme={{\n                  ...chromeDark,\n                  ...{\n                    TREENODE_FONT_SIZE: \"12px\",\n                    BASE_BACKGROUND_COLOR: \"#404148\",\n                    ARROW_FONT_SIZE: 10\n                  }\n                }}\n              />\n            </Content>\n          );\n        })}\n      </Logs>\n    </Container>\n  );\n}\n"
  },
  {
    "path": "ui/test-file/error-panel/index.tsx",
    "content": "import React from \"react\";\nimport styled from \"styled-components\";\nimport * as Convert from \"ansi-to-html\";\n\nconst convert = new Convert({\n  colors: {\n    1: \"#FF4F56\",\n    2: \"#19E28D\"\n  }\n});\n\nconst Container = styled.div`\n  padding: 10px;\n  background-color: #404148;\n  border-radius: 5px;\n  margin-bottom: 10px;\n`;\n\ninterface Props {\n  failureMessage: string;\n}\n\nfunction escapeHtml(unsafe: string) {\n  return unsafe\n    .replace(/&/g, \"&amp;\")\n    .replace(/</g, \"&lt;\")\n    .replace(/>/g, \"&gt;\")\n    .replace(/\"/g, \"&quot;\")\n    .replace(/'/g, \"&#039;\");\n}\n\nexport default function ErrorPanel({ failureMessage }: Props) {\n  if (!failureMessage || failureMessage.trim() === \"\") {\n    return null;\n  }\n\n  return (\n    <Container>\n      <pre\n        dangerouslySetInnerHTML={{\n          __html: convert.toHtml(escapeHtml(failureMessage))\n        }}\n      />\n    </Container>\n  );\n}\n"
  },
  {
    "path": "ui/test-file/file-items-subscription.gql",
    "content": "subscription($path: String!) {\n  fileChange(path: $path) {\n    items {\n      id\n      name\n      type\n      parent\n      only\n    }\n  }\n}\n"
  },
  {
    "path": "ui/test-file/index.tsx",
    "content": "import React, { memo } from \"react\";\nimport styled from \"styled-components\";\nimport { space, color } from \"styled-system\";\nimport { useMutation } from \"react-apollo-hooks\";\nimport FILEITEMS_SUB from \"./file-items-subscription.gql\";\nimport FILEITEMS from \"./query.gql\";\nimport RUNFILE from \"./run-file.gql\";\nimport UPDATE_SNAPSHOT from \"./update-snapshot.gql\";\nimport FILERESULTSUB from \"./subscription.gql\";\nimport RESULT from \"./result.gql\";\nimport Test from \"./test-item\";\nimport { transform } from \"./transformer\";\nimport useSubscription from \"./use-subscription\";\nimport FileSummary from \"./summary\";\nimport { TestFileResult } from \"../../server/api/workspace/test-result/file-result\";\nimport { TestFile as TestFileModel } from \"../../server/api/workspace/test-file\";\nimport ConsolePanel from \"./console-panel\";\nimport ErrorPanel from \"./error-panel\";\nimport useKeys, { hasKeys } from \"../hooks/use-keys\";\n\nconst Container = styled.div<any>`\n  ${space};\n  ${color};\n  height: 100vh;\n  padding-left: 20px;\n`;\n\nconst Content = styled.div`\n  overflow: auto;\n  height: calc(100vh - 118px);\n\n  ${({ dim }: any) => dim && `\n  opacity: .5;\n  `}\n`;\n\nconst TestItemsContainer = styled.div`\n  margin-left: -25px;\n`;\n\ninterface Props {\n  selectedFilePath: string;\n  isRunning: boolean;\n  projectRoot: string;\n  onStop: () => void;\n}\n\nfunction TestFile({ selectedFilePath, isRunning, projectRoot, onStop }: Props) {\n  const { data: fileItemResult }: { data: TestFileModel } = useSubscription(\n    FILEITEMS,\n    FILEITEMS_SUB,\n    {\n      path: selectedFilePath\n    },\n    result => result.file,\n    result => result.fileChange\n  );\n\n  const suiteCount = ((fileItemResult && fileItemResult.items) || []).filter(\n    fileItem => fileItem.type === \"describe\"\n  ).length;\n\n  const testCount = ((fileItemResult && fileItemResult.items) || []).filter(\n    fileItem => fileItem.type === \"it\"\n  ).length;\n\n  const todoCount = ((fileItemResult && fileItemResult.items) || []).filter(\n    fileItem => fileItem.type === \"todo\"\n  ).length;\n\n  const runFile = useMutation(RUNFILE, {\n    variables: {\n      path: selectedFilePath\n    }\n  });\n\n  const updateSnapshot = useMutation(UPDATE_SNAPSHOT, {\n    variables: {\n      path: selectedFilePath\n    }\n  });\n\n  const {\n    data: result,\n    loading\n  }: { data: TestFileResult; loading: boolean } = useSubscription(\n    RESULT,\n    FILERESULTSUB,\n    {\n      path: selectedFilePath\n    },\n    result => result.result,\n    result => result.changeToResult\n  );\n\n  const isUpdating = isRunning && (result ===  null ||(result.numPassingTests === 0 && result.numFailingTests === 0));\n\n  const roots = (fileItemResult.items || []).filter(\n    item => item.parent === null\n  );\n  const keys = useKeys();\n  if (hasKeys([\"Alt\", \"Enter\"], keys)) {\n    runFile();\n  }\n  return (\n    <Container p={5} bg=\"dark\" color=\"text\">\n      <FileSummary\n        projectRoot={projectRoot}\n        suiteCount={suiteCount}\n        testCount={testCount}\n        todoCount={todoCount}\n        passingTests={result && result.numPassingTests}\n        failingTests={result && result.numFailingTests}\n        path={selectedFilePath}\n        isRunning={isRunning}\n        isUpdating={isUpdating}\n        isLoadingResult={loading}\n        onRun={() => {\n          runFile();\n        }}\n        onStop={onStop}\n        onSnapshotUpdate={() => {\n          updateSnapshot();\n        }}\n      />\n      <Content dim={isUpdating}>\n        {result && result.testResults && result.testResults.length === 0 && (\n          <ErrorPanel failureMessage={result && result.failureMessage} />\n        )}\n        {result && result.consoleLogs && result.consoleLogs.length > 0 && (\n          <ConsolePanel consoleLogs={result.consoleLogs || []} />\n        )}\n        {fileItemResult && (\n          <TestItemsContainer>\n            {roots.map(item => {\n              const tree = transform(\n                item as any,\n                fileItemResult.items as any,\n                0\n              ) as any;\n              return <Test key={item.id} item={tree} result={result} />;\n            })}\n          </TestItemsContainer>\n        )}\n      </Content>\n    </Container>\n  );\n}\n\nexport default memo(TestFile, (pre: Props, next: Props) => {\n  return (\n    pre.isRunning === next.isRunning &&\n    pre.selectedFilePath === next.selectedFilePath\n  );\n});\n"
  },
  {
    "path": "ui/test-file/open-failure.gql",
    "content": "mutation OpenFailure($failure: String!) {\n  openFailure(failure: $failure)\n}\n"
  },
  {
    "path": "ui/test-file/query.gql",
    "content": "query FileItems($path: String!) {\n  file(path: $path) {\n    items {\n      id\n      name\n      type\n      parent\n      only\n    }\n  }\n}\n"
  },
  {
    "path": "ui/test-file/result.gql",
    "content": "query Results($path: String!) {\n  result(path: $path) {\n    path\n    numFailingTests\n    numPassingTests\n    failureMessage\n    testResults {\n      title\n      numPassingAsserts\n      status\n      failureMessages\n      ancestorTitles\n      duration\n    }\n    consoleLogs {\n      message\n      type\n      origin\n    }\n  }\n}\n"
  },
  {
    "path": "ui/test-file/run-file.gql",
    "content": "mutation RunFile($path: String!) {\n  runFile(path: $path)\n}\n"
  },
  {
    "path": "ui/test-file/subscription.gql",
    "content": "subscription Results($path: String!) {\n  changeToResult(path: $path) {\n    path\n    numFailingTests\n    numPassingTests\n    failureMessage\n    testResults {\n      title\n      numPassingAsserts\n      status\n      failureMessages\n      ancestorTitles\n      duration\n    }\n    consoleLogs {\n      message\n      type\n      origin\n    }\n  }\n}\n"
  },
  {
    "path": "ui/test-file/summary/index.tsx",
    "content": "import React from \"react\";\nimport styled from \"styled-components\";\nimport { space, fontSize, color } from \"styled-system\";\nimport { useSpring, animated } from \"react-spring\";\nimport {\n  Folder,\n  Code,\n  Play,\n  StopCircle,\n  Camera,\n  CheckCircle,\n  Frown,\n  ZapOff,\n  Circle,\n  Eye\n} from \"react-feather\";\nimport Button from \"../../components/button\";\nimport OPEN_IN_EDITOR from \"./open-in-editor.gql\";\nimport OPEN_SNAP_IN_EDITOR from \"./open-snap-in-editor.gql\";\nimport { Tooltip } from \"react-tippy\";\nimport { useMutation } from \"react-apollo-hooks\";\n\nconst Container = styled.div<any>`\n  position: relative;\n  ${space};\n  ${color};\n  border-radius: 3px;\n  display: flex;\n  justify-content: space-between;\n  margin-bottom: 10px;\n  overflow: hidden;\n  flex-wrap: wrap;\n`;\n\nconst ContainerBG = styled(animated.div)`\n  @keyframes MOVE-BG {\n    from {\n      transform: translateX(0);\n    }\n    to {\n      transform: translateX(27px);\n    }\n  }\n  border-radius: 3px;\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  right: 0;\n  left: -46px;\n  background: repeating-linear-gradient(\n    45deg,\n    #404148,\n    #404148 10px,\n    #242326 10px,\n    #242326 20px\n  );\n\n  animation-name: MOVE-BG;\n  animation-duration: 0.5s;\n  animation-timing-function: linear;\n  animation-iteration-count: infinite;\n`;\n\nconst RightContainer = styled.div`\n  z-index: 1;\n`;\n\nconst InfoContainer = styled.div`\n  display: flex;\n`;\n\nconst Info = styled.div`\n  display: flex;\n  align-items: center;\n  margin-right: 15px;\n  font-weight: 600;\n  ${color}\n`;\n\nconst InfoLabel = styled.div`\n  margin-left: 5px;\n`;\n\nconst FilePath = styled.div<any>`\n  ${fontSize};\n  ${space};\n  word-break: break-all;\n  font-weight: 600;\n  margin-right: 5px;\n`;\n\nconst ActionPanel = styled.div`\n  display: flex;\n  align-items: center;\n  z-index: 1;\n`;\n\nconst LoadingResult = styled.div`\n  color: #d9eef2;\n  margin-right: 10px;\n  font-size: 12px;\n`;\n\ninterface Props {\n  path: string;\n  projectRoot: string;\n  suiteCount: number;\n  testCount: number;\n  todoCount: number;\n  passingTests: number;\n  failingTests: number;\n  isRunning: boolean;\n  isUpdating: boolean;\n  isLoadingResult: boolean;\n  onRun: () => void;\n  onStop: () => void;\n  onSnapshotUpdate: () => void;\n  haveSnapshotFailures: boolean;\n}\n\nexport default function FileSummary({\n  path,\n  projectRoot,\n  suiteCount,\n  testCount,\n  todoCount,\n  passingTests,\n  failingTests,\n  isRunning,\n  isUpdating,\n  isLoadingResult,\n  onRun,\n  onStop,\n  onSnapshotUpdate,\n}: Props) {\n  const Icon = isRunning ? StopCircle : Play;\n\n  const openInEditor = useMutation(OPEN_IN_EDITOR, {\n    variables: {\n      path\n    }\n  });\n\n  const openSnapshotInEditor = useMutation(OPEN_SNAP_IN_EDITOR, {\n    variables: {\n      path\n    }\n  });\n\n  return (\n    <Container p={4} bg=\"slightDark\">\n      {( isUpdating || isLoadingResult) && <ContainerBG />}\n      <RightContainer>\n        <FilePath fontSize={15} mb={3}>\n          {path.replace(projectRoot, \"\")}\n        </FilePath>\n\n        <InfoContainer>\n          <Info color=\"primary\">\n            <Folder size={14} /> <InfoLabel>{suiteCount} Suites</InfoLabel>\n          </Info>\n          <Info color=\"primary\">\n            <Code size={14} /> <InfoLabel>{testCount} Tests</InfoLabel>\n          </Info>\n          <Info color=\"success\">\n            <CheckCircle size={14} />{\" \"}\n            <InfoLabel>{passingTests} Passing tests</InfoLabel>\n          </Info>\n          <Info color=\"danger\">\n            <ZapOff size={14} />{\" \"}\n            <InfoLabel>{failingTests} Failing tests</InfoLabel>\n          </Info>\n        </InfoContainer>\n      </RightContainer>\n      <ActionPanel>\n        {isLoadingResult && <LoadingResult>Loading test results</LoadingResult>}\n        <Tooltip title=\"Run file\" position=\"bottom\" size=\"small\">\n          <Button\n            icon={<Icon size={14} />}\n            minimal\n            onClick={() => {\n              if (isRunning) {\n                onStop();\n              } else {\n                onRun();\n              }\n            }}\n          >\n            {isRunning ? \"Stop\" : \"Run\"}\n          </Button>\n        </Tooltip>\n        <Tooltip title=\"Open in editor\" size=\"small\" position=\"bottom\">\n          <Button\n            icon={<Code size={14} />}\n            minimal\n            onClick={() => {\n              openInEditor();\n            }}\n          />\n        </Tooltip>\n          <Tooltip\n            title=\"Update all snapshots for this file\"\n            position=\"bottom\"\n            size=\"small\"\n          >\n            <Button\n              minimal\n              icon={<Camera size={14} />}\n              onClick={() => {\n                onSnapshotUpdate();\n              }}\n            >\n              Update Snapshot\n            </Button>\n          </Tooltip>\n        <Tooltip title=\"Open snapshot in editor\" size=\"small\" position=\"bottom\">\n          <Button\n            icon={<Eye size={14} />}\n            minimal\n            onClick={() => {\n              openSnapshotInEditor();\n            }}\n          />\n        </Tooltip>\n      </ActionPanel>\n    </Container>\n  );\n}\n"
  },
  {
    "path": "ui/test-file/summary/open-in-editor.gql",
    "content": "mutation OpenInEditor($path: String!) {\n  openInEditor(path: $path)\n}\n"
  },
  {
    "path": "ui/test-file/summary/open-snap-in-editor.gql",
    "content": "mutation OpenSnapInEditor($path: String!) {\n  openSnapInEditor(path: $path)\n}\n"
  },
  {
    "path": "ui/test-file/test-indicator.tsx",
    "content": "import React from \"react\";\nimport {\n  CheckCircle,\n  Circle,\n  Package,\n  XCircle,\n  Zap,\n  Edit2\n} from \"react-feather\";\n\ninterface Props {\n  status: string | null | undefined;\n  describe: boolean;\n  todo: boolean;\n}\n\nexport default function TestIndicator({ status, describe, todo }: Props) {\n  let Icon = describe ? Package : Zap;\n  let color = \"#AC61FF\";\n\n  if (todo) {\n    return <Edit2 size={14} color=\"#AC61FF\" />;\n  }\n\n  if (!describe) {\n    if (status === \"passed\") {\n      Icon = CheckCircle;\n    } else if (status === \"todo\") {\n      Icon = Circle;\n    } else if (status === \"failed\") {\n      Icon = XCircle;\n    }\n  }\n\n  if (status === \"passed\") {\n    color = \"#50E3C2\";\n  } else if (status === \"failed\") {\n    color = \"#FF4954\";\n  }\n\n  return <Icon size={14} color={color} />;\n}\n"
  },
  {
    "path": "ui/test-file/test-item.tsx",
    "content": "import React, { Fragment } from \"react\";\nimport styled from \"styled-components\";\nimport { TestFileItem } from \"./transformer\";\nimport { TestFileResult } from \"../../server/api/workspace/test-result/file-result\";\nimport TestIndicator from \"./test-indicator\";\nimport { color, space } from \"styled-system\";\nimport * as Convert from \"ansi-to-html\";\nimport OPEN_FAILURE from \"./open-failure.gql\";\nimport { useMutation } from \"react-apollo-hooks\";\n\nconst convert = new Convert({\n  colors: {\n    1: \"#FF4F56\",\n    2: \"#19E28D\"\n  }\n});\n\nfunction getResults(item: TestFileItem, testResult: TestFileResult) {\n  if (!testResult || !testResult.testResults) {\n    return null;\n  }\n\n  return testResult.testResults.find(result => result.title === item.name);\n}\n\nconst Container = styled.div`\n  ${color};\n  ${space};\n  padding-left: 25px;\n`;\n\nconst Label = styled.div`\n  display: flex;\n  align-items: center;\n  font-weight: 600;\n  font-size: 13px;\n\n  span {\n    margin-left: 5px;\n  }\n`;\n\nconst Content = styled.div<any>`\n  padding: 5px;\n  display: flex;\n  flex-direction: column;\n\n  background-color: #262529;\n  border-radius: 4px;\n  margin-bottom: 10px;\n  border: 1px solid ${props => (props.only ? \"#9d8301\" : \"#333437\")};\n`;\n\nconst FailureMessage = styled.div`\n  padding-left: 20px;\n  pre {\n    overflow: auto;\n  }\n`;\n\nconst Duration = styled.span`\n  font-weight: 400;\n  font-size: 12px;\n  color: #fcd101;\n`;\n\nfunction escapeHtml(unsafe: string) {\n  return unsafe\n    .replace(/&/g, \"&amp;\")\n    .replace(/</g, \"&lt;\")\n    .replace(/>/g, \"&gt;\")\n    .replace(/\"/g, \"&quot;\")\n    .replace(/'/g, \"&#039;\");\n}\n\ninterface Props {\n  item: TestFileItem;\n  result: TestFileResult | null;\n}\n\nexport default function Test({\n  item: { name, only, children },\n  item,\n  result\n}: Props) {\n  const testResult = getResults(item, result as any);\n  const isDurationAvailable = testResult && testResult.duration !== undefined;\n  const haveFailure = testResult && testResult.failureMessages.length > 0;\n  const allChildrenPassing = (children || []).every(child => {\n    if (child.type === \"it\") {\n      const childResult = getResults(child, result as any);\n      return childResult && childResult.status === \"passed\";\n    }\n\n    return true;\n  });\n\n          if (children && children.length > 0) {\n    }\n\n  const openFailure = useMutation(OPEN_FAILURE, {\n    variables: {\n      failure: testResult && testResult.failureMessages ? testResult.failureMessages[0] : ''\n    }\n  });\n\n  return (\n    <Container>\n      <Content only={only} onClick={ () => openFailure()}>\n        <Label>\n          <TestIndicator\n            status={\n              item.type === \"describe\" && allChildrenPassing\n                ? \"passed\"\n                : testResult && testResult.status\n            }\n            describe={item.type === \"describe\"}\n            todo={item.type === \"todo\"}\n          />\n          <span>{name}</span>\n          {isDurationAvailable && (\n            <Duration>{testResult && testResult.duration} ms</Duration>\n          )}\n        </Label>\n        {testResult && haveFailure && (\n          <FailureMessage>\n            <pre\n              dangerouslySetInnerHTML={{\n                __html: convert.toHtml(\n                  escapeHtml(testResult.failureMessages.join(\",\"))\n                )\n              }}\n            />\n          </FailureMessage>\n        )}\n      </Content>\n      {children &&\n        children.map(child => (\n          <Test key={child.id} item={child} result={result} />\n        ))}\n    </Container>\n  );\n}\n"
  },
  {
    "path": "ui/test-file/transformer.ts",
    "content": "import { TestItem } from \"../../server/api/workspace/test-item\";\n\nexport interface TestFileItem extends TestItem {\n  children?: TestFileItem[];\n  index: number;\n}\n\nexport function transform(\n  item: TestFileItem,\n  items: TestItem[],\n  index: number = 0,\n  tree?: TestFileItem\n) {\n  if (!item) {\n    return {};\n  }\n\n  const nextChildren = getChildren(item.id, items);\n  if (!tree) {\n    tree = {\n      id: item.id,\n      type: item.type,\n      name: item.name,\n      parent: item.parent,\n      only: item.only,\n      children: nextChildren,\n      index: index + 1\n    } as any;\n  }\n  item.children = nextChildren as any;\n  item.children &&\n    item.children.forEach(item => {\n      transform(item, items, index + 1, tree);\n    });\n  return tree;\n}\n\nfunction getChildren(id: string, items: TestItem[]) {\n  return items.filter(item => item.parent === id);\n}\n"
  },
  {
    "path": "ui/test-file/update-snapshot.gql",
    "content": "mutation UpdateSnapshot($path: String!) {\n  updateSnapshot(path: $path)\n}\n"
  },
  {
    "path": "ui/test-file/use-subscription.tsx",
    "content": "import React, { useState, useEffect } from \"react\";\nimport { DocumentNode } from \"graphql\";\nimport { useApolloClient } from \"react-apollo-hooks\";\n\nexport default function useSubscription(\n  query: DocumentNode,\n  subscriptionQuery: DocumentNode,\n  variables: any,\n  queryResultMapper: (result: any) => any,\n  subResultMapper: (result: any) => any,\n  name: string = \"\"\n) {\n  const client = useApolloClient();\n  const [result, setResult] = useState<any>({\n    data: {},\n    loading: false,\n    error: null\n  });\n\n  let subscription: any;\n\n  useEffect(\n    () => {\n      if (client) {\n        setResult({\n          ...result,\n          loading: true\n        });\n        client\n          .query({\n            query,\n            variables,\n            fetchPolicy: \"network-only\"\n          })\n          .then(({ data, errors, loading }) => {\n            console.log(name, data);\n            setResult({\n              data: queryResultMapper(data),\n              error: errors,\n              loading\n            });\n          });\n      }\n    },\n    variables.path ? [variables.path] : []\n  );\n\n  useEffect(\n    () => {\n      if (client) {\n        console.log(\"Subbed to\", name);\n        subscription = client\n          .subscribe({\n            query: subscriptionQuery,\n            variables,\n            fetchPolicy: \"network-only\"\n          })\n          .subscribe({\n            error: (error: any) => {\n              setResult({ loading: false, data: result.data, error });\n            },\n            next: (nextResult: any) => {\n              console.log(\"Sub Result\", name, nextResult.data);\n              const newResult = {\n                data: subResultMapper(nextResult.data),\n                error: undefined,\n                loading: false\n              };\n              setResult(newResult);\n            }\n          });\n      }\n    },\n    variables.path ? [variables.path] : []\n  );\n\n  useEffect(\n    () => {\n      return () => {\n        subscription && subscription.unsubscribe();\n      };\n    },\n    variables.path ? [variables.path] : []\n  );\n\n  return result;\n}\n"
  },
  {
    "path": "ui/theme.ts",
    "content": "export default {\n  colors: {\n    veryDark: \"#262529\",\n    dark: \"#242326\",\n    slightDark: \"#404148\",\n    text: \"#F5F5F5\",\n    primary: \"#FDC055\",\n    danger: \"#ff4f56\",\n    success: \"#19E28D\"\n  },\n  space: [0, 2, 4, 8, 16, 32, 64, 128, 256, 512]\n};\n"
  },
  {
    "path": "ui/typings.d.ts",
    "content": "declare module \"apollo-link-ws\";\ndeclare module \"apollo-utilities\";\ndeclare module \"apollo-link\";\ndeclare module \"apollo-client-preset\";\ndeclare module \"*.gql\" {\n  const content: any;\n  export default content;\n}\ndeclare module \"resolve-pkg\";\ndeclare module \"react-virtualized-auto-sizer\";\ndeclare module \"react-window\";\ndeclare module \"minimist\";\ndeclare module \"react-tippy\";\ndeclare module \"ansi-to-html\";\ndeclare module \"*.png\";\ndeclare module \"react-inspector\";\n"
  }
]