diff --git a/.babelrc b/.babelrc index c11cefaec..a5e10d850 100644 --- a/.babelrc +++ b/.babelrc @@ -10,9 +10,7 @@ ], "@babel/preset-typescript" ], - "ignore": [ - "**/*.d.ts" - ], + "ignore": ["**/*.d.ts"], "sourceMaps": "inline", "plugins": [ "@babel/plugin-proposal-class-properties", diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..c2e961ab1 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,62 @@ +module.exports = { + extends: [ + 'eslint:recommended', + 'plugin:typescript/recommended', + 'plugin:prettier/recommended', + ], + plugins: ['import', 'notice'], + env: { + browser: true, + es6: true, + node: true, + }, + rules: { + // eslint rules + 'no-dupe-class-members': 'off', + 'no-undef': 'off', // ts compiler catches this + 'prefer-const': 'error', + + // plugin: import + 'import/first': 'error', + 'import/order': ['error', { 'newlines-between': 'always' }], + + // plugin: notice + 'notice/notice': [ + 'error', + { + mustMatch: 'Copyright \\(c\\) Microsoft', + templateFile: require.resolve('./copyright.js'), + messages: { + whenFailedToMatch: 'Missing copyright header.', + }, + }, + ], + + // plugin: typescript + 'typescript/explicit-function-return-type': 'off', + 'typescript/explicit-member-accessibility': 'off', + 'typescript/indent': 'off', + 'typescript/no-empty-interface': 'warn', + 'typescript/no-object-literal-type-assertion': 'off', + 'typescript/no-parameter-properties': 'off', + 'typescript/no-use-before-define': [ + 'error', + { functions: false, classes: false }, + ], + }, + overrides: [ + { + files: ['**/*.+(js|jsx)'], + parser: 'babel-eslint', + }, + { + files: ['**/*.+(test|spec).+(js|jsx|ts|tsx)'], + env: { + jest: true, + }, + rules: { + 'typescript/class-name-casing': 'off', + }, + }, + ], +}; diff --git a/.eslintrc.react.js b/.eslintrc.react.js new file mode 100644 index 000000000..f3d739661 --- /dev/null +++ b/.eslintrc.react.js @@ -0,0 +1,26 @@ +module.exports = { + extends: ['./.eslintrc.js', 'plugin:react/recommended'], + settings: { + react: { + version: 'detect', + }, + }, + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + rules: { + 'react/no-deprecated': 'warn', + }, + overrides: [ + { + files: ['**/*.+(test|spec).+(js|jsx|ts|tsx)'], + rules: { + 'react/display-name': 'off', + }, + }, + ], +}; diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..8b7204386 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "parser": "typescript", + "trailingComma": "es5", + "singleQuote": true +} diff --git a/copyright.js b/copyright.js new file mode 100644 index 000000000..ff4bfaa5b --- /dev/null +++ b/copyright.js @@ -0,0 +1,32 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// diff --git a/package-lock.json b/package-lock.json index cfe0408bd..76739f57d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.1.2.tgz", "integrity": "sha512-70A9HWLS/1RHk3Ck8tNHKxOoKQuSKocYgwDN85Pyl/RBduss6AKxUR7RIZ/lzduQMSYfWEM4DDBu6A+XGbkFig==", - "dev": true, "requires": { "@babel/types": "^7.1.2", "jsesc": "^2.5.1", @@ -80,8 +79,7 @@ "jsesc": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", - "dev": true + "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=" } } }, @@ -150,7 +148,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.0.0", "@babel/template": "^7.1.0", @@ -161,7 +158,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, "requires": { "@babel/types": "^7.0.0" } @@ -268,7 +264,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", - "dev": true, "requires": { "@babel/types": "^7.0.0" } @@ -309,8 +304,7 @@ "@babel/parser": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.1.2.tgz", - "integrity": "sha512-x5HFsW+E/nQalGMw7hu+fvPqnBeBaIr0lWJ2SG0PPL2j+Pm9lYvCrsZJGIgauPIENx0v10INIyFjmSNUD/gSqQ==", - "dev": true + "integrity": "sha512-x5HFsW+E/nQalGMw7hu+fvPqnBeBaIr0lWJ2SG0PPL2j+Pm9lYvCrsZJGIgauPIENx0v10INIyFjmSNUD/gSqQ==" }, "@babel/plugin-proposal-async-generator-functions": { "version": "7.1.0", @@ -938,7 +932,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", - "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "@babel/parser": "^7.1.2", @@ -949,7 +942,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.0.tgz", "integrity": "sha512-bwgln0FsMoxm3pLOgrrnGaXk18sSM9JNf1/nHC/FksmNGFbYnPWY4GYCfLxyP1KRmfsxqkRpfoa6xr6VuuSxdw==", - "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "@babel/generator": "^7.0.0", @@ -965,8 +957,7 @@ "globals": { "version": "11.8.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.8.0.tgz", - "integrity": "sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA==", - "dev": true + "integrity": "sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA==" } } }, @@ -980,6 +971,62 @@ "to-fast-properties": "^2.0.0" } }, + "@iamstarkov/listr-update-renderer": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@iamstarkov/listr-update-renderer/-/listr-update-renderer-0.4.1.tgz", + "integrity": "sha512-IJyxQWsYDEkf8C8QthBn5N8tIUR9V9je6j3sMIpAkonaadjbvxmRC6RAhpa3RKxndhNnU2M6iNbtJwd7usQYIA==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "cli-truncate": "^0.2.1", + "elegant-spinner": "^1.0.1", + "figures": "^1.7.0", + "indent-string": "^3.0.0", + "log-symbols": "^1.0.2", + "log-update": "^2.3.0", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, + "log-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", + "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "dev": true, + "requires": { + "chalk": "^1.0.0" + } + } + } + }, "@lerna/add": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.4.1.tgz", @@ -1997,6 +2044,15 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.2.tgz", "integrity": "sha512-yprFYuno9FtNsSHVlSWd+nRlmGoAbqbeCwOryP6sC/zoCjhpArcRMYp19EvpSUSizJAlsXEwJv+wcWS9XaXdMw==" }, + "@samverschueren/stream-to-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", + "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", + "dev": true, + "requires": { + "any-observable": "^0.3.0" + } + }, "@sindresorhus/is": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", @@ -2537,6 +2593,11 @@ } } }, + "acorn-jsx": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", + "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==" + }, "acorn-walk": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", @@ -2687,6 +2748,12 @@ "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=" }, + "any-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", + "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", + "dev": true + }, "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -2851,6 +2918,15 @@ "resolved": "https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/array-ify/-/array-ify-1.0.0.tgz", "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=" }, + "array-includes": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", + "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" + } + }, "array-map": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", @@ -3113,6 +3189,30 @@ "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", "dev": true }, + "babel-eslint": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.1.tgz", + "integrity": "sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "eslint-scope": "3.7.1", + "eslint-visitor-keys": "^1.0.0" + }, + "dependencies": { + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + } + } + }, "babel-generator": { "version": "6.26.1", "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", @@ -4073,6 +4173,11 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, + "boolify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/boolify/-/boolify-1.0.1.tgz", + "integrity": "sha1-tcCeF8rNET0Rt7s+04TMASmU2Gs=" + }, "boom": { "version": "2.10.1", "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", @@ -4684,9 +4789,33 @@ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "requires": { + "callsites": "^2.0.0" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "requires": { + "callsites": "^0.2.0" + }, + "dependencies": { + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=" + } + } + }, "callsites": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "resolved": "http://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" }, "camel-case": { @@ -4843,7 +4972,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" } } @@ -4921,6 +5050,11 @@ "safe-buffer": "^5.0.1" } }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==" + }, "circular-json-es6": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/circular-json-es6/-/circular-json-es6-2.0.2.tgz", @@ -5098,6 +5232,44 @@ "string-width": "^2.1.1" } }, + "cli-truncate": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", + "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", + "dev": true, + "requires": { + "slice-ansi": "0.0.4", + "string-width": "^1.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, "cli-usage": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/cli-usage/-/cli-usage-0.1.8.tgz", @@ -5317,6 +5489,11 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.18.0.tgz", "integrity": "sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ==" }, + "common-tags": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", + "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==" + }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -5480,6 +5657,11 @@ "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=" + }, "content-disposition": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", @@ -6205,7 +6387,7 @@ "dependencies": { "regexpu-core": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", "requires": { "regenerate": "^1.2.1", @@ -6729,6 +6911,11 @@ "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=" }, + "dlv": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.2.tgz", + "integrity": "sha512-xxD4VSH67GbRvSGUrckvha94RD7hjgOH7rqGxiytLpkaeMvixOHFZTGFK6EkIm3T761OVHT8ABHmGkq9gXgu6Q==" + }, "dmg-builder": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-6.0.0.tgz", @@ -6766,6 +6953,14 @@ "buffer-indexof": "^1.0.0" } }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "requires": { + "esutils": "^2.0.2" + } + }, "dom-serializer": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", @@ -7261,7 +7456,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" } } @@ -7314,6 +7509,12 @@ } } }, + "elegant-spinner": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", + "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=", + "dev": true + }, "elliptic": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", @@ -7545,65 +7746,419 @@ } } }, - "eslint-scope": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "eslint": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.12.0.tgz", + "integrity": "sha512-LntwyPxtOHrsJdcSwyQKVtHofPHdv+4+mFwEe91r2V13vqpM8yLr7b1sW+Oo/yheOPkWYsYlYJCkzlFAt8KV7g==", "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "@babel/code-frame": "^7.0.0", + "ajv": "^6.5.3", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^2.1.0", + "eslint-scope": "^4.0.0", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.0", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.1.0", + "js-yaml": "^3.12.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.5", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.0.2", + "text-table": "^0.2.0" + }, + "dependencies": { + "ajv": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz", + "integrity": "sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "globals": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.10.0.tgz", + "integrity": "sha512-0GZF1RiPKU97IHUO5TORo9w1PwrH/NBPl+fS7oMLdaTRiYmYbwK4NWoZWrAdd0/abG9R2BU+OiwyQpTpE6pdfQ==" + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "eslint-config-prettier": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-3.5.0.tgz", + "integrity": "sha512-LcZEoAY5lL3/H2NTFSeUl/z8X8oMea1IxLEIb5uDbRxPTdQeeT7oGpRWT6UwHXGcoRbYH0TZmfRsh8iXbpyW7A==", "requires": { - "estraverse": "^4.1.0" + "get-stdin": "^6.0.0" + }, + "dependencies": { + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==" + } } }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "event-as-promise": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/event-as-promise/-/event-as-promise-1.0.5.tgz", - "integrity": "sha512-z/WIlyou7oTvXBjm5YYjfklr2d8gUWtx8b5GAcrIs1n1D35f7NIK0CrcYSXbY3VYikG9bUan+wScPyGXL/NH4A==" - }, - "eventemitter3": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", - "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==" - }, - "events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", - "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==" - }, - "eventsource": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-0.1.6.tgz", - "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=", + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", "requires": { - "original": ">=0.0.5" - } + "debug": "^2.6.9", + "resolve": "^1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "eslint-module-utils": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz", + "integrity": "sha1-snA2LNiLGkitMIl2zn+lTphBF0Y=", + "requires": { + "debug": "^2.6.8", + "pkg-dir": "^1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "requires": { + "find-up": "^1.0.0" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz", + "integrity": "sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g==", + "requires": { + "contains-path": "^0.1.0", + "debug": "^2.6.8", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.1", + "eslint-module-utils": "^2.2.0", + "has": "^1.0.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.3", + "read-pkg-up": "^2.0.0", + "resolve": "^1.6.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "requires": { + "pify": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + } + } + }, + "eslint-plugin-notice": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/eslint-plugin-notice/-/eslint-plugin-notice-0.7.7.tgz", + "integrity": "sha512-kHVNObyW2QPEqunQyZiV9loKRV04IuDv096rtYJSziyDASOSPcjl2eLibQdw8BFNbb/DNZj3obN8iMAeydL2hg==", + "requires": { + "find-root": "^1.1.0", + "lodash": ">=2.4.0", + "metric-lcs": "^0.1.2" + } + }, + "eslint-plugin-prettier": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.0.1.tgz", + "integrity": "sha512-/PMttrarPAY78PLvV3xfWibMOdMDl57hmlQ2XqFeA37wd+CJ7WSxV7txqjVPHi/AAFKd2lX0ZqfsOc/i5yFCSQ==", + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-plugin-react": { + "version": "7.12.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.12.4.tgz", + "integrity": "sha512-1puHJkXJY+oS1t467MjbqjvX53uQ05HXwjqDgdbGBqf5j9eeydI54G3KwiJmWciQ0HTBacIKw2jgwSBSH3yfgQ==", + "requires": { + "array-includes": "^3.0.3", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.0.1", + "object.fromentries": "^2.0.0", + "prop-types": "^15.6.2", + "resolve": "^1.9.0" + }, + "dependencies": { + "resolve": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz", + "integrity": "sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ==", + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "eslint-plugin-typescript": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-typescript/-/eslint-plugin-typescript-1.0.0-rc.3.tgz", + "integrity": "sha512-urXH7sKqRkFLcDj6dP3tvQALfWIRGn8J8Kg9dYSoavXvy62FxfUv6JgjDAHQZudi9fSRQpEZA/LqdJXDOwKHPA==", + "requires": { + "requireindex": "^1.2.0", + "typescript-eslint-parser": "21.0.2" + } + }, + "eslint-scope": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==" + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==" + }, + "espree": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.0.tgz", + "integrity": "sha512-1MpUfwsdS9MMoN7ZXqAr9e9UKdVHDcvrJpyx7mm1WuQlx/ygErEQBzgi5Nh5qBHIoYweprhtMkTCb9GhcAIcsA==", + "requires": { + "acorn": "^6.0.2", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + }, + "dependencies": { + "acorn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.5.tgz", + "integrity": "sha512-i33Zgp3XWtmZBMNvCr4azvOFeWVw1Rk6p3hfi3LUDvIFraOMywb1kAtrbi+med14m4Xfpqm3zRZMT+c0FNE7kg==" + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "event-as-promise": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/event-as-promise/-/event-as-promise-1.0.5.tgz", + "integrity": "sha512-z/WIlyou7oTvXBjm5YYjfklr2d8gUWtx8b5GAcrIs1n1D35f7NIK0CrcYSXbY3VYikG9bUan+wScPyGXL/NH4A==" + }, + "eventemitter3": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", + "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==" + }, + "events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==" + }, + "eventsource": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-0.1.6.tgz", + "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=", + "requires": { + "original": ">=0.0.5" + } }, "evp_bytestokey": { "version": "1.0.3", @@ -7860,7 +8415,7 @@ "dependencies": { "array-flatten": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "debug": { @@ -7978,6 +8533,11 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==" + }, "fast-glob": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.3.tgz", @@ -8064,6 +8624,15 @@ "escape-string-regexp": "^1.0.5" } }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, "file-loader": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", @@ -8155,6 +8724,12 @@ "pkg-dir": "^2.0.0" } }, + "find-parent-dir": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz", + "integrity": "sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ=", + "dev": true + }, "find-replace": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-2.0.1.tgz", @@ -8164,6 +8739,11 @@ "test-value": "^3.0.0" } }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -8172,6 +8752,17 @@ "locate-path": "^2.0.0" } }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "requires": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + } + }, "flatten": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", @@ -8882,6 +9473,22 @@ "is-callable": "^1.1.3" } }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" + }, + "g-status": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/g-status/-/g-status-2.0.2.tgz", + "integrity": "sha512-kQoE9qH+T1AHKgSSD0Hkv98bobE90ILQcXAF4wvGgsr7uFqNvwmh8j+Lq3l0RVt3E3HjSbv2B9biEGcEtpHLCA==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "matcher": "^1.0.0", + "simple-git": "^1.85.0" + } + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -8951,6 +9558,12 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" }, + "get-own-enumerable-property-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz", + "integrity": "sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg==", + "dev": true + }, "get-pkg-repo": { "version": "1.4.0", "resolved": "https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz", @@ -9869,32 +10482,207 @@ "ms": "^2.0.0" } }, - "hyphenate-style-name": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz", - "integrity": "sha1-MRYKNpMK2vH8BMYHT360FGXU7Es=" - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "icss-replace-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", - "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=" - }, - "icss-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz", - "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", + "husky": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/husky/-/husky-1.3.1.tgz", + "integrity": "sha512-86U6sVVVf4b5NYSZ0yvv88dRgBSSXXmHaiq5pP4KDj5JVzdwKgBjEtUPOm8hcoytezFwbU+7gotXNhpHdystlg==", + "dev": true, "requires": { - "postcss": "^6.0.1" - }, - "dependencies": { + "cosmiconfig": "^5.0.7", + "execa": "^1.0.0", + "find-up": "^3.0.0", + "get-stdin": "^6.0.0", + "is-ci": "^2.0.0", + "pkg-dir": "^3.0.0", + "please-upgrade-node": "^3.1.1", + "read-pkg": "^4.0.1", + "run-node": "^1.0.0", + "slash": "^2.0.0" + }, + "dependencies": { + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cosmiconfig": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.7.tgz", + "integrity": "sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "read-pkg": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", + "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", + "dev": true, + "requires": { + "normalize-package-data": "^2.3.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0" + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + } + } + }, + "hyphenate-style-name": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz", + "integrity": "sha1-MRYKNpMK2vH8BMYHT360FGXU7Es=" + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=" + }, + "icss-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz", + "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", + "requires": { + "postcss": "^6.0.1" + }, + "dependencies": { "postcss": { "version": "6.0.23", "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", @@ -9943,6 +10731,22 @@ "minimatch": "^3.0.4" } }, + "import-fresh": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", + "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + } + } + }, "import-lazy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", @@ -10333,6 +11137,23 @@ "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=" }, + "is-observable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz", + "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", + "dev": true, + "requires": { + "symbol-observable": "^1.1.0" + }, + "dependencies": { + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true + } + } + }, "is-path-cwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", @@ -10405,6 +11226,11 @@ "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=" }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" + }, "is-retry-allowed": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", @@ -11164,7 +11990,7 @@ }, "jest-enzyme": { "version": "6.0.0", - "resolved": "http://registry.npmjs.org/jest-enzyme/-/jest-enzyme-6.0.0.tgz", + "resolved": "https://registry.npmjs.org/jest-enzyme/-/jest-enzyme-6.0.0.tgz", "integrity": "sha1-UY1OzF4yyMFWS0G3vIPqOGZ+hmE=", "requires": { "enzyme-matchers": "^6.0.0", @@ -11183,7 +12009,7 @@ }, "jest-get-type": { "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", + "resolved": "http://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==" }, "jest-haste-map": { @@ -11852,6 +12678,11 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -11933,6 +12764,14 @@ "verror": "1.10.0" } }, + "jsx-ast-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz", + "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=", + "requires": { + "array-includes": "^3.0.3" + } + }, "jwa": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", @@ -12112,60 +12951,237 @@ "uc.micro": "^1.0.1" } }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "lint-staged": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-8.1.0.tgz", + "integrity": "sha512-yfSkyJy7EuVsaoxtUSEhrD81spdJOe/gMTGea3XaV7HyoRhTb9Gdlp6/JppRZERvKSEYXP9bjcmq6CA5oL2lYQ==", + "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", + "@iamstarkov/listr-update-renderer": "0.4.1", + "chalk": "^2.3.1", + "commander": "^2.14.1", + "cosmiconfig": "5.0.6", + "debug": "^3.1.0", + "dedent": "^0.7.0", + "del": "^3.0.0", + "execa": "^1.0.0", + "find-parent-dir": "^0.3.0", + "g-status": "^2.0.2", + "is-glob": "^4.0.0", + "is-windows": "^1.0.2", + "jest-validate": "^23.5.0", + "listr": "^0.14.2", + "lodash": "^4.17.5", + "log-symbols": "^2.2.0", + "micromatch": "^3.1.8", + "npm-which": "^3.0.1", + "p-map": "^1.1.1", + "path-is-inside": "^1.0.2", "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "loader-runner": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.1.tgz", - "integrity": "sha512-By6ZFY7ETWOc9RFaAIb23IjJVcM4dvJC/N57nmdz9RSkMXvAXGI7SyVlAw3v8vjtDRlqThgVDVmTnr9fqMlxkw==" - }, - "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^2.0.0", - "json5": "^1.0.1" + "please-upgrade-node": "^3.0.2", + "staged-git-files": "1.1.2", + "string-argv": "^0.0.2", + "stringify-object": "^3.2.2" }, "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, "requires": { - "minimist": "^1.2.0" + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lock": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/lock/-/lock-0.1.4.tgz", - "integrity": "sha1-/sfervF+fDoKVeHaBCgD4l2RdF0=" + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "listr": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", + "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", + "dev": true, + "requires": { + "@samverschueren/stream-to-observable": "^0.3.0", + "is-observable": "^1.1.0", + "is-promise": "^2.1.0", + "is-stream": "^1.1.0", + "listr-silent-renderer": "^1.1.1", + "listr-update-renderer": "^0.5.0", + "listr-verbose-renderer": "^0.5.0", + "p-map": "^2.0.0", + "rxjs": "^6.3.3" + }, + "dependencies": { + "p-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.0.0.tgz", + "integrity": "sha512-GO107XdrSUmtHxVoi60qc9tUl/KkNKm+X2CF4P9amalpGxv5YqVPJNfSb0wcA+syCopkZvYYIzW8OVTQW59x/w==", + "dev": true + }, + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } + } + }, + "listr-silent-renderer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", + "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=", + "dev": true + }, + "listr-update-renderer": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz", + "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "cli-truncate": "^0.2.1", + "elegant-spinner": "^1.0.1", + "figures": "^1.7.0", + "indent-string": "^3.0.0", + "log-symbols": "^1.0.2", + "log-update": "^2.3.0", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, + "log-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", + "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "dev": true, + "requires": { + "chalk": "^1.0.0" + } + } + } + }, + "listr-verbose-renderer": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz", + "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "cli-cursor": "^2.1.0", + "date-fns": "^1.27.2", + "figures": "^2.0.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "loader-runner": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.1.tgz", + "integrity": "sha512-By6ZFY7ETWOc9RFaAIb23IjJVcM4dvJC/N57nmdz9RSkMXvAXGI7SyVlAw3v8vjtDRlqThgVDVmTnr9fqMlxkw==" + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lock": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/lock/-/lock-0.1.4.tgz", + "integrity": "sha1-/sfervF+fDoKVeHaBCgD4l2RdF0=" }, "lodash": { "version": "4.17.11", @@ -12431,6 +13447,11 @@ "lodash.keys": "^3.0.0" } }, + "lodash.unescape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", + "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=" + }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -12441,11 +13462,81 @@ "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==" }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "log-update": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", + "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "cli-cursor": "^2.0.0", + "wrap-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", + "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0" + } + } + } + }, "loglevel": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=" }, + "loglevel-colored-level-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz", + "integrity": "sha1-akAhj9x64V/HbD0PPmdsRlOIYD4=", + "requires": { + "chalk": "^1.1.3", + "loglevel": "^1.4.1" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + } + } + }, "long": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", @@ -12610,6 +13701,22 @@ } } }, + "make-plural": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz", + "integrity": "sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==", + "requires": { + "minimist": "^1.2.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "optional": true + } + } + }, "makeerror": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", @@ -12683,6 +13790,15 @@ "node-emoji": "^1.4.1" } }, + "matcher": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-1.1.1.tgz", + "integrity": "sha512-+BmqxWIubKTRKNWx/ahnCkk3mG8m7OturVlqq6HiojGJTd5hVYbgZm6WzcYPCoB+KBT4Vd6R7WSRG2OADNaCjg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.4" + } + }, "math-expression-evaluator": { "version": "1.2.17", "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", @@ -12799,11 +13915,48 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.2.tgz", "integrity": "sha512-bgM8twH86rWni21thii6WCMQMRMmwqqdW3sGWi9IipnVAszdLXRjwDwAnyrVXo6DuP3AjRMMttZKUB48QWIFGg==" }, + "messageformat": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/messageformat/-/messageformat-1.1.1.tgz", + "integrity": "sha512-Q0uXcDtF5pEZsVSyhzDOGgZZK6ykN79VY9CwU3Nv0gsqx62BjdJW0MT+63UkHQ4exe3HE33ZlxR2/YwoJarRTg==", + "requires": { + "glob": "~7.0.6", + "make-plural": "^4.1.1", + "messageformat-parser": "^1.1.0", + "nopt": "~3.0.6", + "reserved-words": "^0.1.2" + }, + "dependencies": { + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "messageformat-parser": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-1.1.0.tgz", + "integrity": "sha512-Hwem6G3MsKDLS1FtBRGIs8T50P1Q00r3srS6QJePCFbad9fq0nYxwf3rnU2BreApRGhmpKMV7oZI06Sy1c9TPA==" + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, + "metric-lcs": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/metric-lcs/-/metric-lcs-0.1.2.tgz", + "integrity": "sha512-+TZ5dUDPKPJaU/rscTzxyN8ZkX7eAVLAiQU/e+YINleXPv03SCmJShaMT1If1liTH8OcmWXZs0CmzCBRBLcMpA==" + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -13309,9 +14462,9 @@ } }, "node-fetch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.2.0.tgz", - "integrity": "sha512-OayFWziIxiHY8bCUyLX6sTpDH8Jsbp4FfYd1j1f7vZyfgkcOnAyM4oQR16f8a0s7Gl/viMGRey8eScYk4V4EZA==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", + "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==" }, "node-fetch-npm": { "version": "2.0.2", @@ -13703,6 +14856,15 @@ "npm-bundled": "^1.0.1" } }, + "npm-path": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.4.tgz", + "integrity": "sha512-IFsj0R9C7ZdR5cP+ET342q77uSRdtWOlWpih5eC+lu29tIDbNEgDbzgVJ5UFvYHWhxDZ5TFkJafFioO0pPQjCw==", + "dev": true, + "requires": { + "which": "^1.2.10" + } + }, "npm-pick-manifest": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-2.1.0.tgz", @@ -13759,6 +14921,17 @@ "path-key": "^2.0.0" } }, + "npm-which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-which/-/npm-which-3.0.1.tgz", + "integrity": "sha1-kiXybsOihcIJyuZ8OxGmtKtxQKo=", + "dev": true, + "requires": { + "commander": "^2.9.0", + "npm-path": "^2.0.2", + "which": "^1.2.10" + } + }, "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", @@ -13943,6 +15116,17 @@ "has": "^1.0.1" } }, + "object.fromentries": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz", + "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==", + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.11.0", + "function-bind": "^1.1.1", + "has": "^1.0.1" + } + }, "object.getownpropertydescriptors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", @@ -14407,13 +15591,28 @@ "readable-stream": "^2.1.5" } }, - "parse-asn1": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", - "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", + "parent-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.0.tgz", + "integrity": "sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA==", "requires": { - "asn1.js": "^4.0.0", - "browserify-aes": "^1.0.0", + "callsites": "^3.0.0" + }, + "dependencies": { + "callsites": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==" + } + } + }, + "parse-asn1": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", + "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.0", "pbkdf2": "^3.0.3" @@ -14635,6 +15834,11 @@ "xmldom": "0.1.x" } }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==" + }, "pn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", @@ -14652,7 +15856,7 @@ "dependencies": { "async": { "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" }, "debug": { @@ -16240,60 +17444,648 @@ "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" + }, + "prettier": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.15.3.tgz", + "integrity": "sha512-gAU9AGAPMaKb3NNSUUuhhFAS7SCO4ALTN4nRIn6PJ075Qd28Yn2Ig2ahEJWdJwJmlEBTUfC7mMUSFy8MwsOCfg==" + }, + "prettier-eslint": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-8.8.2.tgz", + "integrity": "sha512-2UzApPuxi2yRoyMlXMazgR6UcH9DKJhNgCviIwY3ixZ9THWSSrUww5vkiZ3C48WvpFl1M1y/oU63deSy1puWEA==", + "requires": { + "babel-runtime": "^6.26.0", + "common-tags": "^1.4.0", + "dlv": "^1.1.0", + "eslint": "^4.0.0", + "indent-string": "^3.2.0", + "lodash.merge": "^4.6.0", + "loglevel-colored-level-prefix": "^1.0.0", + "prettier": "^1.7.0", + "pretty-format": "^23.0.1", + "require-relative": "^0.8.7", + "typescript": "^2.5.1", + "typescript-eslint-parser": "^16.0.0", + "vue-eslint-parser": "^2.0.2" + }, + "dependencies": { + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" + } + } + }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=" + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=" + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "eslint": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", + "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "requires": { + "ajv": "^5.3.0", + "babel-code-frame": "^6.22.0", + "chalk": "^2.1.0", + "concat-stream": "^1.6.0", + "cross-spawn": "^5.1.0", + "debug": "^3.1.0", + "doctrine": "^2.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.4", + "esquery": "^1.0.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.0.1", + "ignore": "^3.3.3", + "imurmurhash": "^0.1.4", + "inquirer": "^3.0.6", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.9.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.4", + "minimatch": "^3.0.2", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^1.0.1", + "require-uncached": "^1.0.3", + "semver": "^5.3.0", + "strip-ansi": "^4.0.0", + "strip-json-comments": "~2.0.1", + "table": "4.0.2", + "text-table": "~0.2.0" + } + }, + "eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "requires": { + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" + } + }, + "globals": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.10.0.tgz", + "integrity": "sha512-0GZF1RiPKU97IHUO5TORo9w1PwrH/NBPl+fS7oMLdaTRiYmYbwK4NWoZWrAdd0/abG9R2BU+OiwyQpTpE6pdfQ==" + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.4", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rx-lite": "^4.0.8", + "rx-lite-aggregates": "^4.0.8", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + } + }, + "regexpp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", + "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==" + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "requires": { + "is-fullwidth-code-point": "^2.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "requires": { + "ajv": "^5.2.3", + "ajv-keywords": "^2.1.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" + } + }, + "typescript": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", + "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==" + }, + "typescript-eslint-parser": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/typescript-eslint-parser/-/typescript-eslint-parser-16.0.1.tgz", + "integrity": "sha512-IKawLTu4A2xN3aN/cPLxvZ0bhxZHILGDKTZWvWNJ3sLNhJ3PjfMEDQmR2VMpdRPrmWOadgWXRwjLBzSA8AGsaQ==", + "requires": { + "lodash.unescape": "4.0.1", + "semver": "5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + } + } + } + } + }, + "prettier-eslint-cli": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/prettier-eslint-cli/-/prettier-eslint-cli-4.7.1.tgz", + "integrity": "sha512-hQbsGaEVz97oBBcKdsJ46khv0kOGkMyWrXzcFOXW6X8UuetZ/j0yDJkNJgUTVc6PVFbbzBXk+qgd5vos9qzXPQ==", + "requires": { + "arrify": "^1.0.1", + "babel-runtime": "^6.23.0", + "boolify": "^1.0.0", + "camelcase-keys": "^4.1.0", + "chalk": "2.3.0", + "common-tags": "^1.4.0", + "eslint": "^4.5.0", + "find-up": "^2.1.0", + "get-stdin": "^5.0.1", + "glob": "^7.1.1", + "ignore": "^3.2.7", + "indent-string": "^3.1.0", + "lodash.memoize": "^4.1.2", + "loglevel-colored-level-prefix": "^1.0.0", + "messageformat": "^1.0.2", + "prettier-eslint": "^8.5.0", + "rxjs": "^5.3.0", + "yargs": "10.0.3" + }, + "dependencies": { + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" + } + } + }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=" + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + } + }, + "chalk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "requires": { + "ansi-styles": "^3.1.0", + "escape-string-regexp": "^1.0.5", + "supports-color": "^4.0.0" + } + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=" + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "eslint": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", + "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "requires": { + "ajv": "^5.3.0", + "babel-code-frame": "^6.22.0", + "chalk": "^2.1.0", + "concat-stream": "^1.6.0", + "cross-spawn": "^5.1.0", + "debug": "^3.1.0", + "doctrine": "^2.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.4", + "esquery": "^1.0.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.0.1", + "ignore": "^3.3.3", + "imurmurhash": "^0.1.4", + "inquirer": "^3.0.6", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.9.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.4", + "minimatch": "^3.0.2", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^1.0.1", + "require-uncached": "^1.0.3", + "semver": "^5.3.0", + "strip-ansi": "^4.0.0", + "strip-json-comments": "~2.0.1", + "table": "4.0.2", + "text-table": "~0.2.0" + } + }, + "eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "requires": { + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" + } + }, + "get-stdin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", + "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=" + }, + "globals": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.10.0.tgz", + "integrity": "sha512-0GZF1RiPKU97IHUO5TORo9w1PwrH/NBPl+fS7oMLdaTRiYmYbwK4NWoZWrAdd0/abG9R2BU+OiwyQpTpE6pdfQ==" + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.4", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rx-lite": "^4.0.8", + "rx-lite-aggregates": "^4.0.8", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + } + }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=" + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" } }, - "has-flag": { + "regexpp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", + "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==" + }, + "slice-ansi": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "requires": { + "is-fullwidth-code-point": "^2.0.0" + } }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" + "ansi-regex": "^3.0.0" } }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", "requires": { - "has-flag": "^1.0.0" + "has-flag": "^2.0.0" + } + }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "requires": { + "ajv": "^5.2.3", + "ajv-keywords": "^2.1.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" + } + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "yargs": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-10.0.3.tgz", + "integrity": "sha512-DqBpQ8NAUX4GyPP/ijDGHsJya4tYqLQrjPr95HNsr1YwL3+daCfvBwg7+gIC6IdJhR2kATh3hb61vjzMWEtjdw==", + "requires": { + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^8.0.0" + } + }, + "yargs-parser": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-8.1.0.tgz", + "integrity": "sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==", + "requires": { + "camelcase": "^4.1.0" } } } }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" - }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "requires": { + "fast-diff": "^1.1.2" + } }, "pretty-bytes": { "version": "1.0.4", @@ -16343,6 +18135,11 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + }, "progress-stream": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.2.0.tgz", @@ -16789,7 +18586,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "requires": { "ansi-styles": "^2.2.1", @@ -16816,7 +18613,7 @@ }, "external-editor": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "requires": { "chardet": "^0.4.0", @@ -17097,7 +18894,7 @@ }, "json5": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "requires": { "minimist": "^1.2.0" @@ -17105,7 +18902,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" } } @@ -17447,6 +19244,11 @@ "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.9.tgz", "integrity": "sha512-VncXxOF6uFlYog5prG2j+e2UGJeam5MfNiJnB/qEgo4KTnMm2XrELCg4rNZ6IlaEUZnGlb8aB6lXowCRQtTkkA==" }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==" + }, "regexpu-core": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", @@ -17570,11 +19372,42 @@ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, + "require-relative": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", + "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=" + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=" + } + } + }, + "requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==" + }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "reserved-words": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/reserved-words/-/reserved-words-0.1.2.tgz", + "integrity": "sha1-AKCUD5jNUBrqqsMWQR2a3FKzGrE=" + }, "resolve": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", @@ -17783,6 +19616,12 @@ "is-promise": "^2.1.0" } }, + "run-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz", + "integrity": "sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==", + "dev": true + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -18266,6 +20105,32 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, + "simple-git": { + "version": "1.107.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.107.0.tgz", + "integrity": "sha512-t4OK1JRlp4ayKRfcW6owrWcRVLyHRUlhGd0uN6ZZTqfDq8a5XpcUdOKiGRNobHEuMtNqzp0vcJNvhYWwh5PsQA==", + "dev": true, + "requires": { + "debug": "^4.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, "simple-update-in": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/simple-update-in/-/simple-update-in-1.4.0.tgz", @@ -18309,6 +20174,26 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" }, + "slice-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.0.0.tgz", + "integrity": "sha512-4j2WTWjp3GsZ+AOagyzVbzp4vWGtZ0hEZ/gDY/uTvm6MTxUfTUIsnMIFb1bn8o0RuXiqUw15H1bue8f22Vw2oQ==", + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + } + } + }, "slide": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", @@ -18710,6 +20595,12 @@ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" }, + "staged-git-files": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/staged-git-files/-/staged-git-files-1.1.2.tgz", + "integrity": "sha512-0Eyrk6uXW6tg9PYkhi/V/J4zHp33aNyi2hOCmhFLqLTIhbgqWn5jlSzI+IU0VqrZq6+DbHcabQl/WP6P3BG0QA==", + "dev": true + }, "stat-mode": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.2.2.tgz", @@ -18848,6 +20739,12 @@ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" }, + "string-argv": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.0.2.tgz", + "integrity": "sha1-2sMECGkMIfPDYwo/86BYd73L1zY=", + "dev": true + }, "string-length": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", @@ -18924,6 +20821,17 @@ "safe-buffer": "~5.1.0" } }, + "stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "requires": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + } + }, "stringstream": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", @@ -19056,6 +20964,40 @@ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=" }, + "table": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/table/-/table-5.2.1.tgz", + "integrity": "sha512-qmhNs2GEHNqY5fd2Mo+8N1r2sw/rvTAAvBZTaTx+Y7PHLypqyrxr1MdIu0pLw6Xvl/Gi4ONu/sdceP8vvUjkyA==", + "requires": { + "ajv": "^6.6.1", + "lodash": "^4.17.11", + "slice-ansi": "2.0.0", + "string-width": "^2.1.1" + }, + "dependencies": { + "ajv": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz", + "integrity": "sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + } + } + }, "tapable": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz", @@ -19445,53 +21387,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" }, - "tslint": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.12.0.tgz", - "integrity": "sha512-CKEcH1MHUBhoV43SA/Jmy1l24HJJgI0eyLbBNSRyFlsQvb9v6Zdq+Nz2vEOH00nC5SUx4SneJ59PZUS/ARcokQ==", - "requires": { - "babel-code-frame": "^6.22.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^3.2.0", - "glob": "^7.1.1", - "js-yaml": "^3.7.0", - "minimatch": "^3.0.4", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.8.0", - "tsutils": "^2.27.2" - } - }, - "tslint-loader": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/tslint-loader/-/tslint-loader-3.6.0.tgz", - "integrity": "sha512-Me9Qf/87BOfCY8uJJw+J7VMF4U8WiMXKLhKKKugMydF0xMhMOt9wo2mjYTNhwbF9H7SHh8PAIwRG8roisTNekQ==", - "requires": { - "loader-utils": "^1.0.2", - "mkdirp": "^0.5.1", - "object-assign": "^4.1.1", - "rimraf": "^2.4.4", - "semver": "^5.3.0" - } - }, - "tslint-react": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/tslint-react/-/tslint-react-3.6.0.tgz", - "integrity": "sha512-AIv1QcsSnj7e9pFir6cJ6vIncTqxfqeFF3Lzh8SuuBljueYzEAtByuB6zMaD27BL0xhMEqsZ9s5eHuCONydjBw==", - "requires": { - "tsutils": "^2.13.1" - } - }, - "tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", - "requires": { - "tslib": "^1.8.1" - } - }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", @@ -19538,6 +21433,32 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.1.tgz", "integrity": "sha512-Veu0w4dTc/9wlWNf2jeRInNodKlcdLgemvPsrNpfu5Pq39sgfFjvIIgTsvUHCoLBnMhPoUA+tFxsXjU6VexVRQ==" }, + "typescript-eslint-parser": { + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/typescript-eslint-parser/-/typescript-eslint-parser-21.0.2.tgz", + "integrity": "sha512-u+pj4RVJBr4eTzj0n5npoXD/oRthvfUCjSKndhNI714MG0mQq2DJw5WP7qmonRNIFgmZuvdDOH3BHm9iOjIAfg==", + "requires": { + "eslint-scope": "^4.0.0", + "eslint-visitor-keys": "^1.0.0", + "typescript-estree": "5.3.0" + } + }, + "typescript-estree": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/typescript-estree/-/typescript-estree-5.3.0.tgz", + "integrity": "sha512-Vu0KmYdSCkpae+J48wsFC1ti19Hq3Wi/lODUaE+uesc3gzqhWbZ5itWbsjylLVbjNW4K41RqDzSfnaYNbmEiMQ==", + "requires": { + "lodash.unescape": "4.0.1", + "semver": "5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + } + } + }, "typical": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", @@ -20174,6 +22095,54 @@ "indexof": "0.0.1" } }, + "vue-eslint-parser": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz", + "integrity": "sha512-ZezcU71Owm84xVF6gfurBQUGg8WQ+WZGxgDEQu1IHFBZNx7BFZg3L1yHxrCBNNwbwFtE1GuvfJKMtb6Xuwc/Bw==", + "requires": { + "debug": "^3.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.2", + "esquery": "^1.0.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" + } + } + }, + "eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + } + } + }, "w3c-hr-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", @@ -20833,6 +22802,14 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "requires": { + "mkdirp": "^0.5.1" + } + }, "write-file-atomic": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", diff --git a/package.json b/package.json index e00e2f5fa..894ac930d 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,8 @@ "scripts": { "bootstrap": "lerna bootstrap --hoist", "build": "npm rebuild node-sass && lerna run build", + "lint": "lerna run lint --no-bail", + "lint:fix": "lerna run lint:fix --no-bail", "start": "cd packages\\app\\client && npm run start", "test": "jest --no-cache", "test:coveralls": "jest --runInBand --bail --coverage --coverageReporters=text-lcov | coveralls" @@ -15,7 +17,10 @@ "@babel/preset-env": "^7.1.0", "@babel/preset-typescript": "^7.1.0", "babel-core": "^7.0.0-bridge.0", - "babel-jest": "23.6.0" + "babel-jest": "23.6.0", + "husky": "^1.3.1", + "lint-staged": "^8.1.0", + "prettier": "^1.15.3" }, "jest": { "setupTestFrameworkScriptFile": "./testSetup.js", @@ -35,6 +40,17 @@ "node" ] }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "*.{ts,tsx,js,jsx}": [ + "prettier --write", + "git add" + ] + }, "dependencies": { "lerna": "3.4.0" } diff --git a/packages/app/client/.eslintignore b/packages/app/client/.eslintignore new file mode 100644 index 000000000..19566fb8a --- /dev/null +++ b/packages/app/client/.eslintignore @@ -0,0 +1 @@ +**/*.scss.d.ts diff --git a/packages/app/client/.eslintrc.js b/packages/app/client/.eslintrc.js new file mode 100644 index 000000000..b3fa9390d --- /dev/null +++ b/packages/app/client/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: '../../../.eslintrc.react.js', +}; diff --git a/packages/app/client/package-lock.json b/packages/app/client/package-lock.json index 3904039df..4e259b723 100644 --- a/packages/app/client/package-lock.json +++ b/packages/app/client/package-lock.json @@ -1374,6 +1374,12 @@ } } }, + "acorn-jsx": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", + "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "dev": true + }, "acorn-walk": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", @@ -1481,7 +1487,7 @@ }, "ansi-escapes": { "version": "3.1.0", - "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "dev": true }, @@ -1568,7 +1574,7 @@ }, "array-equal": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", "dev": true }, @@ -1853,6 +1859,32 @@ } } }, + "babel-eslint": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.1.tgz", + "integrity": "sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "eslint-scope": "3.7.1", + "eslint-visitor-keys": "^1.0.0" + }, + "dependencies": { + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + } + } + }, "babel-generator": { "version": "6.26.1", "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", @@ -2078,7 +2110,7 @@ }, "babel-plugin-istanbul": { "version": "4.1.6", - "resolved": "http://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", "dev": true, "requires": { @@ -2096,43 +2128,43 @@ }, "babel-plugin-syntax-async-functions": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", "dev": true }, "babel-plugin-syntax-class-properties": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", "dev": true }, "babel-plugin-syntax-dynamic-import": { "version": "6.18.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=", "dev": true }, "babel-plugin-syntax-exponentiation-operator": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", "dev": true }, "babel-plugin-syntax-flow": { "version": "6.18.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=", "dev": true }, "babel-plugin-syntax-jsx": { "version": "6.18.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", "dev": true }, "babel-plugin-syntax-object-rest-spread": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", "dev": true }, @@ -2934,7 +2966,7 @@ }, "boom": { "version": "2.10.1", - "resolved": "http://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", "dev": true, "requires": { @@ -3142,7 +3174,7 @@ "dependencies": { "resolve": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", "dev": true } @@ -3150,7 +3182,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -3187,7 +3219,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -3241,7 +3273,7 @@ }, "buffer": { "version": "4.9.1", - "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { @@ -3300,7 +3332,7 @@ }, "cacache": { "version": "10.0.4", - "resolved": "http://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", "dev": true, "requires": { @@ -3350,7 +3382,7 @@ }, "camelcase-keys": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { @@ -3499,6 +3531,12 @@ "safe-buffer": "^5.0.1" } }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, "circular-json-es6": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/circular-json-es6/-/circular-json-es6-2.0.2.tgz", @@ -3674,7 +3712,7 @@ }, "color": { "version": "0.11.4", - "resolved": "http://registry.npmjs.org/color/-/color-0.11.4.tgz", + "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", "dev": true, "requires": { @@ -3698,7 +3736,7 @@ }, "color-string": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", "dev": true, "requires": { @@ -3718,7 +3756,7 @@ }, "colors": { "version": "1.1.2", - "resolved": "http://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", "dev": true }, @@ -3824,6 +3862,12 @@ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", "dev": true }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, "content-disposition": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", @@ -3961,7 +4005,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { @@ -3974,7 +4018,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -4011,7 +4055,7 @@ }, "cryptiles": { "version": "2.0.5", - "resolved": "http://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", "dev": true, "requires": { @@ -4059,7 +4103,7 @@ }, "css-color-names": { "version": "0.0.4", - "resolved": "http://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", "dev": true }, @@ -4096,7 +4140,7 @@ }, "css-select": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", "dev": true, "requires": { @@ -4556,7 +4600,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { @@ -4623,6 +4667,15 @@ "buffer-indexof": "^1.0.0" } }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "dom-serializer": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", @@ -4634,7 +4687,7 @@ "dependencies": { "domelementtype": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" } } @@ -4684,13 +4737,13 @@ }, "dotenv": { "version": "4.0.0", - "resolved": "http://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz", "integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=", "dev": true }, "duplexer": { "version": "0.1.1", - "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, @@ -4951,6 +5004,336 @@ } } }, + "eslint": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.12.0.tgz", + "integrity": "sha512-LntwyPxtOHrsJdcSwyQKVtHofPHdv+4+mFwEe91r2V13vqpM8yLr7b1sW+Oo/yheOPkWYsYlYJCkzlFAt8KV7g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.5.3", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^2.1.0", + "eslint-scope": "^4.0.0", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.0", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.1.0", + "js-yaml": "^3.12.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.5", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.0.2", + "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "inquirer": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.1.tgz", + "integrity": "sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.0", + "figures": "^2.0.0", + "lodash": "^4.17.10", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.1.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "strip-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", + "dev": true, + "requires": { + "ansi-regex": "^4.0.0" + } + } + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + } + } + } + } + }, + "eslint-config-prettier": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-3.5.0.tgz", + "integrity": "sha512-LcZEoAY5lL3/H2NTFSeUl/z8X8oMea1IxLEIb5uDbRxPTdQeeT7oGpRWT6UwHXGcoRbYH0TZmfRsh8iXbpyW7A==", + "dev": true, + "requires": { + "get-stdin": "^6.0.0" + }, + "dependencies": { + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + } + } + }, + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.5.0" + } + }, + "eslint-module-utils": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz", + "integrity": "sha1-snA2LNiLGkitMIl2zn+lTphBF0Y=", + "dev": true, + "requires": { + "debug": "^2.6.8", + "pkg-dir": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, + "requires": { + "find-up": "^1.0.0" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz", + "integrity": "sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g==", + "dev": true, + "requires": { + "contains-path": "^0.1.0", + "debug": "^2.6.8", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.1", + "eslint-module-utils": "^2.2.0", + "has": "^1.0.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.3", + "read-pkg-up": "^2.0.0", + "resolve": "^1.6.0" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "eslint-plugin-notice": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/eslint-plugin-notice/-/eslint-plugin-notice-0.7.7.tgz", + "integrity": "sha512-kHVNObyW2QPEqunQyZiV9loKRV04IuDv096rtYJSziyDASOSPcjl2eLibQdw8BFNbb/DNZj3obN8iMAeydL2hg==", + "dev": true, + "requires": { + "find-root": "^1.1.0", + "lodash": ">=2.4.0", + "metric-lcs": "^0.1.2" + } + }, + "eslint-plugin-prettier": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.0.1.tgz", + "integrity": "sha512-/PMttrarPAY78PLvV3xfWibMOdMDl57hmlQ2XqFeA37wd+CJ7WSxV7txqjVPHi/AAFKd2lX0ZqfsOc/i5yFCSQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-plugin-typescript": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-typescript/-/eslint-plugin-typescript-1.0.0-rc.3.tgz", + "integrity": "sha512-urXH7sKqRkFLcDj6dP3tvQALfWIRGn8J8Kg9dYSoavXvy62FxfUv6JgjDAHQZudi9fSRQpEZA/LqdJXDOwKHPA==", + "dev": true, + "requires": { + "requireindex": "^1.2.0", + "typescript-eslint-parser": "21.0.2" + } + }, "eslint-scope": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", @@ -4961,12 +5344,52 @@ "estraverse": "^4.1.1" } }, + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.0.tgz", + "integrity": "sha512-1MpUfwsdS9MMoN7ZXqAr9e9UKdVHDcvrJpyx7mm1WuQlx/ygErEQBzgi5Nh5qBHIoYweprhtMkTCb9GhcAIcsA==", + "dev": true, + "requires": { + "acorn": "^6.0.2", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + }, + "dependencies": { + "acorn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.5.tgz", + "integrity": "sha512-i33Zgp3XWtmZBMNvCr4azvOFeWVw1Rk6p3hfi3LUDvIFraOMywb1kAtrbi+med14m4Xfpqm3zRZMT+c0FNE7kg==", + "dev": true + } + } + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", @@ -5355,6 +5778,12 @@ "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", "dev": true }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", @@ -5421,6 +5850,16 @@ "escape-string-regexp": "^1.0.5" } }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, "file-loader": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", @@ -5478,7 +5917,7 @@ }, "finalhandler": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "dev": true, "requires": { @@ -5502,6 +5941,12 @@ "pkg-dir": "^3.0.0" } }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "dev": true + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -5534,6 +5979,18 @@ } } }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + } + }, "flatten": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", @@ -6225,6 +6682,12 @@ "is-callable": "^1.1.3" } }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -6304,7 +6767,7 @@ }, "get-stream": { "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true }, @@ -6681,7 +7144,7 @@ }, "hawk": { "version": "3.1.3", - "resolved": "http://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", "dev": true, "requires": { @@ -6726,7 +7189,7 @@ }, "hoek": { "version": "2.16.3", - "resolved": "http://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", "dev": true }, @@ -6821,7 +7284,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { @@ -6850,7 +7313,7 @@ }, "http-proxy-middleware": { "version": "0.18.0", - "resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", "dev": true, "requires": { @@ -6942,6 +7405,24 @@ "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", "dev": true }, + "import-fresh": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", + "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, "import-local": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", @@ -7121,7 +7602,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { @@ -7168,7 +7649,7 @@ }, "is-builtin-module": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { @@ -7192,7 +7673,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { @@ -8132,7 +8613,7 @@ }, "jest-get-type": { "version": "22.4.3", - "resolved": "http://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", "dev": true }, @@ -8793,6 +9274,12 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -8816,7 +9303,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -8977,7 +9464,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { @@ -9249,6 +9736,12 @@ "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", "dev": true }, + "lodash.unescape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", + "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=", + "dev": true + }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -9410,7 +9903,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, @@ -9497,6 +9990,12 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", "dev": true }, + "metric-lcs": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/metric-lcs/-/metric-lcs-0.1.2.tgz", + "integrity": "sha512-+TZ5dUDPKPJaU/rscTzxyN8ZkX7eAVLAiQU/e+YINleXPv03SCmJShaMT1If1liTH8OcmWXZs0CmzCBRBLcMpA==", + "dev": true + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -9615,7 +10114,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, @@ -9678,7 +10177,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -10519,7 +11018,7 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, @@ -10536,7 +11035,7 @@ }, "os-tmpdir": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, @@ -10575,7 +11074,7 @@ }, "p-is-promise": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", "dev": true }, @@ -10626,9 +11125,26 @@ "readable-stream": "^2.1.5" } }, + "parent-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.0.tgz", + "integrity": "sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + }, + "dependencies": { + "callsites": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", + "dev": true + } + } + }, "parse-asn1": { "version": "5.1.1", - "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", "dev": true, "requires": { @@ -10706,7 +11222,7 @@ }, "path-browserify": { "version": "0.0.0", - "resolved": "http://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", "dev": true }, @@ -10724,7 +11240,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, @@ -10792,7 +11308,7 @@ }, "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, @@ -10865,6 +11381,12 @@ } } }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, "pn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", @@ -10884,7 +11406,7 @@ "dependencies": { "async": { "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true } @@ -11380,6 +11902,15 @@ "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", "dev": true }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "pretty-format": { "version": "23.6.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", @@ -11415,6 +11946,12 @@ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", @@ -12040,7 +12577,7 @@ }, "reduce-css-calc": { "version": "1.3.0", - "resolved": "http://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", "dev": true, "requires": { @@ -12149,6 +12686,12 @@ "integrity": "sha512-8t6074A68gHfU8Neftl0Le6KTDwfGAj7IyjPIMSfikI2wJUTHDMaIq42bUsfVnj8mhx0R+45rdUXHGpN164avA==", "dev": true }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, "regexpu-core": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.4.0.tgz", @@ -12180,7 +12723,7 @@ "dependencies": { "jsesc": { "version": "0.5.0", - "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "dev": true } @@ -12273,6 +12816,12 @@ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, + "requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true + }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -12369,7 +12918,7 @@ "dependencies": { "convert-source-map": { "version": "0.3.5", - "resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz", "integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=", "dev": true } @@ -12471,7 +13020,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { @@ -12502,7 +13051,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -12590,7 +13139,7 @@ }, "os-locale": { "version": "1.4.0", - "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { @@ -12711,7 +13260,7 @@ "dependencies": { "source-map": { "version": "0.4.4", - "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { @@ -12845,7 +13394,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -12934,6 +13483,17 @@ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true }, + "slice-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.0.0.tgz", + "integrity": "sha512-4j2WTWjp3GsZ+AOagyzVbzp4vWGtZ0hEZ/gDY/uTvm6MTxUfTUIsnMIFb1bn8o0RuXiqUw15H1bue8f22Vw2oQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -13043,7 +13603,7 @@ }, "sntp": { "version": "1.0.9", - "resolved": "http://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", "dev": true, "requires": { @@ -13249,7 +13809,7 @@ }, "sprintf-js": { "version": "1.0.3", - "resolved": "http://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "srcset": { @@ -13337,7 +13897,7 @@ }, "stream-browserify": { "version": "2.0.1", - "resolved": "http://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", "dev": true, "requires": { @@ -13472,7 +14032,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -13490,7 +14050,7 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, @@ -13503,6 +14063,12 @@ "get-stdin": "^4.0.1" } }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, "style-loader": { "version": "0.21.0", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.21.0.tgz", @@ -13565,6 +14131,18 @@ "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", "dev": true }, + "table": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/table/-/table-5.2.1.tgz", + "integrity": "sha512-qmhNs2GEHNqY5fd2Mo+8N1r2sw/rvTAAvBZTaTx+Y7PHLypqyrxr1MdIu0pLw6Xvl/Gi4ONu/sdceP8vvUjkyA==", + "dev": true, + "requires": { + "ajv": "^6.6.1", + "lodash": "^4.17.11", + "slice-ansi": "2.0.0", + "string-width": "^2.1.1" + } + }, "tapable": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz", @@ -13700,7 +14278,7 @@ }, "through": { "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "through2": { @@ -13871,51 +14449,9 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" }, - "tslint": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.12.0.tgz", - "integrity": "sha512-CKEcH1MHUBhoV43SA/Jmy1l24HJJgI0eyLbBNSRyFlsQvb9v6Zdq+Nz2vEOH00nC5SUx4SneJ59PZUS/ARcokQ==", - "dev": true, - "requires": { - "babel-code-frame": "^6.22.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^3.2.0", - "glob": "^7.1.1", - "js-yaml": "^3.7.0", - "minimatch": "^3.0.4", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.8.0", - "tsutils": "^2.27.2" - } - }, - "tslint-loader": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/tslint-loader/-/tslint-loader-3.6.0.tgz", - "integrity": "sha512-Me9Qf/87BOfCY8uJJw+J7VMF4U8WiMXKLhKKKugMydF0xMhMOt9wo2mjYTNhwbF9H7SHh8PAIwRG8roisTNekQ==", - "dev": true, - "requires": { - "loader-utils": "^1.0.2", - "mkdirp": "^0.5.1", - "object-assign": "^4.1.1", - "rimraf": "^2.4.4", - "semver": "^5.3.0" - } - }, - "tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, "tty-browserify": { "version": "0.0.0", - "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", "dev": true }, @@ -13965,6 +14501,35 @@ "integrity": "sha512-Veu0w4dTc/9wlWNf2jeRInNodKlcdLgemvPsrNpfu5Pq39sgfFjvIIgTsvUHCoLBnMhPoUA+tFxsXjU6VexVRQ==", "dev": true }, + "typescript-eslint-parser": { + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/typescript-eslint-parser/-/typescript-eslint-parser-21.0.2.tgz", + "integrity": "sha512-u+pj4RVJBr4eTzj0n5npoXD/oRthvfUCjSKndhNI714MG0mQq2DJw5WP7qmonRNIFgmZuvdDOH3BHm9iOjIAfg==", + "dev": true, + "requires": { + "eslint-scope": "^4.0.0", + "eslint-visitor-keys": "^1.0.0", + "typescript-estree": "5.3.0" + } + }, + "typescript-estree": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/typescript-estree/-/typescript-estree-5.3.0.tgz", + "integrity": "sha512-Vu0KmYdSCkpae+J48wsFC1ti19Hq3Wi/lODUaE+uesc3gzqhWbZ5itWbsjylLVbjNW4K41RqDzSfnaYNbmEiMQ==", + "dev": true, + "requires": { + "lodash.unescape": "4.0.1", + "semver": "5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + } + } + }, "typings-for-css-modules-loader": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/typings-for-css-modules-loader/-/typings-for-css-modules-loader-1.7.0.tgz", @@ -14346,7 +14911,7 @@ }, "util": { "version": "0.10.3", - "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, "requires": { @@ -14433,7 +14998,7 @@ }, "vm-browserify": { "version": "0.0.4", - "resolved": "http://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", "dev": true, "requires": { @@ -14478,7 +15043,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -15130,7 +15695,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { @@ -15166,6 +15731,15 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, "write-file-atomic": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", @@ -15254,7 +15828,7 @@ }, "yargs": { "version": "11.1.0", - "resolved": "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", "dev": true, "requires": { diff --git a/packages/app/client/package.json b/packages/app/client/package.json index b553564a3..4a636424f 100644 --- a/packages/app/client/package.json +++ b/packages/app/client/package.json @@ -12,7 +12,8 @@ "build:shared:dev": "webpack --mode development --progress --colors", "build:app:dev": "webpack --mode development --progress --colors", "build": "run-s lint build:vendors build:shared build:app", - "lint": "tslint --project tsconfig.json", + "lint": "eslint --color --quiet --ext .js,.jsx,.ts,.tsx ./src", + "lint:fix": "npm run lint -- --fix", "start": "run-s build:vendors:dev build:shared:dev webpackdevServer:dev", "webpackdevServer:dev": "webpack-dev-server --mode development --hot --inline --progress --colors --content-base ./public", "test": "jest" @@ -50,6 +51,7 @@ "@types/react": "~16.3.2", "@types/react-dom": "^16.0.4", "@types/request": "^2.47.0", + "babel-eslint": "^10.0.1", "babel-jest": "23.6.0", "babel-loader": "^8.0.2", "babel-preset-react-app": "^3.1.1", @@ -59,6 +61,12 @@ "css-loader": "^0.28.11", "enzyme": "^3.3.0", "enzyme-adapter-react-16": "^1.1.1", + "eslint": "^5.12.0", + "eslint-config-prettier": "^3.5.0", + "eslint-plugin-import": "^2.14.0", + "eslint-plugin-notice": "^0.7.7", + "eslint-plugin-prettier": "^3.0.1", + "eslint-plugin-typescript": "^1.0.0-rc.3", "file-loader": "^1.1.11", "hard-source-webpack-plugin": "^0.12.0", "jest": "^23.0.0", @@ -75,8 +83,6 @@ "sass-loader": "^7.1.0", "style-loader": "^0.21.0", "ts-loader": "^4.4.2", - "tslint": "^5.10.0", - "tslint-loader": "^3.6.0", "typescript": "3.1.1", "typings-for-css-modules-loader": "^1.7.0", "url-loader": "^1.0.1", diff --git a/packages/app/client/src/commands/botCommands.spec.ts b/packages/app/client/src/commands/botCommands.spec.ts index 1d5426940..e2643a759 100644 --- a/packages/app/client/src/commands/botCommands.spec.ts +++ b/packages/app/client/src/commands/botCommands.spec.ts @@ -1,44 +1,81 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { SharedConstants } from '@bfemulator/app-shared'; -import { BotConfigWithPathImpl, CommandRegistryImpl } from '@bfemulator/sdk-shared'; +import { + BotConfigWithPathImpl, + CommandRegistryImpl, +} from '@bfemulator/sdk-shared'; +import { combineReducers, createStore } from 'redux'; + import * as BotActions from '../data/action/botActions'; import { bot } from '../data/reducer/bot'; import { resources } from '../data/reducer/resourcesReducer'; import { CommandServiceImpl } from '../platform/commands/commandServiceImpl'; import { ActiveBotHelper } from '../ui/helpers/activeBotHelper'; + import { registerCommands } from './botCommands'; -import { combineReducers, createStore } from 'redux'; const mockBotInfo = { path: 'some/path.bot', displayName: 'MyBot', - secret: 'secret' + secret: 'secret', }; const mockBot = BotConfigWithPathImpl.fromJSON({ - 'path': 'some/path', - 'name': 'AuthBot', - 'description': '', - 'padlock': '', - 'services': [ + path: 'some/path', + name: 'AuthBot', + description: '', + padlock: '', + services: [ { - 'appId': '4f8fde3f-48d3-4d8a-a954-393efe39809e', - 'id': 'cded37c0-83f2-11e8-ac6d-b7172cd24b28', - 'type': 'endpoint', - 'appPassword': 'REDACTED', - 'endpoint': 'http://localhost:55697/api/messages', - 'name': 'authsample' - } - ] + appId: '4f8fde3f-48d3-4d8a-a954-393efe39809e', + id: 'cded37c0-83f2-11e8-ac6d-b7172cd24b28', + type: 'endpoint', + appPassword: 'REDACTED', + endpoint: 'http://localhost:55697/api/messages', + name: 'authsample', + }, + ], } as any); -let mockStore = createStore(combineReducers({ bot, resources }), { - bot: { botFiles: [mockBotInfo] } +const mockStore = createStore(combineReducers({ bot, resources }), { + bot: { botFiles: [mockBotInfo] }, }); jest.mock('../data/store', () => ({ get store() { return mockStore; - } + }, })); jest.mock('../ui/dialogs/', () => ({})); @@ -51,7 +88,9 @@ describe('The bot commands', () => { it('should make the appropriate calls to switch bots', () => { const spy = jest.spyOn(ActiveBotHelper, 'confirmAndSwitchBots'); - const { handler } = registry.getCommand(SharedConstants.Commands.Bot.Switch); + const { handler } = registry.getCommand( + SharedConstants.Commands.Bot.Switch + ); handler({}); expect(spy).toHaveBeenCalledWith({}); }); @@ -82,11 +121,15 @@ describe('The bot commands', () => { it('should make the appropriate calls to sync the bot list', () => { const dispatchSpy = jest.spyOn(mockStore, 'dispatch'); const remoteCallSpy = jest.spyOn(CommandServiceImpl, 'remoteCall'); - const { handler } = registry.getCommand(SharedConstants.Commands.Bot.SyncBotList); + const { handler } = registry.getCommand( + SharedConstants.Commands.Bot.SyncBotList + ); handler([{}]); expect(dispatchSpy).toHaveBeenCalledWith(BotActions.load([{}])); - expect(remoteCallSpy).toHaveBeenCalledWith(SharedConstants.Commands.Electron.UpdateFileMenu); + expect(remoteCallSpy).toHaveBeenCalledWith( + SharedConstants.Commands.Electron.UpdateFileMenu + ); }); it('should make the appropriate call when setting the active bot', async () => { @@ -95,7 +138,9 @@ describe('The bot commands', () => { remoteCallArgs.push(args); return true; }; - const { handler } = registry.getCommand(SharedConstants.Commands.Bot.SetActive); + const { handler } = registry.getCommand( + SharedConstants.Commands.Bot.SetActive + ); await handler(mockBot, mockBotInfo.path); const state: any = mockStore.getState(); expect(state.bot.activeBot).toEqual(mockBot); @@ -104,23 +149,32 @@ describe('The bot commands', () => { }); it('should dispatch the appropriate actions when updating the list of transcript files on disc', () => { - const { handler: transcriptFilesUpdated } = registry.getCommand(SharedConstants.Commands.Bot.TranscriptFilesUpdated); - const { handler: transcriptPathUpdated } = registry.getCommand(SharedConstants.Commands.Bot.TranscriptsPathUpdated); + const { handler: transcriptFilesUpdated } = registry.getCommand( + SharedConstants.Commands.Bot.TranscriptFilesUpdated + ); + const { handler: transcriptPathUpdated } = registry.getCommand( + SharedConstants.Commands.Bot.TranscriptsPathUpdated + ); transcriptFilesUpdated([{ path: 'transcript/path.transcript' }]); transcriptPathUpdated('transcript/'); const state: any = mockStore.getState(); - expect(state.resources.transcripts).toEqual([{ path: 'transcript/path.transcript' }]); + expect(state.resources.transcripts).toEqual([ + { path: 'transcript/path.transcript' }, + ]); expect(state.resources.transcriptsPath).toBe('transcript/'); }); it('should dispatch the appropriate actions when updating the list of chat files on disc', () => { - const { handler: chatFilesUpdated } = registry.getCommand(SharedConstants.Commands.Bot.ChatFilesUpdated); - const { handler: chatPathUpdated } = registry.getCommand(SharedConstants.Commands.Bot.ChatsPathUpdated); + const { handler: chatFilesUpdated } = registry.getCommand( + SharedConstants.Commands.Bot.ChatFilesUpdated + ); + const { handler: chatPathUpdated } = registry.getCommand( + SharedConstants.Commands.Bot.ChatsPathUpdated + ); chatFilesUpdated([{ path: 'chat/path.chat' }]); chatPathUpdated('chat/'); const state: any = mockStore.getState(); expect(state.resources.chats).toEqual([{ path: 'chat/path.chat' }]); expect(state.resources.chatsPath).toBe('chat/'); }); - }); diff --git a/packages/app/client/src/commands/botCommands.ts b/packages/app/client/src/commands/botCommands.ts index 7b022b89e..4a567458b 100644 --- a/packages/app/client/src/commands/botCommands.ts +++ b/packages/app/client/src/commands/botCommands.ts @@ -31,21 +31,26 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +import { + BotInfo, + getBotDisplayName, + SharedConstants, +} from '@bfemulator/app-shared'; import { BotConfigWithPath, CommandRegistryImpl } from '@bfemulator/sdk-shared'; -import { ActiveBotHelper } from '../ui/helpers/activeBotHelper'; -import { pathExistsInRecentBots } from '../data/botHelpers'; -import { CommandServiceImpl } from '../platform/commands/commandServiceImpl'; -import { store } from '../data/store'; +import { IFileService } from 'botframework-config/lib/schema'; + import * as BotActions from '../data/action/botActions'; import * as FileActions from '../data/action/fileActions'; -import { BotInfo, getBotDisplayName, SharedConstants } from '@bfemulator/app-shared'; import { chatFilesUpdated, chatsDirectoryUpdated, transcriptDirectoryUpdated, - transcriptsUpdated + transcriptsUpdated, } from '../data/action/resourcesAction'; -import { IFileService } from 'botframework-config/lib/schema'; +import { pathExistsInRecentBots } from '../data/botHelpers'; +import { store } from '../data/store'; +import { CommandServiceImpl } from '../platform/commands/commandServiceImpl'; +import { ActiveBotHelper } from '../ui/helpers/activeBotHelper'; /** Registers bot commands */ export function registerCommands(commandRegistry: CommandRegistryImpl) { @@ -53,58 +58,89 @@ export function registerCommands(commandRegistry: CommandRegistryImpl) { // --------------------------------------------------------------------------- // Switches the current active bot - commandRegistry.registerCommand(Commands.Bot.Switch, - (bot: BotConfigWithPath | string) => ActiveBotHelper.confirmAndSwitchBots(bot)); + commandRegistry.registerCommand( + Commands.Bot.Switch, + (bot: BotConfigWithPath | string) => + ActiveBotHelper.confirmAndSwitchBots(bot) + ); // --------------------------------------------------------------------------- // Closes the current active bot - commandRegistry.registerCommand(Commands.Bot.Close, () => ActiveBotHelper.confirmAndCloseBot()); + commandRegistry.registerCommand(Commands.Bot.Close, () => + ActiveBotHelper.confirmAndCloseBot() + ); // --------------------------------------------------------------------------- // Browse for a .bot file and open it - commandRegistry.registerCommand(Commands.Bot.OpenBrowse, () => ActiveBotHelper.confirmAndOpenBotFromFile()); + commandRegistry.registerCommand(Commands.Bot.OpenBrowse, () => + ActiveBotHelper.confirmAndOpenBotFromFile() + ); // --------------------------------------------------------------------------- // Loads the bot on the client side using the activeBotHelper - commandRegistry.registerCommand(Commands.Bot.Load, (bot: BotConfigWithPath): Promise => { - if (!pathExistsInRecentBots(bot.path)) { - // create and switch bots - return ActiveBotHelper.confirmAndCreateBot(bot, ''); + commandRegistry.registerCommand( + Commands.Bot.Load, + (bot: BotConfigWithPath): Promise => { + if (!pathExistsInRecentBots(bot.path)) { + // create and switch bots + return ActiveBotHelper.confirmAndCreateBot(bot, ''); + } + return ActiveBotHelper.confirmAndSwitchBots(bot); } - return ActiveBotHelper.confirmAndSwitchBots(bot); - }); + ); // --------------------------------------------------------------------------- // Syncs the client side list of bots with bots arg (usually called from server side) - commandRegistry.registerCommand(Commands.Bot.SyncBotList, async (bots: BotInfo[]): Promise => { - store.dispatch(BotActions.load(bots)); - await CommandServiceImpl.remoteCall(Commands.Electron.UpdateFileMenu); - }); + commandRegistry.registerCommand( + Commands.Bot.SyncBotList, + async (bots: BotInfo[]): Promise => { + store.dispatch(BotActions.load(bots)); + await CommandServiceImpl.remoteCall(Commands.Electron.UpdateFileMenu); + } + ); // --------------------------------------------------------------------------- // Sets a bot as active (called from server-side) - commandRegistry.registerCommand(Commands.Bot.SetActive, async (bot: BotConfigWithPath, botDirectory: string) => { - store.dispatch(BotActions.setActive(bot)); - store.dispatch(FileActions.setRoot(botDirectory)); - await Promise.all([ - CommandServiceImpl.remoteCall(Commands.Electron.UpdateFileMenu), - CommandServiceImpl.remoteCall(Commands.Electron.SetTitleBar, getBotDisplayName(bot)) - ]); - }); + commandRegistry.registerCommand( + Commands.Bot.SetActive, + async (bot: BotConfigWithPath, botDirectory: string) => { + store.dispatch(BotActions.setActive(bot)); + store.dispatch(FileActions.setRoot(botDirectory)); + await Promise.all([ + CommandServiceImpl.remoteCall(Commands.Electron.UpdateFileMenu), + CommandServiceImpl.remoteCall( + Commands.Electron.SetTitleBar, + getBotDisplayName(bot) + ), + ]); + } + ); - commandRegistry.registerCommand(Commands.Bot.TranscriptFilesUpdated, (transcripts: IFileService[]) => { - store.dispatch(transcriptsUpdated(transcripts)); - }); + commandRegistry.registerCommand( + Commands.Bot.TranscriptFilesUpdated, + (transcripts: IFileService[]) => { + store.dispatch(transcriptsUpdated(transcripts)); + } + ); - commandRegistry.registerCommand(Commands.Bot.ChatFilesUpdated, (chatFiles: IFileService[]) => { - store.dispatch(chatFilesUpdated(chatFiles)); - }); + commandRegistry.registerCommand( + Commands.Bot.ChatFilesUpdated, + (chatFiles: IFileService[]) => { + store.dispatch(chatFilesUpdated(chatFiles)); + } + ); - commandRegistry.registerCommand(Commands.Bot.TranscriptsPathUpdated, (path: string) => { - store.dispatch(transcriptDirectoryUpdated(path)); - }); + commandRegistry.registerCommand( + Commands.Bot.TranscriptsPathUpdated, + (path: string) => { + store.dispatch(transcriptDirectoryUpdated(path)); + } + ); - commandRegistry.registerCommand(Commands.Bot.ChatsPathUpdated, (path: string) => { - store.dispatch(chatsDirectoryUpdated(path)); - }); + commandRegistry.registerCommand( + Commands.Bot.ChatsPathUpdated, + (path: string) => { + store.dispatch(chatsDirectoryUpdated(path)); + } + ); } diff --git a/packages/app/client/src/commands/electronCommands.ts b/packages/app/client/src/commands/electronCommands.ts index 84992947d..716ce9dff 100644 --- a/packages/app/client/src/commands/electronCommands.ts +++ b/packages/app/client/src/commands/electronCommands.ts @@ -31,8 +31,8 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { CommandRegistryImpl } from '@bfemulator/sdk-shared'; import { SharedConstants } from '@bfemulator/app-shared'; +import { CommandRegistryImpl } from '@bfemulator/sdk-shared'; /** Registers electron commands */ export function registerCommands(commandRegistry: CommandRegistryImpl) { @@ -45,15 +45,20 @@ export function registerCommands(commandRegistry: CommandRegistryImpl) { // --------------------------------------------------------------------------- // An update is ready to install - commandRegistry.registerCommand(Electron.UpdateAvailable, (...args: any[]) => { - // TODO: Show a notification - console.log('Update available', ...args); - }); + commandRegistry.registerCommand( + Electron.UpdateAvailable, + (...args: any[]) => { + // TODO: Show a notification + // eslint-disable-next-line no-console + console.log('Update available', ...args); + } + ); // --------------------------------------------------------------------------- // Application is up to date commandRegistry.registerCommand(Electron.UpdateNotAvailable, () => { // TODO: Show a notification + // eslint-disable-next-line no-console console.log('Application is up to date'); }); diff --git a/packages/app/client/src/commands/emulatorCommands.spec.ts b/packages/app/client/src/commands/emulatorCommands.spec.ts index 29640d150..1d518391d 100644 --- a/packages/app/client/src/commands/emulatorCommands.spec.ts +++ b/packages/app/client/src/commands/emulatorCommands.spec.ts @@ -1,25 +1,59 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { newNotification, SharedConstants } from '@bfemulator/app-shared'; import { CommandRegistryImpl } from '@bfemulator/sdk-shared'; +import { combineReducers, createStore } from 'redux'; + import { clientAwareSettingsChanged } from '../data/action/clientAwareSettingsActions'; import { beginAdd } from '../data/action/notificationActions'; import { bot } from '../data/reducer/bot'; import { chat } from '../data/reducer/chat'; import { clientAwareSettings } from '../data/reducer/clientAwareSettingsReducer'; import { editor } from '../data/reducer/editor'; -import { combineReducers, createStore } from 'redux'; import { RootState, store } from '../data/store'; import { CommandServiceImpl } from '../platform/commands/commandServiceImpl'; + import { registerCommands } from './emulatorCommands'; const mockEndpoint = { - endpoint: 'https://localhost:8080/api/messages' + endpoint: 'https://localhost:8080/api/messages', }; let mockStore; jest.mock('../data/store', () => ({ get store() { return mockStore; - } + }, })); jest.mock('../ui/dialogs/', () => ({})); @@ -31,17 +65,23 @@ describe('The emulator commands', () => { }); beforeEach(() => { - mockStore = createStore(combineReducers({ bot, chat, clientAwareSettings, editor })); - mockStore.dispatch(clientAwareSettingsChanged({ - users: { currentUserId: '1234' }, - cwd: 'path', - locale: 'en-us', - serverUrl: 'https://localhost' - })); + mockStore = createStore( + combineReducers({ bot, chat, clientAwareSettings, editor }) + ); + mockStore.dispatch( + clientAwareSettingsChanged({ + users: { currentUserId: '1234' }, + cwd: 'path', + locale: 'en-us', + serverUrl: 'https://localhost', + }) + ); }); it('Should open a new emulator tabbed document for an endpoint', () => { - const { handler } = registry.getCommand(SharedConstants.Commands.Emulator.NewLiveChat); + const { handler } = registry.getCommand( + SharedConstants.Commands.Emulator.NewLiveChat + ); const documentId = handler(mockEndpoint, false); const state: RootState = mockStore.getState(); const documentIds = Object.keys(state.chat.chats); @@ -50,18 +90,28 @@ describe('The emulator commands', () => { }); it('should set the active tab of an existing chat', () => { - const { handler } = registry.getCommand(SharedConstants.Commands.Emulator.NewLiveChat); + const { handler } = registry.getCommand( + SharedConstants.Commands.Emulator.NewLiveChat + ); const documentId = handler(mockEndpoint, false); - const secondDocumentId = handler({ endpoint: 'https://localhost:8181/api/messages' }); + const secondDocumentId = handler({ + endpoint: 'https://localhost:8181/api/messages', + }); // At this point we should have 2 open documents // with the second on - expect(mockStore.getState().editor.editors.primary.activeDocumentId).toBe(secondDocumentId); + expect(mockStore.getState().editor.editors.primary.activeDocumentId).toBe( + secondDocumentId + ); handler(mockEndpoint, true); // re-open the original document - expect(mockStore.getState().editor.editors.primary.activeDocumentId).toBe(documentId); + expect(mockStore.getState().editor.editors.primary.activeDocumentId).toBe( + documentId + ); }); it('should open a transcript', () => { - const { handler } = registry.getCommand(SharedConstants.Commands.Emulator.OpenTranscript); + const { handler } = registry.getCommand( + SharedConstants.Commands.Emulator.OpenTranscript + ); const filePath = 'transcript.transcript'; handler(filePath, filePath); @@ -71,28 +121,44 @@ describe('The emulator commands', () => { }); it('Should prompt to open a transcript', async () => { - const { handler } = registry.getCommand(SharedConstants.Commands.Emulator.PromptToOpenTranscript); - const remoteCallSpy = jest.spyOn(CommandServiceImpl, 'remoteCall').mockResolvedValue('transcript.transcript'); + const { handler } = registry.getCommand( + SharedConstants.Commands.Emulator.PromptToOpenTranscript + ); + const remoteCallSpy = jest + .spyOn(CommandServiceImpl, 'remoteCall') + .mockResolvedValue('transcript.transcript'); const callSpy = jest.spyOn(CommandServiceImpl, 'call'); await handler(); - expect(remoteCallSpy).toHaveBeenCalledWith('shell:showExplorer-open-dialog', { - 'buttonLabel': 'Choose file', - 'filters': [{ 'extensions': ['transcript'], 'name': 'Transcript Files' }], - 'properties': ['openFile'], - 'title': 'Open transcript file' - }); + expect(remoteCallSpy).toHaveBeenCalledWith( + 'shell:showExplorer-open-dialog', + { + buttonLabel: 'Choose file', + filters: [{ extensions: ['transcript'], name: 'Transcript Files' }], + properties: ['openFile'], + title: 'Open transcript file', + } + ); - expect(callSpy).toHaveBeenCalledWith('transcript:open', 'transcript.transcript'); + expect(callSpy).toHaveBeenCalledWith( + 'transcript:open', + 'transcript.transcript' + ); }); it('should dispatch a notification when opening a transcript fails', async () => { - const { handler } = registry.getCommand(SharedConstants.Commands.Emulator.PromptToOpenTranscript); - const remoteCallSpy = jest.spyOn(CommandServiceImpl, 'remoteCall').mockResolvedValue('transcript.transcript'); - const callSpy = jest.spyOn(CommandServiceImpl, 'call').mockImplementationOnce(() => { - throw new Error('Oh noes!'); - }); + const { handler } = registry.getCommand( + SharedConstants.Commands.Emulator.PromptToOpenTranscript + ); + const remoteCallSpy = jest + .spyOn(CommandServiceImpl, 'remoteCall') + .mockResolvedValue('transcript.transcript'); + const callSpy = jest + .spyOn(CommandServiceImpl, 'call') + .mockImplementationOnce(() => { + throw new Error('Oh noes!'); + }); const dispatchSpy = jest.spyOn(mockStore, 'dispatch'); const errMsg = `Error while opening transcript file: Error: Oh noes!`; const notification = newNotification(errMsg); @@ -101,18 +167,24 @@ describe('The emulator commands', () => { action.payload.notification.id = jasmine.any(String) as any; await handler(); expect(remoteCallSpy).toHaveBeenCalled(); - expect(callSpy).toHaveBeenCalledWith('transcript:open', 'transcript.transcript'); + expect(callSpy).toHaveBeenCalledWith( + 'transcript:open', + 'transcript.transcript' + ); expect(dispatchSpy).toHaveBeenCalledWith(action); jest.restoreAllMocks(); }); it('should reload a transcript', async () => { - const { handler: openTranscriptHandler } = - registry.getCommand(SharedConstants.Commands.Emulator.OpenTranscript); + const { handler: openTranscriptHandler } = registry.getCommand( + SharedConstants.Commands.Emulator.OpenTranscript + ); await openTranscriptHandler('transcript.transcript'); let state = mockStore.getState(); expect(state.chat.changeKey).toBe(1); - const { handler } = registry.getCommand(SharedConstants.Commands.Emulator.ReloadTranscript); + const { handler } = registry.getCommand( + SharedConstants.Commands.Emulator.ReloadTranscript + ); await handler('transcript.transcript'); state = mockStore.getState(); expect(state.chat.changeKey).toBe(3); diff --git a/packages/app/client/src/commands/emulatorCommands.ts b/packages/app/client/src/commands/emulatorCommands.ts index 5174ae431..221860fa5 100644 --- a/packages/app/client/src/commands/emulatorCommands.ts +++ b/packages/app/client/src/commands/emulatorCommands.ts @@ -32,8 +32,13 @@ // import { newNotification, SharedConstants } from '@bfemulator/app-shared'; -import { Activity, CommandRegistryImpl, uniqueId } from '@bfemulator/sdk-shared'; +import { + Activity, + CommandRegistryImpl, + uniqueId, +} from '@bfemulator/sdk-shared'; import { IEndpointService } from 'botframework-config/lib/schema'; + import * as Constants from '../constants'; import * as ChatActions from '../data/action/chatActions'; import * as EditorActions from '../data/action/editorActions'; @@ -48,66 +53,71 @@ export function registerCommands(commandRegistry: CommandRegistryImpl) { // --------------------------------------------------------------------------- // Open a new emulator tabbed document - commandRegistry.registerCommand(Emulator.NewLiveChat, + commandRegistry.registerCommand( + Emulator.NewLiveChat, (endpoint: IEndpointService, focusExistingChat: boolean = false) => { const state = store.getState(); let documentId: string; if (focusExistingChat && state.chat.chats) { const { chats } = state.chat; - documentId = Object.keys(chats) - .find((docId) => chats[docId].endpointUrl === endpoint.endpoint); + documentId = Object.keys(chats).find( + docId => chats[docId].endpointUrl === endpoint.endpoint + ); } if (!documentId) { documentId = uniqueId(); const { currentUserId } = state.clientAwareSettings.users; - store.dispatch(ChatActions.newDocument( - documentId, - 'livechat', - { + store.dispatch( + ChatActions.newDocument(documentId, 'livechat', { botId: 'bot', endpointId: endpoint.id, endpointUrl: endpoint.endpoint, - userId: currentUserId - } - )); + userId: currentUserId, + }) + ); } - store.dispatch(EditorActions.open({ - contentType: Constants.CONTENT_TYPE_LIVE_CHAT, - documentId, - isGlobal: false - })); + store.dispatch( + EditorActions.open({ + contentType: Constants.CONTENT_TYPE_LIVE_CHAT, + documentId, + isGlobal: false, + }) + ); return documentId; - }); + } + ); // --------------------------------------------------------------------------- // Open the transcript file in a tabbed document - commandRegistry.registerCommand(Emulator.OpenTranscript, + commandRegistry.registerCommand( + Emulator.OpenTranscript, (filePath: string, fileName: string, additionalData?: object) => { const tabGroup = getTabGroupForDocument(filePath); const { currentUserId } = store.getState().clientAwareSettings.users; if (!tabGroup) { - store.dispatch(ChatActions.newDocument( - filePath, - 'transcript', - { + store.dispatch( + ChatActions.newDocument(filePath, 'transcript', { ...additionalData, botId: 'bot', - userId: currentUserId - } - )); + userId: currentUserId, + }) + ); } - store.dispatch(EditorActions.open({ - contentType: Constants.CONTENT_TYPE_TRANSCRIPT, - documentId: filePath, - fileName, - filePath, - isGlobal: false - })); - }); + store.dispatch( + EditorActions.open({ + contentType: Constants.CONTENT_TYPE_TRANSCRIPT, + documentId: filePath, + fileName, + filePath, + isGlobal: false, + }) + ); + } + ); // --------------------------------------------------------------------------- // Prompt to open a transcript file, then open it @@ -119,13 +129,16 @@ export function registerCommands(commandRegistry: CommandRegistryImpl) { filters: [ { name: 'Transcript Files', - extensions: ['transcript'] - } + extensions: ['transcript'], + }, ], }; try { const { ShowOpenDialog } = SharedConstants.Commands.Electron; - const filename = await CommandServiceImpl.remoteCall(ShowOpenDialog, dialogOptions); + const filename = await CommandServiceImpl.remoteCall( + ShowOpenDialog, + dialogOptions + ); await CommandServiceImpl.call(Emulator.OpenTranscript, filename); } catch (e) { const errMsg = `Error while opening transcript file: ${e}`; @@ -136,48 +149,75 @@ export function registerCommands(commandRegistry: CommandRegistryImpl) { // --------------------------------------------------------------------------- // Same as open transcript, except that it closes the transcript first, before reopening it - commandRegistry.registerCommand(Emulator.ReloadTranscript, + commandRegistry.registerCommand( + Emulator.ReloadTranscript, (filePath: string, fileName: string, additionalData?: object) => { const tabGroup = getTabGroupForDocument(filePath); const { currentUserId } = store.getState().clientAwareSettings.users; if (tabGroup) { - store.dispatch(EditorActions.close(getTabGroupForDocument(filePath), filePath)); + store.dispatch( + EditorActions.close(getTabGroupForDocument(filePath), filePath) + ); store.dispatch(ChatActions.closeDocument(filePath)); } - store.dispatch(ChatActions.newDocument( - filePath, - 'transcript', - { + store.dispatch( + ChatActions.newDocument(filePath, 'transcript', { ...additionalData, botId: 'bot', - userId: currentUserId - } - )); - store.dispatch(EditorActions.open({ - contentType: Constants.CONTENT_TYPE_TRANSCRIPT, - documentId: filePath, - filePath, - fileName, - isGlobal: false - })); - }); + userId: currentUserId, + }) + ); + store.dispatch( + EditorActions.open({ + contentType: Constants.CONTENT_TYPE_TRANSCRIPT, + documentId: filePath, + filePath, + fileName, + isGlobal: false, + }) + ); + } + ); // --------------------------------------------------------------------------- // Open the chat file in a tabbed document as a transcript - commandRegistry.registerCommand(Emulator.OpenChatFile, async (filePath: string, reload?: boolean) => { - try { - // wait for the main side to use the chatdown library to parse the activities (transcript) out of the .chat file - const { activities, fileName }: { activities: Activity[], fileName: string } - = await CommandServiceImpl.remoteCall(Emulator.OpenChatFile, filePath); + commandRegistry.registerCommand( + Emulator.OpenChatFile, + async (filePath: string, reload?: boolean) => { + try { + // wait for the main side to use the chatdown library to parse the activities (transcript) out of the .chat file + const { + activities, + fileName, + }: { + activities: Activity[]; + fileName: string; + } = await CommandServiceImpl.remoteCall( + Emulator.OpenChatFile, + filePath + ); - // open or reload the transcript - if (reload) { - await CommandServiceImpl.call(Emulator.ReloadTranscript, filePath, fileName, { activities, inMemory: true }); - } else { - await CommandServiceImpl.call(Emulator.OpenTranscript, filePath, fileName, { activities, inMemory: true }); + // open or reload the transcript + if (reload) { + await CommandServiceImpl.call( + Emulator.ReloadTranscript, + filePath, + fileName, + { activities, inMemory: true } + ); + } else { + await CommandServiceImpl.call( + Emulator.OpenTranscript, + filePath, + fileName, + { activities, inMemory: true } + ); + } + } catch (err) { + throw new Error( + `Error while retrieving activities from main side: ${err}` + ); } - } catch (err) { - throw new Error(`Error while retrieving activities from main side: ${err}`); } - }); + ); } diff --git a/packages/app/client/src/commands/fileCommands.ts b/packages/app/client/src/commands/fileCommands.ts index 4ec7de27d..ff25a7437 100644 --- a/packages/app/client/src/commands/fileCommands.ts +++ b/packages/app/client/src/commands/fileCommands.ts @@ -31,24 +31,29 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { store } from '../data/store'; -import * as FileActions from '../data/action/fileActions'; -import * as EditorActions from '../data/action/editorActions'; +import { + isChatFile, + isTranscriptFile, + SharedConstants, +} from '@bfemulator/app-shared'; import { CommandRegistryImpl } from '@bfemulator/sdk-shared'; -import { SharedConstants, isChatFile, isTranscriptFile } from '@bfemulator/app-shared'; + +import * as EditorActions from '../data/action/editorActions'; +import * as FileActions from '../data/action/fileActions'; +import { store } from '../data/store'; /** Registers file commands */ export function registerCommands(commandRegistry: CommandRegistryImpl) { - const {File} = SharedConstants.Commands; + const { File } = SharedConstants.Commands; // --------------------------------------------------------------------------- // Adds a file to the file store - commandRegistry.registerCommand(File.Add, (payload) => { + commandRegistry.registerCommand(File.Add, payload => { store.dispatch(FileActions.addFile(payload)); }); // --------------------------------------------------------------------------- // Removes a file from the file store - commandRegistry.registerCommand(File.Remove, (path) => { + commandRegistry.registerCommand(File.Remove, path => { store.dispatch(FileActions.removeFile(path)); }); diff --git a/packages/app/client/src/commands/miscCommands.ts b/packages/app/client/src/commands/miscCommands.ts index e247863eb..b72c5e6ee 100644 --- a/packages/app/client/src/commands/miscCommands.ts +++ b/packages/app/client/src/commands/miscCommands.ts @@ -31,9 +31,10 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { store } from '../data/store'; -import { CommandRegistryImpl } from '@bfemulator/sdk-shared'; import { SharedConstants } from '@bfemulator/app-shared'; +import { CommandRegistryImpl } from '@bfemulator/sdk-shared'; + +import { store } from '../data/store'; /** Registers miscellaneous commands */ export function registerCommands(commandRegistry: CommandRegistryImpl) { diff --git a/packages/app/client/src/commands/notificationCommands.ts b/packages/app/client/src/commands/notificationCommands.ts index d504a61cd..975157cb8 100644 --- a/packages/app/client/src/commands/notificationCommands.ts +++ b/packages/app/client/src/commands/notificationCommands.ts @@ -31,12 +31,13 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { store } from '../data/store'; -import { CommandRegistryImpl } from '@bfemulator/sdk-shared'; import { SharedConstants } from '@bfemulator/app-shared'; import { Notification } from '@bfemulator/app-shared'; -import { getGlobal } from '../utils'; +import { CommandRegistryImpl } from '@bfemulator/sdk-shared'; + import * as NotificationActions from '../data/action/notificationActions'; +import { store } from '../data/store'; +import { getGlobal } from '../utils'; /** Registers notification commands */ export function registerCommands(commandRegistry: CommandRegistryImpl) { @@ -44,7 +45,9 @@ export function registerCommands(commandRegistry: CommandRegistryImpl) { // --------------------------------------------------------------------------- // Adds a notification from the main side to the store / notification manager commandRegistry.registerCommand(Commands.Add, () => { - const notification: Notification = getGlobal(SharedConstants.NOTIFICATION_FROM_MAIN); + const notification: Notification = getGlobal( + SharedConstants.NOTIFICATION_FROM_MAIN + ); store.dispatch(NotificationActions.beginAdd(notification)); }); diff --git a/packages/app/client/src/commands/registerAllCommands.ts b/packages/app/client/src/commands/registerAllCommands.ts index d3c432565..af22c3b6e 100644 --- a/packages/app/client/src/commands/registerAllCommands.ts +++ b/packages/app/client/src/commands/registerAllCommands.ts @@ -31,18 +31,19 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +import { CommandRegistryImpl } from '@bfemulator/sdk-shared'; + import { ExtensionManager } from '../extensions'; import * as LogService from '../platform/log/logService'; + import { registerCommands as registerBotCommands } from './botCommands'; import { registerCommands as registerElectronCommands } from './electronCommands'; import { registerCommands as registerEmulatorCommands } from './emulatorCommands'; import { registerCommands as registerFileCommands } from './fileCommands'; import { registerCommands as registerMiscCommands } from './miscCommands'; import { registerCommands as registerNotificationCommands } from './notificationCommands'; -import { registerCommands as registerUICommands } from './uiCommands'; import { registerCommands as registerSettingsCommand } from './settingsCommands'; - -import { CommandRegistryImpl } from '@bfemulator/sdk-shared'; +import { registerCommands as registerUICommands } from './uiCommands'; /** Registers all commands */ export function registerAllCommands(commandRegistry: CommandRegistryImpl) { diff --git a/packages/app/client/src/commands/settingsCommands.spec.ts b/packages/app/client/src/commands/settingsCommands.spec.ts index 8387e560a..f3d14b3bb 100644 --- a/packages/app/client/src/commands/settingsCommands.spec.ts +++ b/packages/app/client/src/commands/settingsCommands.spec.ts @@ -1,16 +1,51 @@ -import { registerCommands } from './settingsCommands'; +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + import { CommandRegistryImpl } from '@bfemulator/sdk-shared'; import { SharedConstants } from '@bfemulator/app-shared'; -import { clientAwareSettings } from '../data/reducer/clientAwareSettingsReducer'; import { combineReducers, createStore } from 'redux'; + +import { clientAwareSettings } from '../data/reducer/clientAwareSettingsReducer'; import { store } from '../data/store'; import { clientAwareSettingsChanged } from '../data/action/clientAwareSettingsActions'; -let mockStore = createStore(combineReducers({ clientAwareSettings })); +import { registerCommands } from './settingsCommands'; + +const mockStore = createStore(combineReducers({ clientAwareSettings })); jest.mock('../data/store', () => ({ get store() { return mockStore; - } + }, })); describe('the settings commands', () => { @@ -21,9 +56,13 @@ describe('the settings commands', () => { }); it('should dispatch to the store when settings are sent from the main side', () => { - const command = registry.getCommand(SharedConstants.Commands.Settings.ReceiveGlobalSettings).handler; + const command = registry.getCommand( + SharedConstants.Commands.Settings.ReceiveGlobalSettings + ).handler; const dispatchSpy = jest.spyOn(store, 'dispatch'); command({}); - expect(dispatchSpy).toHaveBeenCalledWith(clientAwareSettingsChanged({} as any)); + expect(dispatchSpy).toHaveBeenCalledWith( + clientAwareSettingsChanged({} as any) + ); }); }); diff --git a/packages/app/client/src/commands/settingsCommands.ts b/packages/app/client/src/commands/settingsCommands.ts index 10d3b4459..1e0437877 100644 --- a/packages/app/client/src/commands/settingsCommands.ts +++ b/packages/app/client/src/commands/settingsCommands.ts @@ -31,16 +31,20 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { CommandRegistryImpl } from '@bfemulator/sdk-shared'; import { ClientAwareSettings, SharedConstants } from '@bfemulator/app-shared'; -import { store } from '../data/store'; +import { CommandRegistryImpl } from '@bfemulator/sdk-shared'; + import { clientAwareSettingsChanged } from '../data/action/clientAwareSettingsActions'; +import { store } from '../data/store'; /** Registers settings commands */ export function registerCommands(commandRegistry: CommandRegistryImpl) { const { Settings } = SharedConstants.Commands; - commandRegistry.registerCommand(Settings.ReceiveGlobalSettings, (settings: ClientAwareSettings) => { - store.dispatch(clientAwareSettingsChanged(settings)); - }); + commandRegistry.registerCommand( + Settings.ReceiveGlobalSettings, + (settings: ClientAwareSettings) => { + store.dispatch(clientAwareSettingsChanged(settings)); + } + ); } diff --git a/packages/app/client/src/commands/uiCommands.spec.ts b/packages/app/client/src/commands/uiCommands.spec.ts index 77a775f73..0fbb1dd42 100644 --- a/packages/app/client/src/commands/uiCommands.spec.ts +++ b/packages/app/client/src/commands/uiCommands.spec.ts @@ -1,31 +1,71 @@ -jest.mock('../ui/dialogs', () => ({ - AzureLoginPromptDialogContainer: class { - }, - AzureLoginSuccessDialogContainer: class { - }, - BotCreationDialog: class { - }, - DialogService: { showDialog: () => Promise.resolve(true) }, - SecretPromptDialog: class { - } - } -)); +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { SharedConstants } from '@bfemulator/app-shared'; import { CommandRegistryImpl } from '@bfemulator/sdk-shared'; -import { CONTENT_TYPE_APP_SETTINGS, DOCUMENT_ID_APP_SETTINGS } from '../constants'; -import { AzureAuthAction, AzureAuthWorkflow, invalidateArmToken } from '../data/action/azureAuthActions'; + +import { + CONTENT_TYPE_APP_SETTINGS, + DOCUMENT_ID_APP_SETTINGS, +} from '../constants'; +import { + AzureAuthAction, + AzureAuthWorkflow, + invalidateArmToken, +} from '../data/action/azureAuthActions'; import { EditorActions, OpenEditorAction } from '../data/action/editorActions'; -import { NavBarActions, SelectNavBarAction } from '../data/action/navBarActions'; +import { + NavBarActions, + SelectNavBarAction, +} from '../data/action/navBarActions'; import * as editorHelpers from '../data/editorHelpers'; import { store } from '../data/store'; import { AzureLoginPromptDialogContainer, AzureLoginSuccessDialogContainer, BotCreationDialog, - DialogService, OpenBotDialogContainer, - SecretPromptDialogContainer + DialogService, + OpenBotDialogContainer, + SecretPromptDialogContainer, } from '../ui/dialogs'; + import { registerCommands } from './uiCommands'; +jest.mock('../ui/dialogs', () => ({ + AzureLoginPromptDialogContainer: class {}, + AzureLoginSuccessDialogContainer: class {}, + BotCreationDialog: class {}, + DialogService: { showDialog: () => Promise.resolve(true) }, + SecretPromptDialog: class {}, +})); const Commands = SharedConstants.Commands.UI; @@ -44,37 +84,45 @@ describe('the uiCommands', () => { it('should call DialogService.showDialog when the ShowBotCreationDialog command is dispatched', async () => { const spy = jest.spyOn(DialogService, 'showDialog'); - const result = await registry.getCommand(Commands.ShowBotCreationDialog).handler(); + const result = await registry + .getCommand(Commands.ShowBotCreationDialog) + .handler(); expect(spy).toHaveBeenCalledWith(BotCreationDialog); expect(result).toBe(true); }); it('should call DialogService.showDialog when the ShowSecretPromptDialog command is dispatched', async () => { const spy = jest.spyOn(DialogService, 'showDialog'); - const result = await registry.getCommand(Commands.ShowSecretPromptDialog).handler(); + const result = await registry + .getCommand(Commands.ShowSecretPromptDialog) + .handler(); expect(spy).toHaveBeenCalledWith(SecretPromptDialogContainer); expect(result).toBe(true); }); it('should call DialogService.showDialog when the ShowOpenBotDialog command is dispatched', async () => { const spy = jest.spyOn(DialogService, 'showDialog'); - const result = await registry.getCommand(Commands.ShowOpenBotDialog).handler(); + const result = await registry + .getCommand(Commands.ShowOpenBotDialog) + .handler(); expect(spy).toHaveBeenCalledWith(OpenBotDialogContainer); expect(result).toBe(true); }); describe('should dispatch the appropriate action to the store', () => { it('when the SwitchNavBarTab command is dispatched', () => { + // eslint-disable-next-line prefer-const let arg: SelectNavBarAction = {} as SelectNavBarAction; - store.dispatch = action => (arg as any) = action; + store.dispatch = action => ((arg as any) = action); registry.getCommand(Commands.SwitchNavBarTab).handler('Do it Nauuuw!'); expect(arg.type).toBe(NavBarActions.select); expect(arg.payload.selection).toBe('Do it Nauuuw!'); }); it('when the ShowAppSettings command is dispatched', () => { + // eslint-disable-next-line prefer-const let arg: OpenEditorAction = {} as OpenEditorAction; - store.dispatch = action => (arg as any) = action; + store.dispatch = action => ((arg as any) = action); registry.getCommand(Commands.ShowAppSettings).handler(); expect(arg.type).toBe(EditorActions.open); expect(arg.payload.contentType).toBe(CONTENT_TYPE_APP_SETTINGS); @@ -83,23 +131,29 @@ describe('the uiCommands', () => { }); it('when the SignInToAzure command is dispatched', async () => { - let arg: AzureAuthAction = {} as AzureAuthAction; - store.dispatch = action => (arg as any) = action; + // eslint-disable-next-line prefer-const + let arg: AzureAuthAction = {} as AzureAuthAction< + AzureAuthWorkflow + >; + store.dispatch = action => ((arg as any) = action); registry.getCommand(Commands.SignInToAzure).handler(); - expect(arg.payload.loginSuccessDialog).toBe(AzureLoginSuccessDialogContainer); + expect(arg.payload.loginSuccessDialog).toBe( + AzureLoginSuccessDialogContainer + ); expect(arg.payload.promptDialog).toBe(AzureLoginPromptDialogContainer); }); it('when the InvalidateArmToken command is dispatched', async () => { + // eslint-disable-next-line prefer-const let arg: AzureAuthAction = {} as AzureAuthAction; - store.dispatch = action => (arg as any) = action; + store.dispatch = action => ((arg as any) = action); registry.getCommand(Commands.InvalidateAzureArmToken).handler(); expect(arg).toEqual(invalidateArmToken()); }); }); it('should set the proper href on the theme tag when the SwitchTheme command is dispatched', () => { - let link = document.createElement('link'); + const link = document.createElement('link'); link.id = 'themeVars'; document.querySelector('head').appendChild(link); registry.getCommand(Commands.SwitchTheme).handler('light', './light.css'); diff --git a/packages/app/client/src/commands/uiCommands.ts b/packages/app/client/src/commands/uiCommands.ts index 2be5cf44a..6f215d207 100644 --- a/packages/app/client/src/commands/uiCommands.ts +++ b/packages/app/client/src/commands/uiCommands.ts @@ -34,11 +34,19 @@ import { SharedConstants } from '@bfemulator/app-shared'; import { CommandRegistry } from '@bfemulator/sdk-shared'; import { ServiceTypes } from 'botframework-config/lib/schema'; + import * as Constants from '../constants'; -import { azureArmTokenDataChanged, beginAzureAuthWorkflow, invalidateArmToken } from '../data/action/azureAuthActions'; +import { + azureArmTokenDataChanged, + beginAzureAuthWorkflow, + invalidateArmToken, +} from '../data/action/azureAuthActions'; import * as EditorActions from '../data/action/editorActions'; import * as NavBarActions from '../data/action/navBarActions'; -import { ProgressIndicatorPayload, updateProgressIndicator } from '../data/action/progressIndicatorActions'; +import { + ProgressIndicatorPayload, + updateProgressIndicator, +} from '../data/action/progressIndicatorActions'; import { switchTheme } from '../data/action/themeActions'; import { showWelcomePage } from '../data/editorHelpers'; import { AzureAuthState } from '../data/reducer/azureAuthReducer'; @@ -54,7 +62,7 @@ import { ProgressIndicatorContainer, SecretPromptDialogContainer, UpdateAvailableDialogContainer, - UpdateUnavailableDialogContainer + UpdateUnavailableDialogContainer, } from '../ui/dialogs'; /** Register UI commands (toggling UI) */ @@ -87,47 +95,72 @@ export function registerCommands(commandRegistry: CommandRegistry) { // --------------------------------------------------------------------------- // Switches navbar tab selection - commandRegistry.registerCommand(UI.SwitchNavBarTab, (tabName: string): void => { - store.dispatch(NavBarActions.select(tabName)); - }); + commandRegistry.registerCommand( + UI.SwitchNavBarTab, + (tabName: string): void => { + store.dispatch(NavBarActions.select(tabName)); + } + ); // --------------------------------------------------------------------------- // Open App Settings - commandRegistry.registerCommand(UI.ShowAppSettings, (): void => { - const { CONTENT_TYPE_APP_SETTINGS, DOCUMENT_ID_APP_SETTINGS } = Constants; - store.dispatch(EditorActions.open({ - contentType: CONTENT_TYPE_APP_SETTINGS, - documentId: DOCUMENT_ID_APP_SETTINGS, - isGlobal: true, - meta: null - })); - }); + commandRegistry.registerCommand( + UI.ShowAppSettings, + (): void => { + const { CONTENT_TYPE_APP_SETTINGS, DOCUMENT_ID_APP_SETTINGS } = Constants; + store.dispatch( + EditorActions.open({ + contentType: CONTENT_TYPE_APP_SETTINGS, + documentId: DOCUMENT_ID_APP_SETTINGS, + isGlobal: true, + meta: null, + }) + ); + } + ); // --------------------------------------------------------------------------- // Theme switching from main - commandRegistry.registerCommand(UI.SwitchTheme, (themeName: string, themeHref: string) => { - const linkTags = document.querySelectorAll('[data-theme-component="true"]'); - const themeTag = document.getElementById('themeVars') as HTMLLinkElement; - if (themeTag) { - themeTag.href = themeHref; + commandRegistry.registerCommand( + UI.SwitchTheme, + (themeName: string, themeHref: string) => { + const linkTags = document.querySelectorAll( + '[data-theme-component="true"]' + ); + const themeTag = document.getElementById('themeVars') as HTMLLinkElement; + if (themeTag) { + themeTag.href = themeHref; + } + const themeComponents = Array.prototype.map.call( + linkTags, + link => link.href + ); // href is fully qualified + store.dispatch(switchTheme(themeName, themeComponents)); } - const themeComponents = Array.prototype.map.call(linkTags, link => link.href); // href is fully qualified - store.dispatch(switchTheme(themeName, themeComponents)); - }); + ); // --------------------------------------------------------------------------- // Azure sign in - commandRegistry.registerCommand(UI.SignInToAzure, (serviceType: ServiceTypes) => { - store.dispatch(beginAzureAuthWorkflow( - AzureLoginPromptDialogContainer, - { serviceType }, - AzureLoginSuccessDialogContainer, - AzureLoginFailedDialogContainer)); - }); + commandRegistry.registerCommand( + UI.SignInToAzure, + (serviceType: ServiceTypes) => { + store.dispatch( + beginAzureAuthWorkflow( + AzureLoginPromptDialogContainer, + { serviceType }, + AzureLoginSuccessDialogContainer, + AzureLoginFailedDialogContainer + ) + ); + } + ); - commandRegistry.registerCommand(UI.ArmTokenReceivedOnStartup, (azureAuth: AzureAuthState) => { - store.dispatch(azureArmTokenDataChanged(azureAuth.access_token)); - }); + commandRegistry.registerCommand( + UI.ArmTokenReceivedOnStartup, + (azureAuth: AzureAuthState) => { + store.dispatch(azureArmTokenDataChanged(azureAuth.access_token)); + } + ); commandRegistry.registerCommand(UI.InvalidateAzureArmToken, () => { store.dispatch(invalidateArmToken()); @@ -141,25 +174,44 @@ export function registerCommands(commandRegistry: CommandRegistry) { // --------------------------------------------------------------------------- // Shows the progress indicator component - commandRegistry.registerCommand(UI.ShowProgressIndicator, async (props?: ProgressIndicatorPayload) => { - return await DialogService.showDialog(ProgressIndicatorContainer, props).catch(e => console.error(e)); - }); + commandRegistry.registerCommand( + UI.ShowProgressIndicator, + async (props?: ProgressIndicatorPayload) => { + return await DialogService.showDialog( + ProgressIndicatorContainer, + props + // eslint-disable-next-line no-console + ).catch(e => console.error(e)); + } + ); // --------------------------------------------------------------------------- // Updates the progress of the progress indicator component - commandRegistry.registerCommand(UI.UpdateProgressIndicator, (value: ProgressIndicatorPayload) => { - store.dispatch(updateProgressIndicator(value)); - }); + commandRegistry.registerCommand( + UI.UpdateProgressIndicator, + (value: ProgressIndicatorPayload) => { + store.dispatch(updateProgressIndicator(value)); + } + ); // --------------------------------------------------------------------------- // Shows the dialog telling the user that an update is available - commandRegistry.registerCommand(UI.ShowUpdateAvailableDialog, async (version: string = '') => { - return await DialogService.showDialog(UpdateAvailableDialogContainer, { version }).catch(e => console.error(e)); - }); + commandRegistry.registerCommand( + UI.ShowUpdateAvailableDialog, + async (version: string = '') => { + return await DialogService.showDialog(UpdateAvailableDialogContainer, { + version, + // eslint-disable-next-line no-console + }).catch(e => console.error(e)); + } + ); // --------------------------------------------------------------------------- // Shows the dialog telling the user that an update is unavailable commandRegistry.registerCommand(UI.ShowUpdateUnavailableDialog, async () => { - return await DialogService.showDialog(UpdateUnavailableDialogContainer).catch(e => console.error(e)); + return await DialogService.showDialog( + UpdateUnavailableDialogContainer + // eslint-disable-next-line no-console + ).catch(e => console.error(e)); }); } diff --git a/packages/app/client/src/constants.ts b/packages/app/client/src/constants.ts index fcab78ad0..85d5e9532 100644 --- a/packages/app/client/src/constants.ts +++ b/packages/app/client/src/constants.ts @@ -33,10 +33,14 @@ import { SharedConstants } from '@bfemulator/app-shared'; -export const CONTENT_TYPE_APP_SETTINGS = 'application/vnd.microsoft.bfemulator.document.appsettings'; -export const CONTENT_TYPE_WELCOME_PAGE = 'application/vnd.microsoft.bfemulator.document.welcome'; -export const CONTENT_TYPE_TRANSCRIPT = 'application/vnd.microsoft.bfemulator.document.transcript'; -export const CONTENT_TYPE_LIVE_CHAT = SharedConstants.ContentTypes.CONTENT_TYPE_LIVE_CHAT; +export const CONTENT_TYPE_APP_SETTINGS = + 'application/vnd.microsoft.bfemulator.document.appsettings'; +export const CONTENT_TYPE_WELCOME_PAGE = + 'application/vnd.microsoft.bfemulator.document.welcome'; +export const CONTENT_TYPE_TRANSCRIPT = + 'application/vnd.microsoft.bfemulator.document.transcript'; +export const CONTENT_TYPE_LIVE_CHAT = + SharedConstants.ContentTypes.CONTENT_TYPE_LIVE_CHAT; export const NAVBAR_BOT_EXPLORER = 'navbar.botExplorer'; export const NAVBAR_SETTINGS = 'navbar.settings'; @@ -46,10 +50,7 @@ export const NAVBAR_RESOURCES = 'navbar:resources'; export const EDITOR_KEY_PRIMARY = 'primary'; export const EDITOR_KEY_SECONDARY = 'secondary'; -export const EditorKeys = [ - EDITOR_KEY_PRIMARY, - EDITOR_KEY_SECONDARY -]; +export const EditorKeys = [EDITOR_KEY_PRIMARY, EDITOR_KEY_SECONDARY]; export const DOCUMENT_ID_APP_SETTINGS = 'app:settings'; export const DOCUMENT_ID_BOT_SETTINGS = 'bot:settings'; diff --git a/packages/app/client/src/data/action/azureAuthActions.ts b/packages/app/client/src/data/action/azureAuthActions.ts index 1cfbd7c60..245b7c9a9 100644 --- a/packages/app/client/src/data/action/azureAuthActions.ts +++ b/packages/app/client/src/data/action/azureAuthActions.ts @@ -31,8 +31,8 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { Action } from 'redux'; import { ComponentClass } from 'react'; +import { Action } from 'redux'; export const AZURE_ARM_TOKEN_DATA_CHANGED = 'AZURE_ARM_TOKEN_DATA_CHANGED'; export const AZURE_BEGIN_AUTH_WORKFLOW = 'AZURE_BEGIN_AUTH_WORKFLOW'; @@ -61,19 +61,27 @@ export function beginAzureAuthWorkflow( ): AzureAuthAction { return { type: AZURE_BEGIN_AUTH_WORKFLOW, - payload: { promptDialog, promptDialogProps, loginSuccessDialog, loginFailedDialog } + payload: { + promptDialog, + promptDialogProps, + loginSuccessDialog, + loginFailedDialog, + }, }; } -export function azureArmTokenDataChanged(armToken: string): AzureAuthAction { +export function azureArmTokenDataChanged( + armToken: string +): AzureAuthAction { return { type: AZURE_ARM_TOKEN_DATA_CHANGED, - payload: { access_token: armToken } + // eslint-disable-next-line typescript/camelcase + payload: { access_token: armToken }, }; } export function invalidateArmToken(): AzureAuthAction { return { - type: AZURE_INVALIDATE_ARM_TOKEN + type: AZURE_INVALIDATE_ARM_TOKEN, }; } diff --git a/packages/app/client/src/data/action/botActions.ts b/packages/app/client/src/data/action/botActions.ts index c1a16fba4..4a21d4080 100644 --- a/packages/app/client/src/data/action/botActions.ts +++ b/packages/app/client/src/data/action/botActions.ts @@ -39,20 +39,20 @@ export enum BotActions { setActive = 'BOT/SET_ACTIVE', close = 'BOT/CLOSE', browse = 'BOT/BROWSE', - hashGenerated = 'BOT/HASH_GENERATED' + hashGenerated = 'BOT/HASH_GENERATED', } export interface LoadBotAction { type: BotActions.load; payload: { - bots: BotInfo[] + bots: BotInfo[]; }; } export interface SetActiveBotAction { type: BotActions.setActive; payload: { - bot: BotConfigWithPath + bot: BotConfigWithPath; }; } @@ -72,11 +72,11 @@ export interface BotHashAction { } export type BotAction = - LoadBotAction | - SetActiveBotAction | - CloseBotAction | - BrowseBotAction | - BotHashAction; + | LoadBotAction + | SetActiveBotAction + | CloseBotAction + | BrowseBotAction + | BotHashAction; export function load(bots: BotInfo[]): LoadBotAction { // prune bad bots @@ -85,8 +85,8 @@ export function load(bots: BotInfo[]): LoadBotAction { return { type: BotActions.load, payload: { - bots - } + bots, + }, }; } @@ -98,28 +98,28 @@ export function setActive(bot: BotConfigWithPath): SetActiveBotAction { return { type: BotActions.setActive, payload: { - bot - } + bot, + }, }; } export function close(): CloseBotAction { return { type: BotActions.close, - payload: {} + payload: {}, }; } export function browse(): BrowseBotAction { return { type: BotActions.browse, - payload: {} + payload: {}, }; } export function botHashGenerated(hash: string): BotHashAction { return { type: BotActions.hashGenerated, - payload: { hash } + payload: { hash }, }; } diff --git a/packages/app/client/src/data/action/chatActions.ts b/packages/app/client/src/data/action/chatActions.ts index b8d9cc21e..c0a696c10 100644 --- a/packages/app/client/src/data/action/chatActions.ts +++ b/packages/app/client/src/data/action/chatActions.ts @@ -46,7 +46,7 @@ export enum ChatActions { addTranscript = 'CHAT/TRANSCRIPT/ADD', clearTranscripts = 'CHAT/TRANSCRIPT/CLEAR', removeTranscript = 'CHAT/TRANSCRIPT/REMOVE', - updateChat = 'CHAT/DOCUMENT/UPDATE' + updateChat = 'CHAT/DOCUMENT/UPDATE', } export interface ActiveInspectorChangedPayload { @@ -56,9 +56,9 @@ export interface ActiveInspectorChangedPayload { export interface NewChatAction { type: ChatActions.newChat; payload: { - [propName: string]: any, - documentId: string, - mode: ChatMode + [propName: string]: any; + documentId: string; + mode: ChatMode; }; } @@ -85,8 +85,7 @@ export interface SetInspectorObjectsPayload { objs: any; } -export interface AddTranscriptPayload extends RemoveTranscriptPayload { -} +export interface AddTranscriptPayload extends RemoveTranscriptPayload {} export interface RemoveTranscriptPayload { filename: string; @@ -103,39 +102,49 @@ export interface ChatAction extends Action { type ChatMode = 'livechat' | 'transcript'; -export function inspectorChanged(inspectorWebView: HTMLWebViewElement): ChatAction { +export function inspectorChanged( + inspectorWebView: HTMLWebViewElement +): ChatAction { return { type: ChatActions.activeInspectorChanged, - payload: { inspectorWebView } + payload: { inspectorWebView }, }; } -export function addTranscript(filename: string): ChatAction { +export function addTranscript( + filename: string +): ChatAction { return { type: ChatActions.addTranscript, payload: { - filename - } + filename, + }, }; } export function clearTranscripts(): ChatAction<{}> { return { type: ChatActions.clearTranscripts, - payload: {} + payload: {}, }; } -export function removeTranscript(filename: string): ChatAction { +export function removeTranscript( + filename: string +): ChatAction { return { type: ChatActions.removeTranscript, payload: { - filename - } + filename, + }, }; } -export function newDocument(documentId: string, mode: ChatMode, additionalData?: object): NewChatAction { +export function newDocument( + documentId: string, + mode: ChatMode, + additionalData?: object +): NewChatAction { return { type: ChatActions.newChat, payload: { @@ -144,62 +153,70 @@ export function newDocument(documentId: string, mode: ChatMode, additionalData?: conversationId: null, directLine: null, log: { - entries: [] + entries: [], }, inspectorObjects: [], ui: { horizontalSplitter: [ { absolute: null, - percentage: 50 + percentage: 50, }, { absolute: null, - percentage: 50 - } + percentage: 50, + }, ], verticalSplitter: [ { absolute: null, - percentage: 50 + percentage: 50, }, { absolute: null, - percentage: 50 - } + percentage: 50, + }, ], }, - ...additionalData - } + ...additionalData, + }, }; } -export function closeDocument(documentId: string): ChatAction { +export function closeDocument( + documentId: string +): ChatAction { return { type: ChatActions.closeChat, payload: { documentId, - } + }, }; } -export function newConversation(documentId: string, options: any): ChatAction { +export function newConversation( + documentId: string, + options: any +): ChatAction { return { type: ChatActions.newConversation, payload: { documentId, - options - } + options, + }, }; } -export function appendToLog(documentId: string, entry: LogEntry): ChatAction { +export function appendToLog( + documentId: string, + entry: LogEntry +): ChatAction { return { type: ChatActions.appendLog, payload: { documentId, - entry - } + entry, + }, }; } @@ -208,27 +225,33 @@ export function clearLog(documentId: string): ChatAction { type: ChatActions.clearLog, payload: { documentId, - } + }, }; } -export function setInspectorObjects(documentId: string, objs: any): ChatAction { +export function setInspectorObjects( + documentId: string, + objs: any +): ChatAction { objs = Array.isArray(objs) ? objs : [objs]; return { type: ChatActions.setInspectorObjects, payload: { documentId, - objs - } + objs, + }, }; } -export function updateChat(documentId: string, updatedValues: any): ChatAction { +export function updateChat( + documentId: string, + updatedValues: any +): ChatAction { return { type: ChatActions.updateChat, payload: { documentId, - updatedValues - } + updatedValues, + }, }; } diff --git a/packages/app/client/src/data/action/clientAwareSettingsActions.ts b/packages/app/client/src/data/action/clientAwareSettingsActions.ts index ced944c32..825e478e7 100644 --- a/packages/app/client/src/data/action/clientAwareSettingsActions.ts +++ b/packages/app/client/src/data/action/clientAwareSettingsActions.ts @@ -1,5 +1,37 @@ -import { Action } from 'redux'; +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { ClientAwareSettings } from '@bfemulator/app-shared'; +import { Action } from 'redux'; export const CLIENT_AWARE_SETTINGS_CHANGED = 'CLIENT_AWARE_SETTINGS_CHANGED'; @@ -10,9 +42,11 @@ export interface ClientAwareSettingsActions extends Action { payload: ClientAwareSettings; } -export function clientAwareSettingsChanged(settings: ClientAwareSettings): ClientAwareSettingsActions { +export function clientAwareSettingsChanged( + settings: ClientAwareSettings +): ClientAwareSettingsActions { return { type: CLIENT_AWARE_SETTINGS_CHANGED, - payload: settings + payload: settings, }; } diff --git a/packages/app/client/src/data/action/connectedServiceActions.ts b/packages/app/client/src/data/action/connectedServiceActions.ts index e5b775aa2..789e030c4 100644 --- a/packages/app/client/src/data/action/connectedServiceActions.ts +++ b/packages/app/client/src/data/action/connectedServiceActions.ts @@ -31,17 +31,26 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { IConnectedService, ServiceTypes } from 'botframework-config/lib/schema'; +import { + IConnectedService, + ServiceTypes, +} from 'botframework-config/lib/schema'; import { ComponentClass } from 'react'; import { Action } from 'redux'; + import { CONNECTED_SERVICES_PANEL_ID } from './explorerActions'; export const OPEN_SERVICE_DEEP_LINK = 'OPEN_SERVICE_DEEP_LINK'; -export const OPEN_CONTEXT_MENU_FOR_CONNECTED_SERVICE = 'OPEN_CONTEXT_MENU_FOR_CONNECTED_SERVICE'; -export const OPEN_ADD_CONNECTED_SERVICE_CONTEXT_MENU = 'OPEN_ADD_CONNECTED_SERVICE_CONTEXT_MENU'; -export const OPEN_CONNECTED_SERVICE_SORT_CONTEXT_MENU = 'OPEN_CONNECTED_SERVICE_SORT_CONTEXT_MENU'; -export const LAUNCH_CONNECTED_SERVICE_EDITOR = 'LAUNCH_CONNECTED_SERVICE_EDITOR'; -export const LAUNCH_CONNECTED_SERVICE_PICKER = 'LAUNCH_CONNECTED_SERVICE_PICKER'; +export const OPEN_CONTEXT_MENU_FOR_CONNECTED_SERVICE = + 'OPEN_CONTEXT_MENU_FOR_CONNECTED_SERVICE'; +export const OPEN_ADD_CONNECTED_SERVICE_CONTEXT_MENU = + 'OPEN_ADD_CONNECTED_SERVICE_CONTEXT_MENU'; +export const OPEN_CONNECTED_SERVICE_SORT_CONTEXT_MENU = + 'OPEN_CONNECTED_SERVICE_SORT_CONTEXT_MENU'; +export const LAUNCH_CONNECTED_SERVICE_EDITOR = + 'LAUNCH_CONNECTED_SERVICE_EDITOR'; +export const LAUNCH_CONNECTED_SERVICE_PICKER = + 'LAUNCH_CONNECTED_SERVICE_PICKER'; export interface ConnectedServiceAction extends Action { payload: T; @@ -57,18 +66,19 @@ export interface ConnectedServicePayload { export function launchConnectedServiceEditor( editorComponent: ComponentClass, - connectedService?: IConnectedService): ConnectedServiceAction { + connectedService?: IConnectedService +): ConnectedServiceAction { return { type: LAUNCH_CONNECTED_SERVICE_EDITOR, - payload: { editorComponent, connectedService } + payload: { editorComponent, connectedService }, }; } export interface ConnectedServicePickerPayload extends ConnectedServicePayload { azureAuthWorkflowComponents: { - promptDialog: ComponentClass, - loginSuccessDialog: ComponentClass, - loginFailedDialog: ComponentClass + promptDialog: ComponentClass; + loginSuccessDialog: ComponentClass; + loginFailedDialog: ComponentClass; }; pickerComponent: ComponentClass; getStartedDialog: ComponentClass; @@ -76,42 +86,48 @@ export interface ConnectedServicePickerPayload extends ConnectedServicePayload { progressIndicatorComponent?: ComponentClass; } -export function launchConnectedServicePicker(payload: ConnectedServicePickerPayload) - : ConnectedServiceAction { +export function launchConnectedServicePicker( + payload: ConnectedServicePickerPayload +): ConnectedServiceAction { return { type: LAUNCH_CONNECTED_SERVICE_PICKER, - payload + payload, }; } -export function openServiceDeepLink(connectedService: IConnectedService) - : ConnectedServiceAction { +export function openServiceDeepLink( + connectedService: IConnectedService +): ConnectedServiceAction { return { type: OPEN_SERVICE_DEEP_LINK, - payload: { connectedService } + payload: { connectedService }, }; } export function openContextMenuForConnectedService( editorComponent: ComponentClass, - connectedService?: IConnectedService): ConnectedServiceAction { + connectedService?: IConnectedService +): ConnectedServiceAction { return { type: OPEN_CONTEXT_MENU_FOR_CONNECTED_SERVICE, - payload: { editorComponent, connectedService } + payload: { editorComponent, connectedService }, }; } -export function openAddServiceContextMenu(payload: ConnectedServicePickerPayload) - : ConnectedServiceAction { +export function openAddServiceContextMenu( + payload: ConnectedServicePickerPayload +): ConnectedServiceAction { return { type: OPEN_ADD_CONNECTED_SERVICE_CONTEXT_MENU, - payload + payload, }; } -export function openSortContextMenu(): ConnectedServiceAction { +export function openSortContextMenu(): ConnectedServiceAction< + ConnectedServicePayload +> { return { type: OPEN_CONNECTED_SERVICE_SORT_CONTEXT_MENU, - payload: { panelId: CONNECTED_SERVICES_PANEL_ID } + payload: { panelId: CONNECTED_SERVICES_PANEL_ID }, }; } diff --git a/packages/app/client/src/data/action/dialogActions.ts b/packages/app/client/src/data/action/dialogActions.ts index 440675233..b076a5cd2 100644 --- a/packages/app/client/src/data/action/dialogActions.ts +++ b/packages/app/client/src/data/action/dialogActions.ts @@ -32,13 +32,13 @@ // export enum DialogActions { - setShowing = 'DIALOG/SET_SHOWING' + setShowing = 'DIALOG/SET_SHOWING', } export interface SetShowingDialogAction { type: DialogActions.setShowing; payload: { - showing: boolean + showing: boolean; }; } @@ -48,7 +48,7 @@ export function setShowing(showing: boolean = false): SetShowingDialogAction { return { type: DialogActions.setShowing, payload: { - showing - } + showing, + }, }; } diff --git a/packages/app/client/src/data/action/dispatchActions.ts b/packages/app/client/src/data/action/dispatchActions.ts index 2cfb2951d..68675c52d 100644 --- a/packages/app/client/src/data/action/dispatchActions.ts +++ b/packages/app/client/src/data/action/dispatchActions.ts @@ -35,7 +35,8 @@ import { IDispatchService } from 'botframework-config/lib/schema'; import { Action } from 'redux'; export const OPEN_DISPATCH_DEEP_LINK = 'OPEN_DISPATCH_DEEP_LINK'; -export const OPEN_DISPATCH_EXPLORER_CONTEXT_MENU = 'OPEN_DISPATCH_EXPLORER_CONTEXT_MENU'; +export const OPEN_DISPATCH_EXPLORER_CONTEXT_MENU = + 'OPEN_DISPATCH_EXPLORER_CONTEXT_MENU'; export interface DispatchServiceAction extends Action { payload: T; @@ -45,17 +46,20 @@ export interface DispatchServicePayload { dispatchService?: IDispatchService; } -export function openDispatchDeepLink(dispatchService: IDispatchService): DispatchServiceAction { +export function openDispatchDeepLink( + dispatchService: IDispatchService +): DispatchServiceAction { return { type: OPEN_DISPATCH_DEEP_LINK, - payload: { dispatchService } -}; + payload: { dispatchService }, + }; } -export function openDispatchExplorerContextMenu(dispatchService: IDispatchService) - : DispatchServiceAction { +export function openDispatchExplorerContextMenu( + dispatchService: IDispatchService +): DispatchServiceAction { return { type: OPEN_DISPATCH_EXPLORER_CONTEXT_MENU, - payload: { dispatchService } + payload: { dispatchService }, }; } diff --git a/packages/app/client/src/data/action/editorActions.ts b/packages/app/client/src/data/action/editorActions.ts index 3f8d428ca..7f6991bfd 100644 --- a/packages/app/client/src/data/action/editorActions.ts +++ b/packages/app/client/src/data/action/editorActions.ts @@ -47,38 +47,38 @@ export enum EditorActions { splitTab = 'EDITOR/SPLIT_TAB', swapTabs = 'EDITOR/SWAP_TABS', toggleDraggingTab = 'EDITOR/TOGGLE_DRAGGING_TAB', - updateDocument = 'EDITOR/UPDATE_DOCUMENT' + updateDocument = 'EDITOR/UPDATE_DOCUMENT', } export interface AppendTabAction { type: EditorActions.appendTab; payload: { - srcEditorKey: string, - destEditorKey: string, - documentId: string + srcEditorKey: string; + destEditorKey: string; + documentId: string; }; } export interface CloseEditorAction { type: EditorActions.close; payload: { - editorKey: string, - documentId: string + editorKey: string; + documentId: string; }; } export interface CloseAllEditorAction { type: EditorActions.closeAll; payload: { - includeGlobal: boolean + includeGlobal: boolean; }; } export interface SetDirtyFlagAction { type: EditorActions.setDirtyFlag; payload: { - documentId: string - dirty: boolean + documentId: string; + dirty: boolean; }; } @@ -95,41 +95,41 @@ export interface UpdateDocumentAction { export interface SetActiveTabAction { type: EditorActions.setActiveTab; payload: { - documentId: string + documentId: string; }; } export interface SetActiveEditorAction { type: EditorActions.setActiveEditor; payload: { - editorKey: string + editorKey: string; }; } export interface SplitTabAction { type: EditorActions.splitTab; payload: { - contentType: string, - documentId: string, - srcEditorKey: string, - destEditorKey: string + contentType: string; + documentId: string; + srcEditorKey: string; + destEditorKey: string; }; } export interface SwapTabsAction { type: EditorActions.swapTabs; payload: { - srcEditorKey: string, - destEditorKey: string, - srcTabId: string, - destTabId: string + srcEditorKey: string; + destEditorKey: string; + srcTabId: string; + destTabId: string; }; } export interface ToggleDraggingTabAction { type: EditorActions.toggleDraggingTab; payload: { - draggingTab: boolean + draggingTab: boolean; }; } @@ -150,62 +150,73 @@ export interface RemoveDocPendingChangeAction { export interface DropTabOnLeftOverlayAction { type: EditorActions.dropTabOnLeftOverlay; payload: { - tabId: string + tabId: string; }; } export type EditorAction = - AppendTabAction | - CloseEditorAction | - CloseAllEditorAction | - SetDirtyFlagAction | - OpenEditorAction | - UpdateDocumentAction | - SetActiveTabAction | - SetActiveEditorAction | - SplitTabAction | - SwapTabsAction | - ToggleDraggingTabAction | - AddDocPendingChangeAction | - RemoveDocPendingChangeAction | - DropTabOnLeftOverlayAction; + | AppendTabAction + | CloseEditorAction + | CloseAllEditorAction + | SetDirtyFlagAction + | OpenEditorAction + | UpdateDocumentAction + | SetActiveTabAction + | SetActiveEditorAction + | SplitTabAction + | SwapTabsAction + | ToggleDraggingTabAction + | AddDocPendingChangeAction + | RemoveDocPendingChangeAction + | DropTabOnLeftOverlayAction; -export function appendTab(srcEditorKey: string, destEditorKey: string, documentId: string): AppendTabAction { +export function appendTab( + srcEditorKey: string, + destEditorKey: string, + documentId: string +): AppendTabAction { return { type: EditorActions.appendTab, payload: { srcEditorKey, destEditorKey, - documentId - } + documentId, + }, }; } -export function addDocPendingChange(documentId: string): AddDocPendingChangeAction { +export function addDocPendingChange( + documentId: string +): AddDocPendingChangeAction { return { type: EditorActions.addDocPendingChange, payload: { - documentId - } + documentId, + }, }; } -export function removeDocPendingChange(documentId: string): RemoveDocPendingChangeAction { +export function removeDocPendingChange( + documentId: string +): RemoveDocPendingChangeAction { return { type: EditorActions.removeDocPendingChange, payload: { - documentId - } + documentId, + }, }; } -export function close(editorKey: string, documentId: string): CloseEditorAction { +export function close( + editorKey: string, + documentId: string +): CloseEditorAction { return { type: EditorActions.close, payload: { editorKey, - documentId - } + documentId, + }, }; } @@ -213,32 +224,38 @@ export function closeNonGlobalTabs(): CloseAllEditorAction { return { type: EditorActions.closeAll, payload: { - includeGlobal: false - } + includeGlobal: false, + }, }; } -export function setDirtyFlag(documentId: string, dirty: boolean): SetDirtyFlagAction { +export function setDirtyFlag( + documentId: string, + dirty: boolean +): SetDirtyFlagAction { return { type: EditorActions.setDirtyFlag, payload: { documentId, - dirty - } + dirty, + }, }; } export function open(document: Document): OpenEditorAction { return { type: EditorActions.open, - payload: document + payload: document, }; } -export function updateDocument(documentId: string, updatedDocument: Partial): UpdateDocumentAction { +export function updateDocument( + documentId: string, + updatedDocument: Partial +): UpdateDocumentAction { return { type: EditorActions.updateDocument, - payload: { documentId, ...updatedDocument } + payload: { documentId, ...updatedDocument }, }; } @@ -246,8 +263,8 @@ export function setActiveTab(documentId: string): SetActiveTabAction { return { type: EditorActions.setActiveTab, payload: { - documentId - } + documentId, + }, }; } @@ -255,51 +272,63 @@ export function setActiveEditor(editorKey: string): SetActiveEditorAction { return { type: EditorActions.setActiveEditor, payload: { - editorKey - } + editorKey, + }, }; } -export function splitTab(contentType: string, documentId: string, srcEditorKey: string, destEditorKey: string) - : SplitTabAction { +export function splitTab( + contentType: string, + documentId: string, + srcEditorKey: string, + destEditorKey: string +): SplitTabAction { return { type: EditorActions.splitTab, payload: { contentType, documentId, srcEditorKey, - destEditorKey - } + destEditorKey, + }, }; } -export function swapTabs(srcEditorKey: string, destEditorKey: string, srcTabId: string, destTabId: string) - : SwapTabsAction { +export function swapTabs( + srcEditorKey: string, + destEditorKey: string, + srcTabId: string, + destTabId: string +): SwapTabsAction { return { type: EditorActions.swapTabs, payload: { srcEditorKey, destEditorKey, srcTabId, - destTabId - } + destTabId, + }, }; } -export function toggleDraggingTab(draggingTab: boolean): ToggleDraggingTabAction { +export function toggleDraggingTab( + draggingTab: boolean +): ToggleDraggingTabAction { return { type: EditorActions.toggleDraggingTab, payload: { - draggingTab - } + draggingTab, + }, }; } -export function dropTabOnLeftOverlay(tabId: string): DropTabOnLeftOverlayAction { +export function dropTabOnLeftOverlay( + tabId: string +): DropTabOnLeftOverlayAction { return { type: EditorActions.dropTabOnLeftOverlay, payload: { - tabId - } + tabId, + }, }; } diff --git a/packages/app/client/src/data/action/endpointActions.ts b/packages/app/client/src/data/action/endpointActions.ts index 122f81cc7..f7d001d2a 100644 --- a/packages/app/client/src/data/action/endpointActions.ts +++ b/packages/app/client/src/data/action/endpointActions.ts @@ -34,7 +34,8 @@ import { IEndpointService } from 'botframework-config/lib/schema'; import { Action } from 'redux'; -export const OPEN_ENDPOINT_EXPLORER_CONTEXT_MENU = 'OPEN_ENDPOINT_EXPLORER_CONTEXT_MENU'; +export const OPEN_ENDPOINT_EXPLORER_CONTEXT_MENU = + 'OPEN_ENDPOINT_EXPLORER_CONTEXT_MENU'; export interface EndpointServiceAction extends Action { payload: T; @@ -44,10 +45,11 @@ export interface EndpointServicePayload { endpointService?: IEndpointService; } -export function openEndpointExplorerContextMenu(endpointService: IEndpointService) - : EndpointServiceAction { +export function openEndpointExplorerContextMenu( + endpointService: IEndpointService +): EndpointServiceAction { return { type: OPEN_ENDPOINT_EXPLORER_CONTEXT_MENU, - payload: { endpointService } + payload: { endpointService }, }; } diff --git a/packages/app/client/src/data/action/endpointServiceActions.ts b/packages/app/client/src/data/action/endpointServiceActions.ts index 1d64e4e03..10f24f25d 100644 --- a/packages/app/client/src/data/action/endpointServiceActions.ts +++ b/packages/app/client/src/data/action/endpointServiceActions.ts @@ -52,27 +52,32 @@ export interface EndpointEditorPayload extends EndpointServicePayload { endpointEditorComponent?: ComponentClass; } -export function launchEndpointEditor(endpointEditorComponent: ComponentClass, - endpointService?: IEndpointService): EndpointServiceAction { +export function launchEndpointEditor( + endpointEditorComponent: ComponentClass, + endpointService?: IEndpointService +): EndpointServiceAction { return { type: LAUNCH_ENDPOINT_EDITOR, - payload: { endpointEditorComponent, endpointService } + payload: { endpointEditorComponent, endpointService }, }; } -export function openEndpointInEmulator(endpointService: IEndpointService, focusExistingChatIfAvailable: boolean = false) - : EndpointServiceAction { +export function openEndpointInEmulator( + endpointService: IEndpointService, + focusExistingChatIfAvailable: boolean = false +): EndpointServiceAction { return { type: OPEN_ENDPOINT_IN_EMULATOR, - payload: { endpointService, focusExistingChatIfAvailable } + payload: { endpointService, focusExistingChatIfAvailable }, }; } -export function openEndpointExplorerContextMenu(endpointEditorComponent: ComponentClass, - endpointService?: IEndpointService) - : EndpointServiceAction { +export function openEndpointExplorerContextMenu( + endpointEditorComponent: ComponentClass, + endpointService?: IEndpointService +): EndpointServiceAction { return { type: OPEN_ENDPOINT_CONTEXT_MENU, - payload: { endpointEditorComponent, endpointService } + payload: { endpointEditorComponent, endpointService }, }; } diff --git a/packages/app/client/src/data/action/explorerActions.ts b/packages/app/client/src/data/action/explorerActions.ts index 911afebb9..5c7365b8b 100644 --- a/packages/app/client/src/data/action/explorerActions.ts +++ b/packages/app/client/src/data/action/explorerActions.ts @@ -31,12 +31,13 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // import { Action } from 'redux'; + import { SortCriteria } from '../reducer/explorer'; export const CONNECTED_SERVICES_PANEL_ID = 'connectedServices'; export enum ExplorerActions { Show = 'EXPLORER/SHOW', - Sort = 'EXPLORER/SORT' + Sort = 'EXPLORER/SORT', } export interface ExplorerAction extends Action { @@ -52,13 +53,16 @@ export interface ExplorerPayload { export function showExplorer(show: boolean): ExplorerAction { return { type: ExplorerActions.Show, - payload: { show } + payload: { show }, }; } -export function sortExplorerContents(panelId: string, sort: SortCriteria): ExplorerAction { +export function sortExplorerContents( + panelId: string, + sort: SortCriteria +): ExplorerAction { return { type: ExplorerActions.Sort, - payload: { sortSelectionByPanelId: { [panelId]: sort } } + payload: { sortSelectionByPanelId: { [panelId]: sort } }, }; } diff --git a/packages/app/client/src/data/action/fileActions.ts b/packages/app/client/src/data/action/fileActions.ts index 13890cd12..83d0e817a 100644 --- a/packages/app/client/src/data/action/fileActions.ts +++ b/packages/app/client/src/data/action/fileActions.ts @@ -37,33 +37,33 @@ export enum FileActions { setRoot = 'FILE/SET_ROOT', add = 'FILE/ADD', remove = 'FILE/REMOVE', - clear = 'FILE/CLEAR' + clear = 'FILE/CLEAR', } export function addFile(payload: FileInfo) { return { type: FileActions.add, - payload + payload, }; } export function clear() { return { type: FileActions.clear, - payload: {} + payload: {}, }; } export function removeFile(path: string) { return { type: FileActions.remove, - payload: { path } + payload: { path }, }; } export function setRoot(path: string) { return { type: FileActions.setRoot, - payload: { path } + payload: { path }, }; } diff --git a/packages/app/client/src/data/action/navBarActions.ts b/packages/app/client/src/data/action/navBarActions.ts index 1a0bbe4cd..c0bf3e7d3 100644 --- a/packages/app/client/src/data/action/navBarActions.ts +++ b/packages/app/client/src/data/action/navBarActions.ts @@ -32,13 +32,13 @@ // export enum NavBarActions { - select = 'NAVBAR/SELECT' + select = 'NAVBAR/SELECT', } export interface SelectNavBarAction { type: NavBarActions.select; payload: { - selection: string + selection: string; }; } @@ -48,7 +48,7 @@ export function select(selection: string): SelectNavBarAction { return { type: NavBarActions.select, payload: { - selection - } + selection, + }, }; } diff --git a/packages/app/client/src/data/action/notificationActions.ts b/packages/app/client/src/data/action/notificationActions.ts index 1a27aa969..d781b4508 100644 --- a/packages/app/client/src/data/action/notificationActions.ts +++ b/packages/app/client/src/data/action/notificationActions.ts @@ -40,44 +40,44 @@ export enum NotificationActions { finishRemove = 'NOTIFICATION/FINISH_REMOVE', markAllAsRead = 'NOTIFICATION/MARK_ALL_AS_READ', beginClear = 'NOTIFICATION/BEGIN_CLEAR', - finishClear = 'NOTIFICATION/FINISH_CLEAR' + finishClear = 'NOTIFICATION/FINISH_CLEAR', } export type NotificationAction = - BeginAddNotificationAction | - FinishAddNotificationAction | - BeginRemoveNotificationAction | - FinishRemoveNotificationAction | - MarkAllAsReadNotificationAction | - BeginClearNotificationAction | - FinishClearNotificationAction; + | BeginAddNotificationAction + | FinishAddNotificationAction + | BeginRemoveNotificationAction + | FinishRemoveNotificationAction + | MarkAllAsReadNotificationAction + | BeginClearNotificationAction + | FinishClearNotificationAction; export interface BeginAddNotificationAction { type: NotificationActions.beginAdd; payload: { - notification: Notification, - read: boolean + notification: Notification; + read: boolean; }; } export interface FinishAddNotificationAction { type: NotificationActions.finishAdd; payload: { - notification: Notification + notification: Notification; }; } export interface BeginRemoveNotificationAction { type: NotificationActions.beginRemove; payload: { - id: string + id: string; }; } export interface FinishRemoveNotificationAction { type: NotificationActions.finishRemove; payload: { - id: string + id: string; }; } @@ -96,22 +96,27 @@ export interface FinishClearNotificationAction { payload: {}; } -export function beginAdd(notification: Notification, read: boolean = false): BeginAddNotificationAction { +export function beginAdd( + notification: Notification, + read: boolean = false +): BeginAddNotificationAction { return { type: NotificationActions.beginAdd, payload: { notification, - read - } + read, + }, }; } -export function finishAdd(notification: Notification): FinishAddNotificationAction { +export function finishAdd( + notification: Notification +): FinishAddNotificationAction { return { type: NotificationActions.finishAdd, payload: { - notification - } + notification, + }, }; } @@ -119,8 +124,8 @@ export function beginRemove(id: string): BeginRemoveNotificationAction { return { type: NotificationActions.beginRemove, payload: { - id - } + id, + }, }; } @@ -128,28 +133,28 @@ export function finishRemove(id: string): FinishRemoveNotificationAction { return { type: NotificationActions.finishRemove, payload: { - id - } + id, + }, }; } export function markAllAsRead(): MarkAllAsReadNotificationAction { return { type: NotificationActions.markAllAsRead, - payload: {} + payload: {}, }; } export function beginClear(): BeginClearNotificationAction { return { type: NotificationActions.beginClear, - payload: {} + payload: {}, }; } export function finishClear(): FinishClearNotificationAction { return { type: NotificationActions.finishClear, - payload: {} + payload: {}, }; } diff --git a/packages/app/client/src/data/action/presentationActions.ts b/packages/app/client/src/data/action/presentationActions.ts index bc2529ec2..6b0ebbb75 100644 --- a/packages/app/client/src/data/action/presentationActions.ts +++ b/packages/app/client/src/data/action/presentationActions.ts @@ -33,7 +33,7 @@ export enum PresentationActions { disable = 'PRESENTATION/DISABLE', - enable = 'PRESENTATION/ENABLE' + enable = 'PRESENTATION/ENABLE', } export interface EnablePresentationAction { @@ -47,19 +47,19 @@ export interface DisablePresentationAction { } export type PresentationAction = - EnablePresentationAction | - DisablePresentationAction; + | EnablePresentationAction + | DisablePresentationAction; export function enable(): EnablePresentationAction { return { type: PresentationActions.enable, - payload: {} + payload: {}, }; } export function disable(): DisablePresentationAction { return { type: PresentationActions.disable, - payload: {} + payload: {}, }; } diff --git a/packages/app/client/src/data/action/progressIndicatorActions.ts b/packages/app/client/src/data/action/progressIndicatorActions.ts index e01f49c51..28bba9918 100644 --- a/packages/app/client/src/data/action/progressIndicatorActions.ts +++ b/packages/app/client/src/data/action/progressIndicatorActions.ts @@ -1,3 +1,35 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { Action } from 'redux'; export const UPDATE_PROGRESS_INDICATOR = 'UPDATE_PROGRESS_INDICATOR'; @@ -12,17 +44,21 @@ export interface ProgressIndicatorPayload { progress: number; } -export function updateProgressIndicator({ label, progress }: ProgressIndicatorPayload) - : ProgressIndicatorAction { +export function updateProgressIndicator({ + label, + progress, +}: ProgressIndicatorPayload): ProgressIndicatorAction< + ProgressIndicatorPayload +> { return { type: UPDATE_PROGRESS_INDICATOR, - payload: { label, progress } + payload: { label, progress }, }; } export function cancelCurrentProcess(): ProgressIndicatorAction { return { type: CANCEL_CURRENT_PROCESS, - payload: void(0) + payload: void 0, }; } diff --git a/packages/app/client/src/data/action/resourcesAction.ts b/packages/app/client/src/data/action/resourcesAction.ts index c7649aed7..4f14bbdeb 100644 --- a/packages/app/client/src/data/action/resourcesAction.ts +++ b/packages/app/client/src/data/action/resourcesAction.ts @@ -1,6 +1,38 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { IFileService } from 'botframework-config/lib/schema'; -import { Action } from 'redux'; import { ComponentClass } from 'react'; +import { Action } from 'redux'; export const TRANSCRIPTS_UPDATED = 'TRANSCRIPTS_UPDATED'; export const TRANSCRIPTS_DIRECTORY_UPDATED = 'TRANSCRIPTS_DIRECTORY_UPDATED'; @@ -16,67 +48,87 @@ export interface ResourcesAction extends Action { payload: T; } -export function transcriptsUpdated(payload: IFileService[]): ResourcesAction { +export function transcriptsUpdated( + payload: IFileService[] +): ResourcesAction { return { type: TRANSCRIPTS_UPDATED, - payload + payload, }; } -export function transcriptDirectoryUpdated(payload: string): ResourcesAction { +export function transcriptDirectoryUpdated( + payload: string +): ResourcesAction { return { type: TRANSCRIPTS_DIRECTORY_UPDATED, - payload + payload, }; } -export function chatsDirectoryUpdated(payload: string): ResourcesAction { +export function chatsDirectoryUpdated( + payload: string +): ResourcesAction { return { type: CHATS_DIRECTORY_UPDATED, - payload + payload, }; } -export function chatFilesUpdated(payload: IFileService[]): ResourcesAction { +export function chatFilesUpdated( + payload: IFileService[] +): ResourcesAction { return { type: CHAT_FILES_UPDATED, - payload + payload, }; } -export function openContextMenuForResource(payload: IFileService): ResourcesAction { +export function openContextMenuForResource( + payload: IFileService +): ResourcesAction { return { type: OPEN_CONTEXT_MENU_FOR_RESOURCE, - payload + payload, }; } -export function editResource(payload: IFileService): ResourcesAction { +export function editResource( + payload: IFileService +): ResourcesAction { return { type: EDIT_RESOURCE, - payload + payload, }; } -export function renameResource(payload: IFileService): ResourcesAction { +export function renameResource( + payload: IFileService +): ResourcesAction { return { type: RENAME_RESOURCE, - payload + payload, }; } -export function openResource(payload: IFileService): ResourcesAction { +export function openResource( + payload: IFileService +): ResourcesAction { return { type: OPEN_RESOURCE, - payload + payload, }; } -declare type ResourceSettingsPayload = { dialog: ComponentClass }; +declare interface ResourceSettingsPayload { + dialog: ComponentClass; +} -export function openResourcesSettings(payload: ResourceSettingsPayload): ResourcesAction { +export function openResourcesSettings( + payload: ResourceSettingsPayload +): ResourcesAction { return { type: OPEN_RESOURCE_SETTINGS, - payload + payload, }; } diff --git a/packages/app/client/src/data/action/themeActions.ts b/packages/app/client/src/data/action/themeActions.ts index cdceb8e48..ef71b788c 100644 --- a/packages/app/client/src/data/action/themeActions.ts +++ b/packages/app/client/src/data/action/themeActions.ts @@ -1,3 +1,35 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// export const SWITCH_THEME = 'switchTheme'; export declare type ThemeType = 'switchTheme'; @@ -11,9 +43,12 @@ export interface SwitchThemePayload { themeComponents: string[]; } -export function switchTheme(themeName: string, themeComponents: string[]): ThemeAction { +export function switchTheme( + themeName: string, + themeComponents: string[] +): ThemeAction { return { type: SWITCH_THEME, - payload: { themeName, themeComponents } + payload: { themeName, themeComponents }, }; } diff --git a/packages/app/client/src/data/action/welcomePageActions.ts b/packages/app/client/src/data/action/welcomePageActions.ts index ce8feb2c1..f8346cfee 100644 --- a/packages/app/client/src/data/action/welcomePageActions.ts +++ b/packages/app/client/src/data/action/welcomePageActions.ts @@ -1,3 +1,35 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { BotInfo } from '@bfemulator/app-shared'; import { Action } from 'redux'; @@ -7,9 +39,11 @@ export interface WelcomePageAction extends Action { payload: T; } -export function openContextMenuForBot(bot: BotInfo): WelcomePageAction { +export function openContextMenuForBot( + bot: BotInfo +): WelcomePageAction { return { type: OPEN_CONTEXT_MENU_FOR_BOT, - payload: bot + payload: bot, }; } diff --git a/packages/app/client/src/data/botHelpers.spec.ts b/packages/app/client/src/data/botHelpers.spec.ts index 8c511dca3..da0c7f671 100644 --- a/packages/app/client/src/data/botHelpers.spec.ts +++ b/packages/app/client/src/data/botHelpers.spec.ts @@ -31,6 +31,12 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +import { + getActiveBot, + getBotInfoByPath, + pathExistsInRecentBots, +} from './botHelpers'; + jest.mock('./store', () => ({ store: { getState: () => ({ @@ -41,19 +47,15 @@ jest.mock('./store', () => ({ padlock: 'padlock1', services: [], }, - botFiles: [{ - path: 'path1' - }] - } - }) - } + botFiles: [ + { + path: 'path1', + }, + ], + }, + }), + }, })); - -import { - getActiveBot, - getBotInfoByPath, - pathExistsInRecentBots -} from './botHelpers'; describe('Bot helpers tests', () => { it('should get the active bot', () => { const activeBot = getActiveBot(); diff --git a/packages/app/client/src/data/botHelpers.ts b/packages/app/client/src/data/botHelpers.ts index 1dae70640..1f264c0f4 100644 --- a/packages/app/client/src/data/botHelpers.ts +++ b/packages/app/client/src/data/botHelpers.ts @@ -33,6 +33,7 @@ import { BotInfo } from '@bfemulator/app-shared'; import { BotConfigWithPath } from '@bfemulator/sdk-shared'; + import { store } from './store'; export function getActiveBot(): BotConfigWithPath { @@ -42,7 +43,9 @@ export function getActiveBot(): BotConfigWithPath { const encoder = new (window as any).TextEncoder(); const decoder = new (window as any).TextDecoder(); -export const generateBotHash = async (bot: BotConfigWithPath): Promise => { +export const generateBotHash = async ( + bot: BotConfigWithPath +): Promise => { const buffer = encoder.encode(JSON.stringify(bot)); const digest = await window.crypto.subtle.digest('SHA-256', buffer); return btoa(encoder.encode(decoder.decode(digest))); diff --git a/packages/app/client/src/data/chatHelpers.ts b/packages/app/client/src/data/chatHelpers.ts index 8ad14f0e1..b4c8eedb8 100644 --- a/packages/app/client/src/data/chatHelpers.ts +++ b/packages/app/client/src/data/chatHelpers.ts @@ -35,7 +35,7 @@ import { store } from './store'; export function documentIdForConversation(conversationId: string): string { const state = store.getState(); - for (let key in state.chat.chats) { + for (const key in state.chat.chats) { if (state.chat.chats[key].conversationId === conversationId) { return state.chat.chats[key].documentId; } diff --git a/packages/app/client/src/data/debugWebSocketConnection.ts b/packages/app/client/src/data/debugWebSocketConnection.ts index b8b3098cd..b502665de 100644 --- a/packages/app/client/src/data/debugWebSocketConnection.ts +++ b/packages/app/client/src/data/debugWebSocketConnection.ts @@ -39,13 +39,14 @@ class DebugConnection extends EventEmitter { private onopen: any; private onclose: any; - constructor(connection: any) { + public constructor(connection: any) { super(); this._connection = connection; this._connection.onmessage = event => { - console.info(`WS.recv: ${ event.data }`); + // eslint-disable-next-line no-console + console.info(`WS.recv: ${event.data}`); this.emit('message', event); if (this.onmessage) { this.onmessage(event); @@ -53,6 +54,7 @@ class DebugConnection extends EventEmitter { }; this._connection.onopen = () => { + // eslint-disable-next-line no-console console.info(`WS.open`); this.emit('open'); if (this.onopen) { @@ -61,6 +63,7 @@ class DebugConnection extends EventEmitter { }; this._connection.onclose = () => { + // eslint-disable-next-line no-console console.info(`WS.close`); this.emit('close'); if (this.onclose) { @@ -69,16 +72,17 @@ class DebugConnection extends EventEmitter { }; } - close() { + public close() { this._connection.close(); } - end() { + public end() { this._connection.end(); } - send(data: any) { - console.info(`WS.send: ${ data }`); + public send(data: any) { + // eslint-disable-next-line no-console + console.info(`WS.send: ${data}`); this._connection.send(data); } } diff --git a/packages/app/client/src/data/editorHelpers.ts b/packages/app/client/src/data/editorHelpers.ts index 7f3347b42..f0b762825 100644 --- a/packages/app/client/src/data/editorHelpers.ts +++ b/packages/app/client/src/data/editorHelpers.ts @@ -32,14 +32,17 @@ // import * as Constants from '../constants'; + import * as EditorActions from './action/editorActions'; import { Editor } from './reducer/editor'; import { store } from './store'; -export function hasNonGlobalTabs(tabGroups?: { [editorKey: string]: Editor }): number { +export function hasNonGlobalTabs(tabGroups?: { + [editorKey: string]: Editor; +}): number { tabGroups = tabGroups || store.getState().editor.editors; let count = 0; - for (let key in tabGroups) { + for (const key in tabGroups) { if (tabGroups[key]) { count += Object.keys(tabGroups[key].documents) .map(documentId => tabGroups[key].documents[documentId]) @@ -52,9 +55,12 @@ export function hasNonGlobalTabs(tabGroups?: { [editorKey: string]: Editor }): n /** * Returns name of editor group, or undefined if doc is not open */ -export function getTabGroupForDocument(documentId: string, tabGroups?: { [editorKey: string]: Editor }): string { +export function getTabGroupForDocument( + documentId: string, + tabGroups?: { [editorKey: string]: Editor } +): string { tabGroups = tabGroups || store.getState().editor.editors; - for (let key in tabGroups) { + for (const key in tabGroups) { if (tabGroups[key] && tabGroups[key].documents) { if (tabGroups[key].documents[documentId]) { return key; @@ -66,15 +72,19 @@ export function getTabGroupForDocument(documentId: string, tabGroups?: { [editor /** Takes a tab group key and returns the key of the other tab group */ export function getOtherTabGroup(tabGroup: string): string { - return tabGroup === Constants.EDITOR_KEY_PRIMARY ? Constants.EDITOR_KEY_SECONDARY : Constants.EDITOR_KEY_PRIMARY; + return tabGroup === Constants.EDITOR_KEY_PRIMARY + ? Constants.EDITOR_KEY_SECONDARY + : Constants.EDITOR_KEY_PRIMARY; } export function showWelcomePage(): void { - store.dispatch(EditorActions.open({ - contentType: Constants.CONTENT_TYPE_WELCOME_PAGE, - documentId: Constants.DOCUMENT_ID_WELCOME_PAGE, - isGlobal: true - })); + store.dispatch( + EditorActions.open({ + contentType: Constants.CONTENT_TYPE_WELCOME_PAGE, + documentId: Constants.DOCUMENT_ID_WELCOME_PAGE, + isGlobal: true, + }) + ); } export function tabGroupHasDocuments(tabGroup: Editor): boolean { diff --git a/packages/app/client/src/data/reducer/azureAuthReducer.spec.ts b/packages/app/client/src/data/reducer/azureAuthReducer.spec.ts index aee152273..3f91da687 100644 --- a/packages/app/client/src/data/reducer/azureAuthReducer.spec.ts +++ b/packages/app/client/src/data/reducer/azureAuthReducer.spec.ts @@ -30,8 +30,13 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +/* eslint-disable typescript/camelcase */ + +import { + azureArmTokenDataChanged, + invalidateArmToken, +} from '../action/azureAuthActions'; -import { azureArmTokenDataChanged, invalidateArmToken, } from '../action/azureAuthActions'; import { azureAuth, AzureAuthState } from './azureAuthReducer'; describe('Azure auth reducer tests', () => { @@ -40,7 +45,7 @@ describe('Azure auth reducer tests', () => { beforeEach(() => { startingState = { access_token: null, - persistLogin: false + persistLogin: false, }; }); diff --git a/packages/app/client/src/data/reducer/azureAuthReducer.ts b/packages/app/client/src/data/reducer/azureAuthReducer.ts index 553587269..629bbd410 100644 --- a/packages/app/client/src/data/reducer/azureAuthReducer.ts +++ b/packages/app/client/src/data/reducer/azureAuthReducer.ts @@ -30,6 +30,7 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +/* eslint-disable typescript/camelcase */ import { ArmTokenData, @@ -46,19 +47,20 @@ export interface AzureAuthState { const initialState: AzureAuthState = { access_token: null, - persistLogin: false + persistLogin: false, }; -export function azureAuth(state: AzureAuthState = initialState, action: AzureAuthAction) - : AzureAuthState { +export function azureAuth( + state: AzureAuthState = initialState, + action: AzureAuthAction +): AzureAuthState { const { payload = {}, type = '' } = action || {}; const { access_token } = (payload || {}) as ArmTokenData; switch (type) { - case AZURE_BEGIN_AUTH_WORKFLOW: case AZURE_INVALIDATE_ARM_TOKEN: - return { ...state, access_token: ''}; + return { ...state, access_token: '' }; case AZURE_ARM_TOKEN_DATA_CHANGED: return { ...state, access_token }; diff --git a/packages/app/client/src/data/reducer/bot.spec.ts b/packages/app/client/src/data/reducer/bot.spec.ts index d9feff229..5bdf5e2ed 100644 --- a/packages/app/client/src/data/reducer/bot.spec.ts +++ b/packages/app/client/src/data/reducer/bot.spec.ts @@ -31,16 +31,18 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { bot, BotState } from './bot'; -import { BotAction, close, load, setActive } from '../action/botActions'; import { BotInfo } from '@bfemulator/app-shared'; import { BotConfigWithPath } from '@bfemulator/sdk-shared'; +import { BotAction, close, load, setActive } from '../action/botActions'; + +import { bot, BotState } from './bot'; + describe('Bot reducer tests', () => { const DEFAULT_STATE: BotState = { activeBot: null, botFiles: [], - activeBotDigest: '' + activeBotDigest: '', }; it('should return unaltered state for non-matching action type', () => { @@ -57,7 +59,7 @@ describe('Bot reducer tests', () => { padlock: null, services: [], path: 'somePath', - version: '0.1' + version: '0.1', }; it('should set a bot as active', () => { @@ -71,23 +73,23 @@ describe('Bot reducer tests', () => { { displayName: 'bot2', path: 'path2', - secret: 'test-secret' + secret: 'test-secret', }, { displayName: 'bot3', path: 'path3', - secret: null + secret: null, }, { displayName: 'bot1', path: 'somePath', - secret: null + secret: null, }, ]; const startingState: BotState = { ...DEFAULT_STATE, - botFiles: testbots + botFiles: testbots, }; const action = setActive(testbot); @@ -109,10 +111,10 @@ describe('Bot reducer tests', () => { endpoint: 'someEndpointOverride', appId: 'someAppId', appPassword: 'someAppPw', - id: 'someEndpointOverride' - } - } - } as any + id: 'someEndpointOverride', + }, + }, + } as any, }; const action = setActive(testbot); @@ -129,7 +131,7 @@ describe('Bot reducer tests', () => { expect(endpointOverrides.appPassword).toBe('someAppPw'); }); - it('should throw away overrides from the previous bot if they don\'t have the same path', () => { + it("should throw away overrides from the previous bot if they don't have the same path", () => { const startingState: BotState = { ...DEFAULT_STATE, activeBot: { @@ -143,10 +145,10 @@ describe('Bot reducer tests', () => { endpoint: 'someEndpointOverride', appId: 'someAppId', appPassword: 'someAppPw', - id: 'someEndpointOverride' - } - } - } as any + id: 'someEndpointOverride', + }, + }, + } as any, }; const action = setActive(testbot); @@ -163,19 +165,19 @@ describe('Bot reducer tests', () => { { displayName: 'bot1', path: 'path1', - secret: null + secret: null, }, { displayName: 'bot2', path: 'path2', - secret: 'test-secret' + secret: 'test-secret', }, { displayName: 'bot3', path: 'path3', - secret: null + secret: null, }, - null + null, ]; const action = load(bots); const state = bot(DEFAULT_STATE, action); @@ -185,18 +187,18 @@ describe('Bot reducer tests', () => { { displayName: 'bot1', path: 'path1', - secret: null + secret: null, }, { displayName: 'bot2', path: 'path2', - secret: 'test-secret' + secret: 'test-secret', }, { displayName: 'bot3', path: 'path3', - secret: null - } + secret: null, + }, ]); }); @@ -207,8 +209,8 @@ describe('Bot reducer tests', () => { name: 'bot', description: 'this is a test bot', padlock: null, - services: [] - } as any + services: [], + } as any, }; const action = close(); const endingState = bot(startingState, action); diff --git a/packages/app/client/src/data/reducer/bot.ts b/packages/app/client/src/data/reducer/bot.ts index c7b845718..25fe11eee 100644 --- a/packages/app/client/src/data/reducer/bot.ts +++ b/packages/app/client/src/data/reducer/bot.ts @@ -32,7 +32,12 @@ // import { BotInfo } from '@bfemulator/app-shared'; -import { applyBotConfigOverrides, BotConfigWithPath, botsAreTheSame } from '@bfemulator/sdk-shared'; +import { + applyBotConfigOverrides, + BotConfigWithPath, + botsAreTheSame, +} from '@bfemulator/sdk-shared'; + import { BotAction, BotActions } from '../action/botActions'; export interface BotState { @@ -44,12 +49,11 @@ export interface BotState { const DEFAULT_STATE: BotState = { activeBot: null, activeBotDigest: null, - botFiles: [] + botFiles: [], }; export function bot(state: BotState = DEFAULT_STATE, action: BotAction) { switch (action.type) { - case BotActions.load: { state = setBotFilesState(action.payload.bots, state); break; @@ -57,14 +61,21 @@ export function bot(state: BotState = DEFAULT_STATE, action: BotAction) { case BotActions.setActive: { // move active bot up to the top of the recent bots list - const mostRecentBot = state.botFiles.find(botArg => botArg && botArg.path === action.payload.bot.path); - let recentBots = state.botFiles.filter(botArg => botArg && botArg.path !== action.payload.bot.path); + const mostRecentBot = state.botFiles.find( + botArg => botArg && botArg.path === action.payload.bot.path + ); + const recentBots = state.botFiles.filter( + botArg => botArg && botArg.path !== action.payload.bot.path + ); if (mostRecentBot) { recentBots.unshift(mostRecentBot); } let newActiveBot = action.payload.bot; if (botsAreTheSame(state.activeBot, newActiveBot)) { - newActiveBot = applyBotConfigOverrides(newActiveBot, state.activeBot.overrides); + newActiveBot = applyBotConfigOverrides( + newActiveBot, + state.activeBot.overrides + ); } state = setBotFilesState(recentBots, state); state = setActiveBot(newActiveBot, state); @@ -87,16 +98,17 @@ export function bot(state: BotState = DEFAULT_STATE, action: BotAction) { } function setActiveBot(botConfig: BotConfigWithPath, state: BotState): BotState { - return Object.assign({}, state, { + return { + ...state, get activeBot() { // Clones only - this guarantees only pristine bots will exist in the store return JSON.parse(JSON.stringify(botConfig)); - } - }); + }, + }; } function setBotFilesState(botFilesState: BotInfo[], state: BotState): BotState { - let newState = Object.assign({}, state); + const newState = { ...state }; newState.botFiles = botFilesState; return newState; diff --git a/packages/app/client/src/data/reducer/chat.spec.ts b/packages/app/client/src/data/reducer/chat.spec.ts index 1266b07ba..1b7d2d352 100644 --- a/packages/app/client/src/data/reducer/chat.spec.ts +++ b/packages/app/client/src/data/reducer/chat.spec.ts @@ -32,6 +32,7 @@ // import LogEntry from '@bfemulator/emulator-core/lib/types/log/entry'; + import { addTranscript, appendToLog, @@ -43,9 +44,10 @@ import { newDocument, removeTranscript, setInspectorObjects, - updateChat + updateChat, } from '../action/chatActions'; import { closeNonGlobalTabs } from '../action/editorActions'; + import { chat, ChatState } from './chat'; describe('Chat reducer tests', () => { @@ -55,9 +57,9 @@ describe('Chat reducer tests', () => { chats: { [testChatId]: { log: { - entries: [] - } - } + entries: [], + }, + }, }, transcripts: [], }; @@ -116,13 +118,13 @@ describe('Chat reducer tests', () => { ...DEFAULT_STATE, chats: { ...DEFAULT_STATE.chats, - [testChatId]: {} - } + [testChatId]: {}, + }, }; const endingState = chat(startingState, action); const expectedDoc = { ...endingState.chats[testChatId], - testing: true + testing: true, }; expect(endingState.chats[testChatId]).toEqual(expectedDoc); }); @@ -135,10 +137,10 @@ describe('Chat reducer tests', () => { type: 'text', payload: { level: 0, - text: 'testing' - } - } - ] + text: 'testing', + }, + }, + ], }; const action = appendToLog(testChatId, logEntry); const startingState = { @@ -147,10 +149,10 @@ describe('Chat reducer tests', () => { ...DEFAULT_STATE.chats, [testChatId]: { log: { - entries: [] - } - } - } + entries: [], + }, + }, + }, }; const endingState = chat(startingState, action); expect(endingState.chats[testChatId].log.entries[0]).toBeTruthy(); @@ -165,10 +167,10 @@ describe('Chat reducer tests', () => { type: 'text', payload: { level: 0, - text: 'testing' - } - } - ] + text: 'testing', + }, + }, + ], }; const startingState = { ...DEFAULT_STATE, @@ -176,10 +178,10 @@ describe('Chat reducer tests', () => { ...DEFAULT_STATE.chats, [testChatId]: { log: { - entries: [] - } - } - } + entries: [], + }, + }, + }, }; let state = chat(startingState, appendToLog(testChatId, logEntry)); @@ -195,12 +197,16 @@ describe('Chat reducer tests', () => { ...DEFAULT_STATE, chats: { ...DEFAULT_STATE.chats, - [testChatId]: {} - } + [testChatId]: {}, + }, }; const endingState = chat(startingState, action); - expect(endingState.chats[testChatId].inspectorObjects.length).toBeGreaterThan(0); - expect(endingState.chats[testChatId].inspectorObjects[0]).toEqual({ testing: true }); + expect( + endingState.chats[testChatId].inspectorObjects.length + ).toBeGreaterThan(0); + expect(endingState.chats[testChatId].inspectorObjects[0]).toEqual({ + testing: true, + }); }); it('should reset state on a "close all" editor action', () => { @@ -209,10 +215,10 @@ describe('Chat reducer tests', () => { changeKey: 999, chats: { [tempChat]: { - testing: true - } + testing: true, + }, }, - transcripts: ['xs1', 'xs2', 'xs3'] + transcripts: ['xs1', 'xs2', 'xs3'], }; const action = closeNonGlobalTabs(); const state = chat(alteredState, action); @@ -228,11 +234,14 @@ describe('Chat reducer tests', () => { ...DEFAULT_STATE.chats, chat1: { id: 'chat', - userId: 'userId' - } - } + userId: 'userId', + }, + }, }; - const action = updateChat('chat1', { id: 'updatedChatId', userId: 'updatedUserId' }); + const action = updateChat('chat1', { + id: 'updatedChatId', + userId: 'updatedUserId', + }); const state = chat(startingState, action); expect(state.chats.chat1.id).toBe('updatedChatId'); expect(state.chats.chat1.userId).toBe('updatedUserId'); diff --git a/packages/app/client/src/data/reducer/chat.ts b/packages/app/client/src/data/reducer/chat.ts index 1fd1af634..9c08a6d9a 100644 --- a/packages/app/client/src/data/reducer/chat.ts +++ b/packages/app/client/src/data/reducer/chat.ts @@ -47,7 +47,10 @@ const DEFAULT_STATE: ChatState = { transcripts: [], }; -export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | EditorAction): ChatState { +export function chat( + state: ChatState = DEFAULT_STATE, + action: ChatAction | EditorAction +): ChatState { switch (action.type) { case ChatActions.addTranscript: { const { payload } = action; @@ -78,8 +81,8 @@ export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | Edit changeKey: state.changeKey + 1, chats: { ...state.chats, - [payload.documentId]: { ...payload } - } + [payload.documentId]: { ...payload }, + }, }; break; } @@ -89,7 +92,7 @@ export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | Edit // can't use the JSON.parse(JSON.stringify()) // trick with chats because Subscribers are circular if (payload.documentId in state.chats) { - let copy = { ...state }; + const copy = { ...state }; copy.changeKey += 1; delete copy.chats[payload.documentId]; state = { ...copy }; @@ -103,16 +106,16 @@ export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | Edit if (document) { document = { ...document, - ...payload.options + ...payload.options, }; state = { ...state, chats: { ...state.chats, [payload.documentId]: { - ...document - } - } + ...document, + }, + }, }; } break; @@ -126,20 +129,17 @@ export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | Edit ...document, log: { ...document.log, - entries: [ - ...document.log.entries, - payload.entry - ] - } + entries: [...document.log.entries, payload.entry], + }, }; state = { ...state, chats: { ...state.chats, [payload.documentId]: { - ...document - } - } + ...document, + }, + }, }; } break; @@ -152,17 +152,17 @@ export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | Edit document = { ...document, log: { - entries: [] - } + entries: [], + }, }; state = { ...state, chats: { ...state.chats, [payload.documentId]: { - ...document - } - } + ...document, + }, + }, }; } break; @@ -174,7 +174,7 @@ export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | Edit if (document) { document = { ...document, - inspectorObjects: payload.objs + inspectorObjects: payload.objs, }; } state = { @@ -182,9 +182,9 @@ export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | Edit chats: { ...state.chats, [payload.documentId]: { - ...document - } - } + ...document, + }, + }, }; break; } @@ -196,16 +196,16 @@ export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | Edit if (document) { document = { ...document, - ...updatedValues + ...updatedValues, }; state = { ...state, chats: { ...state.chats, [payload.documentId]: { - ...document - } - } + ...document, + }, + }, }; } break; @@ -223,8 +223,11 @@ export function chat(state: ChatState = DEFAULT_STATE, action: ChatAction | Edit return state; } -function setTranscriptsState(transcripts: string[], state: ChatState): ChatState { - let newState = Object.assign({}, state); +function setTranscriptsState( + transcripts: string[], + state: ChatState +): ChatState { + const newState = { ...state }; newState.transcripts = transcripts; newState.changeKey = state.changeKey + 1; diff --git a/packages/app/client/src/data/reducer/clientAwareSettingsReducer.ts b/packages/app/client/src/data/reducer/clientAwareSettingsReducer.ts index a1465e778..76da73002 100644 --- a/packages/app/client/src/data/reducer/clientAwareSettingsReducer.ts +++ b/packages/app/client/src/data/reducer/clientAwareSettingsReducer.ts @@ -1,8 +1,46 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { ClientAwareSettings } from '@bfemulator/app-shared/built'; -import { CLIENT_AWARE_SETTINGS_CHANGED, ClientAwareSettingsActions } from '../action/clientAwareSettingsActions'; -export function clientAwareSettings(state: ClientAwareSettings = {} as any, action: ClientAwareSettingsActions) - : ClientAwareSettings { +import { + CLIENT_AWARE_SETTINGS_CHANGED, + ClientAwareSettingsActions, +} from '../action/clientAwareSettingsActions'; + +export function clientAwareSettings( + state: ClientAwareSettings = {} as any, + action: ClientAwareSettingsActions +): ClientAwareSettings { switch (action.type) { case CLIENT_AWARE_SETTINGS_CHANGED: return { ...state, ...action.payload }; diff --git a/packages/app/client/src/data/reducer/dialog.spec.ts b/packages/app/client/src/data/reducer/dialog.spec.ts index 9e537cbfa..a667ce10d 100644 --- a/packages/app/client/src/data/reducer/dialog.spec.ts +++ b/packages/app/client/src/data/reducer/dialog.spec.ts @@ -32,11 +32,12 @@ // import { DialogAction, setShowing } from '../action/dialogActions'; + import { dialog, DialogState } from './dialog'; describe('Dialog reducer tests', () => { const DEFAULT_STATE: DialogState = { - showing: false + showing: false, }; it('should return unaltered state for non-matching action type', () => { diff --git a/packages/app/client/src/data/reducer/dialog.ts b/packages/app/client/src/data/reducer/dialog.ts index f1b7e494e..b8c06e1a9 100644 --- a/packages/app/client/src/data/reducer/dialog.ts +++ b/packages/app/client/src/data/reducer/dialog.ts @@ -38,10 +38,13 @@ export interface DialogState { } const DEFAULT_STATE: DialogState = { - showing: false + showing: false, }; -export function dialog(state: DialogState = DEFAULT_STATE, action: DialogAction): DialogState { +export function dialog( + state: DialogState = DEFAULT_STATE, + action: DialogAction +): DialogState { switch (action.type) { case DialogActions.setShowing: { state = setShowing(action.payload.showing, state); @@ -55,5 +58,5 @@ export function dialog(state: DialogState = DEFAULT_STATE, action: DialogAction) } export function setShowing(showing: boolean, _state: DialogState): DialogState { - return { showing: showing }; + return { showing }; } diff --git a/packages/app/client/src/data/reducer/editor.spec.ts b/packages/app/client/src/data/reducer/editor.spec.ts index 1dbb3f1a3..a77a77ea4 100644 --- a/packages/app/client/src/data/reducer/editor.spec.ts +++ b/packages/app/client/src/data/reducer/editor.spec.ts @@ -32,6 +32,7 @@ // import { deepCopySlow } from '@bfemulator/app-shared'; + import * as Constants from '../../constants'; import { addDocPendingChange, @@ -48,8 +49,9 @@ import { splitTab, swapTabs, toggleDraggingTab, - updateDocument + updateDocument, } from '../action/editorActions'; + import { editor, Editor, @@ -59,26 +61,25 @@ import { setActiveEditor, setDraggingTab, setEditorState, - setNewPrimaryEditor + setNewPrimaryEditor, } from './editor'; let defaultState: EditorState; jest.mock('../../ui/dialogs', () => ({ - AzureLoginPromptDialogContainer: function mock() { - return undefined; - }, - AzureLoginSuccessDialogContainer: function mock() { - return undefined; - }, - BotCreationDialog: function mock() { - return undefined; - }, - DialogService: { showDialog: () => Promise.resolve(true) }, - SecretPromptDialog: function mock() { - return undefined; - } - } -)); + AzureLoginPromptDialogContainer: function mock() { + return undefined; + }, + AzureLoginSuccessDialogContainer: function mock() { + return undefined; + }, + BotCreationDialog: function mock() { + return undefined; + }, + DialogService: { showDialog: () => Promise.resolve(true) }, + SecretPromptDialog: function mock() { + return undefined; + }, +})); describe('Editor reducer tests', () => { beforeEach(initializeDefaultState); @@ -98,17 +99,21 @@ describe('Editor reducer tests', () => { ...defaultState.editors, [Constants.EDITOR_KEY_PRIMARY]: { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], - tabOrder: ['doc1', 'doc2'] - } - } + tabOrder: ['doc1', 'doc2'], + }, + }, }; const srcEditorKey = Constants.EDITOR_KEY_PRIMARY; const destEditorKey = Constants.EDITOR_KEY_PRIMARY; const docIdToAppend = 'doc1'; const action = appendTab(srcEditorKey, destEditorKey, docIdToAppend); const endingState = editor(startingState, action); - expect(endingState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder[0]).not.toBe(docIdToAppend); - expect(endingState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder[1]).toBe(docIdToAppend); + expect( + endingState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder[0] + ).not.toBe(docIdToAppend); + expect( + endingState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder[1] + ).toBe(docIdToAppend); // assert that draggingTab is toggled off expect(endingState.draggingTab).toBe(false); @@ -123,21 +128,21 @@ describe('Editor reducer tests', () => { [Constants.EDITOR_KEY_PRIMARY]: { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], documents: { - 'doc1': {}, - 'doc2': {} + doc1: {}, + doc2: {}, }, tabOrder: ['doc1', 'doc2'], - recentTabs: ['doc1', 'doc2'] + recentTabs: ['doc1', 'doc2'], }, [Constants.EDITOR_KEY_SECONDARY]: { ...defaultState.editors[Constants.EDITOR_KEY_SECONDARY], documents: { - 'doc3': {} + doc3: {}, }, tabOrder: ['doc3'], - recentTabs: ['doc3'] - } - } + recentTabs: ['doc3'], + }, + }, }; const srcEditorKey = Constants.EDITOR_KEY_PRIMARY; const destEditorKey = Constants.EDITOR_KEY_SECONDARY; @@ -165,20 +170,20 @@ describe('Editor reducer tests', () => { [Constants.EDITOR_KEY_PRIMARY]: { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], documents: { - 'doc1': {} + doc1: {}, }, tabOrder: ['doc1'], - recentTabs: ['doc1'] + recentTabs: ['doc1'], }, [Constants.EDITOR_KEY_SECONDARY]: { ...defaultState.editors[Constants.EDITOR_KEY_SECONDARY], documents: { - 'doc2': {} + doc2: {}, }, tabOrder: ['doc2'], - recentTabs: ['doc2'] - } - } + recentTabs: ['doc2'], + }, + }, }; const srcEditorKey = Constants.EDITOR_KEY_SECONDARY; const destEditorKey = Constants.EDITOR_KEY_PRIMARY; @@ -197,28 +202,32 @@ describe('Editor reducer tests', () => { [Constants.EDITOR_KEY_PRIMARY]: { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], documents: { - 'doc1': {} + doc1: {}, }, tabOrder: ['doc1'], - recentTabs: ['doc1'] + recentTabs: ['doc1'], }, [Constants.EDITOR_KEY_SECONDARY]: { ...defaultState.editors[Constants.EDITOR_KEY_SECONDARY], documents: { - 'doc2': {} + doc2: {}, }, tabOrder: ['doc2'], - recentTabs: ['doc2'] - } - } + recentTabs: ['doc2'], + }, + }, }; const srcEditorKey = Constants.EDITOR_KEY_PRIMARY; const destEditorKey = Constants.EDITOR_KEY_SECONDARY; const action = appendTab(srcEditorKey, destEditorKey, 'doc1'); const endingState = editor(startingState, action); expect(endingState.activeEditor).toBe(Constants.EDITOR_KEY_PRIMARY); - expect(endingState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder).toEqual(['doc2', 'doc1']); - expect(endingState.editors[Constants.EDITOR_KEY_SECONDARY].tabOrder).toEqual([]); + expect( + endingState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder + ).toEqual(['doc2', 'doc1']); + expect( + endingState.editors[Constants.EDITOR_KEY_SECONDARY].tabOrder + ).toEqual([]); // assert that draggingTab is toggled off expect(endingState.draggingTab).toBe(false); @@ -235,13 +244,13 @@ describe('Editor reducer tests', () => { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], activeDocumentId: 'doc1', documents: { - 'doc1': {}, - 'doc2': {} + doc1: {}, + doc2: {}, }, recentTabs: ['doc1', 'doc2'], - tabOrder: ['doc1', 'doc2'] - } - } + tabOrder: ['doc1', 'doc2'], + }, + }, }; const editorKey = Constants.EDITOR_KEY_PRIMARY; const action = close(editorKey, 'doc1'); @@ -263,21 +272,21 @@ describe('Editor reducer tests', () => { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], activeDocumentId: 'doc1', documents: { - 'doc1': {} + doc1: {}, }, recentTabs: ['doc1'], - tabOrder: ['doc1'] + tabOrder: ['doc1'], }, [Constants.EDITOR_KEY_SECONDARY]: { ...defaultState.editors[Constants.EDITOR_KEY_SECONDARY], activeDocumentId: 'doc2', documents: { - 'doc2': {} + doc2: {}, }, recentTabs: ['doc2'], - tabOrder: ['doc2'] - } - } + tabOrder: ['doc2'], + }, + }, }; const editorKey = Constants.EDITOR_KEY_PRIMARY; const action = close(editorKey, 'doc1'); @@ -287,7 +296,11 @@ describe('Editor reducer tests', () => { expect(primaryEditor.recentTabs).toEqual(['doc2']); expect(primaryEditor.tabOrder).toEqual(['doc2']); expect(Object.keys(primaryEditor.documents)).toContain('doc2'); - expect(Object.keys(endingState.editors[Constants.EDITOR_KEY_SECONDARY].documents)).not.toContain('doc1'); + expect( + Object.keys( + endingState.editors[Constants.EDITOR_KEY_SECONDARY].documents + ) + ).not.toContain('doc1'); }); }); @@ -300,7 +313,7 @@ describe('Editor reducer tests', () => { it('should set the active editor', () => { const startingState: EditorState = { ...defaultState, - activeEditor: Constants.EDITOR_KEY_SECONDARY + activeEditor: Constants.EDITOR_KEY_SECONDARY, }; const action = setActiveEditorAction(Constants.EDITOR_KEY_PRIMARY); const endingState = editor(startingState, action); @@ -317,22 +330,22 @@ describe('Editor reducer tests', () => { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], activeDocumentId: 'doc1', documents: { - 'doc1': {}, - 'doc2': {} + doc1: {}, + doc2: {}, }, recentTabs: ['doc1', 'doc2'], - tabOrder: ['doc1', 'doc2'] + tabOrder: ['doc1', 'doc2'], }, [Constants.EDITOR_KEY_SECONDARY]: { ...defaultState.editors[Constants.EDITOR_KEY_SECONDARY], activeDocumentId: 'doc3', documents: { - 'doc3': {} + doc3: {}, }, recentTabs: ['doc3'], - tabOrder: ['doc3'] - } - } + tabOrder: ['doc3'], + }, + }, }; const action = setActiveTab('doc2'); const endingState = editor(startingState, action); @@ -342,7 +355,7 @@ describe('Editor reducer tests', () => { expect(newActiveEditor.recentTabs).toEqual(['doc2', 'doc1']); }); - it('should set a document\'s dirty flag', () => { + it("should set a document's dirty flag", () => { const startingState: EditorState = { ...defaultState, editors: { @@ -351,17 +364,20 @@ describe('Editor reducer tests', () => { ...defaultState.editors[Constants.EDITOR_KEY_SECONDARY], activeDocumentId: 'doc3', documents: { - 'doc3': { dirty: false } + doc3: { dirty: false }, }, recentTabs: ['doc3'], - tabOrder: ['doc3'] - } - } + tabOrder: ['doc3'], + }, + }, }; const dirtyDocId = 'doc3'; const action = setDirtyFlag(dirtyDocId, true); const endingState = editor(startingState, action); - expect(endingState.editors[Constants.EDITOR_KEY_SECONDARY].documents[dirtyDocId].dirty).toBe(true); + expect( + endingState.editors[Constants.EDITOR_KEY_SECONDARY].documents[dirtyDocId] + .dirty + ).toBe(true); }); it('should close all non-global tabs', () => { @@ -374,26 +390,26 @@ describe('Editor reducer tests', () => { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], activeDocumentId: 'doc2', documents: { - 'doc1': { isGlobal: true }, - 'doc2': { isGlobal: false }, - 'doc3': { isGlobal: false } + doc1: { isGlobal: true }, + doc2: { isGlobal: false }, + doc3: { isGlobal: false }, }, recentTabs: ['doc2', 'doc1', 'doc3'], - tabOrder: ['doc1', 'doc2', 'doc3'] + tabOrder: ['doc1', 'doc2', 'doc3'], }, [Constants.EDITOR_KEY_SECONDARY]: { ...defaultState.editors[Constants.EDITOR_KEY_SECONDARY], activeDocumentId: 'doc6', documents: { - 'doc4': { isGlobal: false }, - 'doc5': { isGlobal: true }, - 'doc6': { isGlobal: true } + doc4: { isGlobal: false }, + doc5: { isGlobal: true }, + doc6: { isGlobal: true }, }, recentTabs: ['doc6', 'doc4', 'doc5'], - tabOrder: ['doc4', 'doc5', 'doc6'] - } + tabOrder: ['doc4', 'doc5', 'doc6'], + }, }, - docsWithPendingChanges: ['doc2', 'doc3'] + docsWithPendingChanges: ['doc2', 'doc3'], }; const action = closeNonGlobalTabs(); const endingState = editor(startingState, action); @@ -421,18 +437,22 @@ describe('Editor reducer tests', () => { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], activeDocumentId: 'doc1', documents: { - 'doc1': { documentId: 'doc1' } + doc1: { documentId: 'doc1' }, }, recentTabs: ['doc1'], - tabOrder: ['doc1'] - } - } + tabOrder: ['doc1'], + }, + }, }; const docToUpdateId = 'doc1'; - const action = updateDocument(docToUpdateId, { isGlobal: true, dirty: true }); + const action = updateDocument(docToUpdateId, { + isGlobal: true, + dirty: true, + }); const endingState = editor(startingState, action); - expect(endingState.editors[Constants.EDITOR_KEY_PRIMARY].documents[docToUpdateId]) - .toEqual({ documentId: 'doc1', isGlobal: true, dirty: true }); + expect( + endingState.editors[Constants.EDITOR_KEY_PRIMARY].documents[docToUpdateId] + ).toEqual({ documentId: 'doc1', isGlobal: true, dirty: true }); }); describe('opening a document', () => { @@ -446,27 +466,33 @@ describe('Editor reducer tests', () => { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], activeDocumentId: 'doc1', documents: { - 'doc1': {} + doc1: {}, }, recentTabs: ['doc1'], - tabOrder: ['doc1'] + tabOrder: ['doc1'], }, [Constants.EDITOR_KEY_SECONDARY]: { ...defaultState.editors[Constants.EDITOR_KEY_SECONDARY], activeDocumentId: 'doc2', documents: { - 'doc2': {}, - 'doc3': {} + doc2: {}, + doc3: {}, }, recentTabs: ['doc3', 'doc2'], - tabOrder: ['doc2', 'doc3'] - } - } + tabOrder: ['doc2', 'doc3'], + }, + }, }; - const action = open({ contentType: Constants.CONTENT_TYPE_APP_SETTINGS, documentId: 'doc2', isGlobal: true }); + const action = open({ + contentType: Constants.CONTENT_TYPE_APP_SETTINGS, + documentId: 'doc2', + isGlobal: true, + }); const endingState = editor(startingState, action); expect(endingState.activeEditor).toBe(Constants.EDITOR_KEY_SECONDARY); - expect(endingState.editors[Constants.EDITOR_KEY_SECONDARY].recentTabs).toEqual(['doc2', 'doc3']); + expect( + endingState.editors[Constants.EDITOR_KEY_SECONDARY].recentTabs + ).toEqual(['doc2', 'doc3']); }); it('should focus an already existing document in the same tab group', () => { @@ -479,15 +505,19 @@ describe('Editor reducer tests', () => { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], activeDocumentId: 'doc1', documents: { - 'doc1': {}, - 'doc2': {} + doc1: {}, + doc2: {}, }, recentTabs: ['doc1', 'doc2'], - tabOrder: ['doc1', 'doc2'] - } - } + tabOrder: ['doc1', 'doc2'], + }, + }, }; - const action = open({ contentType: Constants.CONTENT_TYPE_APP_SETTINGS, documentId: 'doc2', isGlobal: true }); + const action = open({ + contentType: Constants.CONTENT_TYPE_APP_SETTINGS, + documentId: 'doc2', + isGlobal: true, + }); const endingState = editor(startingState, action); const primaryEditor = endingState.editors[Constants.EDITOR_KEY_PRIMARY]; expect(primaryEditor.activeDocumentId).toBe('doc2'); @@ -505,15 +535,19 @@ describe('Editor reducer tests', () => { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], activeDocumentId: 'doc1', documents: { - 'doc1': {}, - 'doc2': {} + doc1: {}, + doc2: {}, }, recentTabs: ['doc1', 'doc2'], - tabOrder: ['doc1', 'doc2'] - } - } + tabOrder: ['doc1', 'doc2'], + }, + }, }; - const action = open({ contentType: Constants.CONTENT_TYPE_APP_SETTINGS, documentId: 'doc3', isGlobal: true }); + const action = open({ + contentType: Constants.CONTENT_TYPE_APP_SETTINGS, + documentId: 'doc3', + isGlobal: true, + }); const endingState = editor(startingState, action); const primaryEditor = endingState.editors[Constants.EDITOR_KEY_PRIMARY]; expect(primaryEditor.activeDocumentId).toBe('doc3'); @@ -522,7 +556,7 @@ describe('Editor reducer tests', () => { expect(Object.keys(primaryEditor.documents)).toContain('doc3'); }); - it('should append new document if current activeDocument isn\'t found', () => { + it("should append new document if current activeDocument isn't found", () => { const startingState: EditorState = { ...defaultState, activeEditor: Constants.EDITOR_KEY_PRIMARY, @@ -532,15 +566,19 @@ describe('Editor reducer tests', () => { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], activeDocumentId: 'doc1234', documents: { - 'doc1': {}, - 'doc2': {} + doc1: {}, + doc2: {}, }, recentTabs: ['doc1', 'doc2'], - tabOrder: ['doc1', 'doc2'] - } - } + tabOrder: ['doc1', 'doc2'], + }, + }, }; - const action = open({ contentType: Constants.CONTENT_TYPE_APP_SETTINGS, documentId: 'doc3', isGlobal: true }); + const action = open({ + contentType: Constants.CONTENT_TYPE_APP_SETTINGS, + documentId: 'doc3', + isGlobal: true, + }); const endingState = editor(startingState, action); const primaryEditor = endingState.editors[Constants.EDITOR_KEY_PRIMARY]; expect(primaryEditor.activeDocumentId).toBe('doc3'); @@ -561,22 +599,22 @@ describe('Editor reducer tests', () => { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], activeDocumentId: 'doc2', documents: { - 'doc1': {}, - 'doc2': {} + doc1: {}, + doc2: {}, }, recentTabs: ['doc1', 'doc2'], - tabOrder: ['doc1', 'doc2'] + tabOrder: ['doc1', 'doc2'], }, [Constants.EDITOR_KEY_SECONDARY]: { ...defaultState.editors[Constants.EDITOR_KEY_SECONDARY], activeDocumentId: 'doc3', documents: { - 'doc3': {} + doc3: {}, }, recentTabs: ['doc3'], - tabOrder: ['doc3'] - } - } + tabOrder: ['doc3'], + }, + }, }; const action = splitTab( Constants.CONTENT_TYPE_APP_SETTINGS, @@ -611,14 +649,14 @@ describe('Editor reducer tests', () => { [Constants.EDITOR_KEY_PRIMARY]: { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], documents: { - 'doc1': {}, - 'doc2': {}, - 'doc3': {} + doc1: {}, + doc2: {}, + doc3: {}, }, recentTabs: ['doc3', 'doc2', 'doc1'], - tabOrder: ['doc1', 'doc2', 'doc3'] - } - } + tabOrder: ['doc1', 'doc2', 'doc3'], + }, + }, }; const action = swapTabs( Constants.EDITOR_KEY_PRIMARY, @@ -640,23 +678,23 @@ describe('Editor reducer tests', () => { [Constants.EDITOR_KEY_PRIMARY]: { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], documents: { - 'doc1': {}, - 'doc2': {} + doc1: {}, + doc2: {}, }, recentTabs: ['doc2', 'doc1'], - tabOrder: ['doc1', 'doc2'] + tabOrder: ['doc1', 'doc2'], }, [Constants.EDITOR_KEY_SECONDARY]: { ...defaultState.editors[Constants.EDITOR_KEY_SECONDARY], activeDocumentId: 'doc3', documents: { - 'doc3': {}, - 'doc4': {} + doc3: {}, + doc4: {}, }, recentTabs: ['doc4', 'doc3'], - tabOrder: ['doc3', 'doc4'] - } - } + tabOrder: ['doc3', 'doc4'], + }, + }, }; const action = swapTabs( Constants.EDITOR_KEY_SECONDARY, @@ -666,7 +704,8 @@ describe('Editor reducer tests', () => { ); const endingState = editor(startingState, action); const primaryEditor = endingState.editors[Constants.EDITOR_KEY_PRIMARY]; - const secondaryEditor = endingState.editors[Constants.EDITOR_KEY_SECONDARY]; + const secondaryEditor = + endingState.editors[Constants.EDITOR_KEY_SECONDARY]; expect(endingState.activeEditor).toBe(Constants.EDITOR_KEY_SECONDARY); expect(Object.keys(primaryEditor.documents)).toContain('doc3'); @@ -688,22 +727,22 @@ describe('Editor reducer tests', () => { [Constants.EDITOR_KEY_PRIMARY]: { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], documents: { - 'doc1': {}, - 'doc2': {} + doc1: {}, + doc2: {}, }, recentTabs: ['doc2', 'doc1'], - tabOrder: ['doc1', 'doc2'] + tabOrder: ['doc1', 'doc2'], }, [Constants.EDITOR_KEY_SECONDARY]: { ...defaultState.editors[Constants.EDITOR_KEY_SECONDARY], activeDocumentId: 'doc3', documents: { - 'doc3': {} + doc3: {}, }, recentTabs: ['doc3'], - tabOrder: ['doc3'] - } - } + tabOrder: ['doc3'], + }, + }, }; const action = swapTabs( Constants.EDITOR_KEY_SECONDARY, @@ -725,21 +764,21 @@ describe('Editor reducer tests', () => { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], activeDocumentId: 'doc1', documents: { - 'doc1': {} + doc1: {}, }, recentTabs: ['doc1'], - tabOrder: ['doc1'] + tabOrder: ['doc1'], }, [Constants.EDITOR_KEY_SECONDARY]: { ...defaultState.editors[Constants.EDITOR_KEY_SECONDARY], activeDocumentId: 'doc2', documents: { - 'doc2': {} + doc2: {}, }, recentTabs: ['doc2'], - tabOrder: ['doc2'] - } - } + tabOrder: ['doc2'], + }, + }, }; const action = swapTabs( Constants.EDITOR_KEY_PRIMARY, @@ -749,7 +788,8 @@ describe('Editor reducer tests', () => { ); const endingState = editor(startingState, action); const primaryEditor = endingState.editors[Constants.EDITOR_KEY_PRIMARY]; - const secondaryEditor = endingState.editors[Constants.EDITOR_KEY_SECONDARY]; + const secondaryEditor = + endingState.editors[Constants.EDITOR_KEY_SECONDARY]; expect(endingState.activeEditor).toBe(Constants.EDITOR_KEY_PRIMARY); expect(primaryEditor.activeDocumentId).toBe('doc2'); @@ -768,32 +808,34 @@ describe('Editor reducer tests', () => { it('should add a doc to the list', () => { const startingState: EditorState = { ...defaultState, - docsWithPendingChanges: [ - 'doc1', - 'doc2', - 'doc3' - ] + docsWithPendingChanges: ['doc1', 'doc2', 'doc3'], }; const action1 = addDocPendingChange('doc4'); const endingState1 = editor(startingState, action1); - expect(endingState1.docsWithPendingChanges).toEqual(['doc1', 'doc2', 'doc3', 'doc4']); + expect(endingState1.docsWithPendingChanges).toEqual([ + 'doc1', + 'doc2', + 'doc3', + 'doc4', + ]); // shouldn't allow duplicates const action2 = addDocPendingChange('doc4'); const endingState2 = editor(endingState1, action2); - expect(endingState2.docsWithPendingChanges).toEqual(['doc1', 'doc2', 'doc3', 'doc4']); + expect(endingState2.docsWithPendingChanges).toEqual([ + 'doc1', + 'doc2', + 'doc3', + 'doc4', + ]); }); it('should remove a doc from the list', () => { const startingState: EditorState = { ...defaultState, - docsWithPendingChanges: [ - 'doc1', - 'doc2', - 'doc3' - ] + docsWithPendingChanges: ['doc1', 'doc2', 'doc3'], }; const action = removeDocPendingChange('doc2'); const endingState = editor(startingState, action); @@ -822,34 +864,50 @@ describe('Editor reducer utility function tests', () => { [Constants.EDITOR_KEY_SECONDARY]: { activeDocumentId: 'doc1', documents: { - 'doc1': {} + doc1: {}, }, tabOrder: ['doc1'], - recentTabs: ['doc1'] - } - } + recentTabs: ['doc1'], + }, + }, }; const newPrimaryEditor: Editor = { activeDocumentId: 'doc2', documents: { - 'doc2': {} + doc2: {}, }, tabOrder: ['doc2'], - recentTabs: ['doc2'] + recentTabs: ['doc2'], }; const endingState = setNewPrimaryEditor(newPrimaryEditor, startingState); expect(endingState.activeEditor).toBe(Constants.EDITOR_KEY_PRIMARY); - expect(endingState.editors[Constants.EDITOR_KEY_PRIMARY].activeDocumentId).toEqual('doc2'); - expect(endingState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder).toEqual(['doc2']); - expect(endingState.editors[Constants.EDITOR_KEY_PRIMARY].recentTabs).toEqual(['doc2']); - expect(Object.keys(endingState.editors[Constants.EDITOR_KEY_PRIMARY].documents)).toContain('doc2'); - - expect(endingState.editors[Constants.EDITOR_KEY_SECONDARY].activeDocumentId).toBe(null); - expect(endingState.editors[Constants.EDITOR_KEY_SECONDARY].documents).toEqual({}); - expect(endingState.editors[Constants.EDITOR_KEY_SECONDARY].recentTabs).toEqual([]); - expect(endingState.editors[Constants.EDITOR_KEY_SECONDARY].tabOrder).toEqual([]); + expect( + endingState.editors[Constants.EDITOR_KEY_PRIMARY].activeDocumentId + ).toEqual('doc2'); + expect(endingState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder).toEqual([ + 'doc2', + ]); + expect( + endingState.editors[Constants.EDITOR_KEY_PRIMARY].recentTabs + ).toEqual(['doc2']); + expect( + Object.keys(endingState.editors[Constants.EDITOR_KEY_PRIMARY].documents) + ).toContain('doc2'); + + expect( + endingState.editors[Constants.EDITOR_KEY_SECONDARY].activeDocumentId + ).toBe(null); + expect( + endingState.editors[Constants.EDITOR_KEY_SECONDARY].documents + ).toEqual({}); + expect( + endingState.editors[Constants.EDITOR_KEY_SECONDARY].recentTabs + ).toEqual([]); + expect( + endingState.editors[Constants.EDITOR_KEY_SECONDARY].tabOrder + ).toEqual([]); }); it('setActiveEditor() functionality', () => { @@ -863,13 +921,23 @@ describe('Editor reducer utility function tests', () => { activeDocumentId: 'testing', documents: {}, tabOrder: ['testing'], - recentTabs: ['testing'] + recentTabs: ['testing'], }; - const newState = setEditorState(Constants.EDITOR_KEY_PRIMARY, updatedEditor, defaultState); + const newState = setEditorState( + Constants.EDITOR_KEY_PRIMARY, + updatedEditor, + defaultState + ); expect(newState).not.toBe(defaultState); - expect(newState.editors[Constants.EDITOR_KEY_PRIMARY].activeDocumentId).toBe('testing'); - expect(newState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder).toEqual(['testing']); - expect(newState.editors[Constants.EDITOR_KEY_PRIMARY].recentTabs).toEqual(['testing']); + expect( + newState.editors[Constants.EDITOR_KEY_PRIMARY].activeDocumentId + ).toBe('testing'); + expect(newState.editors[Constants.EDITOR_KEY_PRIMARY].tabOrder).toEqual([ + 'testing', + ]); + expect(newState.editors[Constants.EDITOR_KEY_PRIMARY].recentTabs).toEqual([ + 'testing', + ]); }); describe('removeDocumentFromTabGroup() functionality', () => { @@ -879,19 +947,16 @@ describe('Editor reducer utility function tests', () => { activeDocumentId: docToRemove, documents: { [docToRemove]: {}, - 'doc2': {} + doc2: {}, }, - tabOrder: [ - docToRemove, - 'doc2' - ], - recentTabs: [ - docToRemove, - 'doc2' - ] + tabOrder: [docToRemove, 'doc2'], + recentTabs: [docToRemove, 'doc2'], }; - const modifiedEditor = removeDocumentFromTabGroup(tempEditor, docToRemove); + const modifiedEditor = removeDocumentFromTabGroup( + tempEditor, + docToRemove + ); expect(modifiedEditor).not.toBe(tempEditor); expect(modifiedEditor.activeDocumentId).toBe('doc2'); expect(modifiedEditor.recentTabs).not.toContain(docToRemove); @@ -903,14 +968,10 @@ describe('Editor reducer utility function tests', () => { const tempEditor: Editor = { activeDocumentId: 'doc1', documents: { - 'doc1': {} + doc1: {}, }, - tabOrder: [ - 'doc1' - ], - recentTabs: [ - 'doc1' - ] + tabOrder: ['doc1'], + recentTabs: ['doc1'], }; const modifiedEditor = removeDocumentFromTabGroup(tempEditor, 'doc1'); @@ -933,18 +994,18 @@ describe('Editor reducer utility function tests', () => { activeDocumentId: null, documents: {}, recentTabs: [], - tabOrder: [] + tabOrder: [], }, [Constants.EDITOR_KEY_SECONDARY]: { ...defaultState.editors[Constants.EDITOR_KEY_SECONDARY], activeDocumentId: 'doc2', documents: { - 'doc2': {} + doc2: {}, }, recentTabs: ['doc2'], - tabOrder: ['doc2'] - } - } + tabOrder: ['doc2'], + }, + }, }; const endState = fixupTabGroups(startingState); const primaryEditor = endState.editors[Constants.EDITOR_KEY_PRIMARY]; @@ -972,19 +1033,19 @@ describe('Editor reducer utility function tests', () => { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], activeDocumentId: 'doc1', documents: { - 'doc1': {} + doc1: {}, }, recentTabs: ['doc1'], - tabOrder: ['doc1'] + tabOrder: ['doc1'], }, [Constants.EDITOR_KEY_SECONDARY]: { ...defaultState.editors[Constants.EDITOR_KEY_SECONDARY], activeDocumentId: null, documents: {}, recentTabs: [], - tabOrder: [] - } - } + tabOrder: [], + }, + }, }; const endState = fixupTabGroups(startingState); expect(endState.activeEditor).toBe(Constants.EDITOR_KEY_PRIMARY); @@ -1000,21 +1061,21 @@ describe('Editor reducer utility function tests', () => { ...defaultState.editors[Constants.EDITOR_KEY_PRIMARY], activeDocumentId: 'doc1', documents: { - 'doc1': {} + doc1: {}, }, recentTabs: ['doc1'], - tabOrder: ['doc1'] + tabOrder: ['doc1'], }, [Constants.EDITOR_KEY_SECONDARY]: { ...defaultState.editors[Constants.EDITOR_KEY_SECONDARY], activeDocumentId: 'doc2', documents: { - 'doc2': {} + doc2: {}, }, recentTabs: ['doc2'], - tabOrder: ['doc2'] - } - } + tabOrder: ['doc2'], + }, + }, }; const endState = fixupTabGroups(startingState); expect(endState).toEqual(startingState); @@ -1029,15 +1090,15 @@ describe('Editor reducer utility function tests', () => { [Constants.EDITOR_KEY_PRIMARY]: { activeDocumentId: 'doc2', documents: { - 'doc1': {}, - 'doc2': {}, - 'doc3': {} + doc1: {}, + doc2: {}, + doc3: {}, }, recentTabs: ['doc2', 'doc1', 'doc3'], - tabOrder: ['doc1', 'doc2', 'doc3'] + tabOrder: ['doc1', 'doc2', 'doc3'], }, - [Constants.EDITOR_KEY_SECONDARY]: {} - } + [Constants.EDITOR_KEY_SECONDARY]: {}, + }, }; const tabIdToDrop = 'doc1'; @@ -1074,16 +1135,16 @@ function initializeDefaultState() { activeDocumentId: null, documents: {}, tabOrder: [], - recentTabs: [] + recentTabs: [], }, [Constants.EDITOR_KEY_SECONDARY]: { activeDocumentId: null, documents: {}, tabOrder: [], - recentTabs: [] - } + recentTabs: [], + }, }, - docsWithPendingChanges: [] + docsWithPendingChanges: [], }; defaultState = deepCopySlow(DEFAULT_STATE); } diff --git a/packages/app/client/src/data/reducer/editor.ts b/packages/app/client/src/data/reducer/editor.ts index 7c6837df2..901ee66f5 100644 --- a/packages/app/client/src/data/reducer/editor.ts +++ b/packages/app/client/src/data/reducer/editor.ts @@ -32,9 +32,10 @@ // import { deepCopySlow } from '@bfemulator/app-shared'; + import * as Constants from '../../constants'; -import { EditorAction, EditorActions } from '../action/editorActions'; import { BotAction } from '../action/botActions'; +import { EditorAction, EditorActions } from '../action/editorActions'; import { getOtherTabGroup, tabGroupHasDocuments } from '../editorHelpers'; export interface EditorState { @@ -72,12 +73,15 @@ const DEFAULT_STATE: EditorState = { draggingTab: false, editors: { [Constants.EDITOR_KEY_PRIMARY]: getNewEditor(), - [Constants.EDITOR_KEY_SECONDARY]: getNewEditor() + [Constants.EDITOR_KEY_SECONDARY]: getNewEditor(), }, - docsWithPendingChanges: [] + docsWithPendingChanges: [], }; -export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction | BotAction): EditorState => { +export const editor = ( + state: EditorState = DEFAULT_STATE, + action: EditorAction | BotAction +): EditorState => { Object.freeze(state); switch (action.type) { @@ -87,11 +91,14 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction /** if the tab is being appended to the end of its own editor, just re-adjust tab order */ if (srcEditorKey === destEditorKey) { let tabOrder = [...state.editors[srcEditorKey].tabOrder]; - tabOrder = [...tabOrder.filter(docId => docId !== action.payload.documentId), action.payload.documentId]; + tabOrder = [ + ...tabOrder.filter(docId => docId !== action.payload.documentId), + action.payload.documentId, + ]; - let editorState: Editor = { + const editorState: Editor = { ...state.editors[srcEditorKey], - tabOrder: tabOrder + tabOrder, }; state = setEditorState(srcEditorKey, editorState, state); state = setDraggingTab(false, state); @@ -102,28 +109,44 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction * if the tab is being appended to another editor, * we need to modify both editors' docs, recent tabs, and tab order */ - const docToAppend = state.editors[srcEditorKey].documents[action.payload.documentId]; + const docToAppend = + state.editors[srcEditorKey].documents[action.payload.documentId]; // remove any trace of document from source editor - const srcEditor = removeDocumentFromTabGroup(state.editors[srcEditorKey], action.payload.documentId); + const srcEditor = removeDocumentFromTabGroup( + state.editors[srcEditorKey], + action.payload.documentId + ); // add the tab to the dest editor - const destTabOrder = [...state.editors[destEditorKey].tabOrder, action.payload.documentId]; - const destRecentTabs = [...state.editors[destEditorKey].recentTabs, action.payload.documentId]; - const destDocs = Object.assign({}, state.editors[destEditorKey].documents); + const destTabOrder = [ + ...state.editors[destEditorKey].tabOrder, + action.payload.documentId, + ]; + const destRecentTabs = [ + ...state.editors[destEditorKey].recentTabs, + action.payload.documentId, + ]; + const destDocs = { ...state.editors[destEditorKey].documents }; destDocs[action.payload.documentId] = docToAppend; const destEditor: Editor = { ...state.editors[destEditorKey], documents: destDocs, recentTabs: destRecentTabs, - tabOrder: destTabOrder + tabOrder: destTabOrder, }; - if (!tabGroupHasDocuments(srcEditor) && srcEditorKey === Constants.EDITOR_KEY_PRIMARY) { + if ( + !tabGroupHasDocuments(srcEditor) && + srcEditorKey === Constants.EDITOR_KEY_PRIMARY + ) { state = setNewPrimaryEditor(destEditor, state); } else { - state = setActiveEditor(!tabGroupHasDocuments(srcEditor) ? destEditorKey : state.activeEditor, state); + state = setActiveEditor( + !tabGroupHasDocuments(srcEditor) ? destEditorKey : state.activeEditor, + state + ); state = setEditorState(srcEditorKey, srcEditor, state); state = setEditorState(destEditorKey, destEditor, state); } @@ -138,11 +161,17 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction const { editorKey } = action.payload; // remove any trace of document from editor - const editor1 = removeDocumentFromTabGroup(state.editors[editorKey], action.payload.documentId); + const editor1 = removeDocumentFromTabGroup( + state.editors[editorKey], + action.payload.documentId + ); // close empty editor if there is another one able to take its place const newPrimaryEditorKey = getOtherTabGroup(editorKey); - if (!tabGroupHasDocuments(editor1) && state.editors[newPrimaryEditorKey]) { + if ( + !tabGroupHasDocuments(editor1) && + state.editors[newPrimaryEditorKey] + ) { // if the editor being closed is the primary editor, have the secondary editor become the primary const tmp: Editor = deepCopySlow(state.editors[newPrimaryEditorKey]); state = setNewPrimaryEditor(tmp, state); @@ -157,14 +186,14 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction return DEFAULT_STATE; } else { let newState: EditorState = { - ...state + ...state, }; - for (let key in state.editors) { + for (const key in state.editors) { if (!state.editors.hasOwnProperty(key)) { continue; } - let tabGroup = state.editors[key]; + const tabGroup = state.editors[key]; if (tabGroup) { let newTabOrder = [...tabGroup.tabOrder]; let newRecentTabs = [...tabGroup.recentTabs]; @@ -175,24 +204,28 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction if (document.isGlobal) { newDocs[documentId] = document; } else { - newTabOrder = newTabOrder.filter(documentIdArg => documentIdArg !== documentId); - newRecentTabs = newRecentTabs.filter(documentIdArg => documentIdArg !== documentId); + newTabOrder = newTabOrder.filter( + documentIdArg => documentIdArg !== documentId + ); + newRecentTabs = newRecentTabs.filter( + documentIdArg => documentIdArg !== documentId + ); } }); - let newTabGroup: Editor = { + const newTabGroup: Editor = { activeDocumentId: newRecentTabs[0] || null, documents: newDocs, recentTabs: newRecentTabs, - tabOrder: newTabOrder + tabOrder: newTabOrder, }; newState = { ...newState, editors: { ...newState.editors, - [key]: newTabGroup - } + [key]: newTabGroup, + }, }; } } @@ -207,15 +240,18 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction const otherTabGroup = getOtherTabGroup(editorKey); // if the document is already in another tab group, focus that one - if (tabGroupHasDocuments(state.editors[otherTabGroup]) - && state.editors[otherTabGroup].documents[action.payload.documentId]) { - const recentTabs = [...state.editors[otherTabGroup].recentTabs] - .filter(docId => docId !== action.payload.documentId); + if ( + tabGroupHasDocuments(state.editors[otherTabGroup]) && + state.editors[otherTabGroup].documents[action.payload.documentId] + ) { + const recentTabs = [...state.editors[otherTabGroup].recentTabs].filter( + docId => docId !== action.payload.documentId + ); recentTabs.unshift(action.payload.documentId); const tabGroupState: Editor = { ...state.editors[otherTabGroup], activeDocumentId: action.payload.documentId, - recentTabs + recentTabs, }; state = setEditorState(otherTabGroup, tabGroupState, state); state = setActiveEditor(otherTabGroup, state); @@ -226,19 +262,30 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction if (state.editors[editorKey].documents[action.payload.documentId]) { newTabOrder = [...state.editors[editorKey].tabOrder]; } else { - const activeDocumentId = state.editors[state.activeEditor].activeDocumentId; - const activeIndex = state.editors[editorKey].tabOrder.indexOf(activeDocumentId); + const activeDocumentId = + state.editors[state.activeEditor].activeDocumentId; + const activeIndex = state.editors[editorKey].tabOrder.indexOf( + activeDocumentId + ); if (activeIndex != null && activeIndex !== -1) { - state.editors[editorKey].tabOrder.splice(activeIndex + 1, 0, action.payload.documentId); + state.editors[editorKey].tabOrder.splice( + activeIndex + 1, + 0, + action.payload.documentId + ); newTabOrder = [...state.editors[editorKey].tabOrder]; } else { - newTabOrder = [...state.editors[editorKey].tabOrder, action.payload.documentId]; + newTabOrder = [ + ...state.editors[editorKey].tabOrder, + action.payload.documentId, + ]; } } // move document to top of recent tabs - const newRecentTabs = [...state.editors[editorKey].recentTabs] - .filter(docId => docId !== action.payload.documentId); + const newRecentTabs = [...state.editors[editorKey].recentTabs].filter( + docId => docId !== action.payload.documentId + ); newRecentTabs.unshift(action.payload.documentId); // add document to tab group @@ -256,7 +303,7 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction activeDocumentId: action.payload.documentId, documents: newDocs, recentTabs: newRecentTabs, - tabOrder: newTabOrder + tabOrder: newTabOrder, }; state = setEditorState(editorKey, editorState, state); state = setActiveEditor(editorKey, state); @@ -291,14 +338,19 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction case EditorActions.setActiveTab: { Constants.EditorKeys.forEach(editorKey => { - if (state.editors[editorKey] && state.editors[editorKey].documents[action.payload.documentId]) { - const recentTabs = state.editors[editorKey].recentTabs.filter(tabId => tabId !== action.payload.documentId); + if ( + state.editors[editorKey] && + state.editors[editorKey].documents[action.payload.documentId] + ) { + const recentTabs = state.editors[editorKey].recentTabs.filter( + tabId => tabId !== action.payload.documentId + ); recentTabs.unshift(action.payload.documentId); const editorState = { ...state.editors[editorKey], activeDocumentId: action.payload.documentId, - recentTabs + recentTabs, }; state = setEditorState(editorKey, editorState, state); state = setActiveEditor(editorKey, state); @@ -309,14 +361,17 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction case EditorActions.setDirtyFlag: { Constants.EditorKeys.forEach(editorKey => { - if (state.editors[editorKey] && state.editors[editorKey].documents[action.payload.documentId]) { + if ( + state.editors[editorKey] && + state.editors[editorKey].documents[action.payload.documentId] + ) { const newDocs = deepCopySlow(state.editors[editorKey].documents); const docToSet = newDocs[action.payload.documentId]; docToSet.dirty = action.payload.dirty; const editorState: Editor = { ...state.editors[editorKey], - documents: newDocs + documents: newDocs, }; state = setEditorState(editorKey, editorState, state); } @@ -328,14 +383,19 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction const { srcEditorKey } = action.payload; const { destEditorKey } = action.payload; - const docToAppend = state.editors[srcEditorKey].documents[action.payload.documentId]; + const docToAppend = + state.editors[srcEditorKey].documents[action.payload.documentId]; // remove any trace of document from source editor - const srcEditor = removeDocumentFromTabGroup(state.editors[srcEditorKey], action.payload.documentId); + const srcEditor = removeDocumentFromTabGroup( + state.editors[srcEditorKey], + action.payload.documentId + ); // add the document to the dest editor - const destEditor: Editor = state.editors[destEditorKey] ? - deepCopySlow(state.editors[destEditorKey]) : getNewEditor(); + const destEditor: Editor = state.editors[destEditorKey] + ? deepCopySlow(state.editors[destEditorKey]) + : getNewEditor(); const destTabOrder = [...destEditor.tabOrder, action.payload.documentId]; const destRecentTabs = [...destEditor.recentTabs]; destRecentTabs.unshift(action.payload.documentId); @@ -362,16 +422,20 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction if (srcEditorKey === destEditorKey) { // only change tab order const tabOrder = [...state.editors[srcEditorKey].tabOrder]; - const srcTabIndex = tabOrder.findIndex(docId => docId === action.payload.srcTabId); - const destTabIndex1 = tabOrder.findIndex(docId => docId === action.payload.destTabId); + const srcTabIndex = tabOrder.findIndex( + docId => docId === action.payload.srcTabId + ); + const destTabIndex1 = tabOrder.findIndex( + docId => docId === action.payload.destTabId + ); const destTab = tabOrder[destTabIndex1]; tabOrder[destTabIndex1] = tabOrder[srcTabIndex]; tabOrder[srcTabIndex] = destTab; - let editorState = { + const editorState = { ...state.editors[srcEditorKey], - tabOrder + tabOrder, }; state = setEditorState(srcEditorKey, editorState, state); @@ -379,26 +443,44 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction } /** swapping tab into a different tab group */ - const docToSwap = state.editors[srcEditorKey].documents[action.payload.srcTabId]; + const docToSwap = + state.editors[srcEditorKey].documents[action.payload.srcTabId]; // remove any trace of document from source editor - const srcEditor = removeDocumentFromTabGroup(state.editors[srcEditorKey], action.payload.srcTabId); + const srcEditor = removeDocumentFromTabGroup( + state.editors[srcEditorKey], + action.payload.srcTabId + ); // add the document to the destination tab group const destEditor: Editor = deepCopySlow(state.editors[destEditorKey]); destEditor.documents[action.payload.srcTabId] = docToSwap; - const destRecentTabs = [...destEditor.recentTabs, action.payload.srcTabId]; + const destRecentTabs = [ + ...destEditor.recentTabs, + action.payload.srcTabId, + ]; destEditor.recentTabs = destRecentTabs; // insert before the destination tab's position - const destTabIndex = destEditor.tabOrder.findIndex(docId => docId === action.payload.destTabId); - const destTabOrder = [...destEditor.tabOrder - .splice(0, destTabIndex + 1), action.payload.srcTabId, ...destEditor.tabOrder]; + const destTabIndex = destEditor.tabOrder.findIndex( + docId => docId === action.payload.destTabId + ); + const destTabOrder = [ + ...destEditor.tabOrder.splice(0, destTabIndex + 1), + action.payload.srcTabId, + ...destEditor.tabOrder, + ]; destEditor.tabOrder = destTabOrder; - if (!tabGroupHasDocuments(srcEditor) && srcEditorKey === Constants.EDITOR_KEY_PRIMARY) { + if ( + !tabGroupHasDocuments(srcEditor) && + srcEditorKey === Constants.EDITOR_KEY_PRIMARY + ) { state = setNewPrimaryEditor(destEditor, state); } else { - state = setActiveEditor(!tabGroupHasDocuments(srcEditor) ? destEditorKey : state.activeEditor, state); + state = setActiveEditor( + !tabGroupHasDocuments(srcEditor) ? destEditorKey : state.activeEditor, + state + ); state = setEditorState(srcEditorKey, srcEditor, state); state = setEditorState(destEditorKey, destEditor, state); } @@ -411,16 +493,20 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction } case EditorActions.addDocPendingChange: { - let docsPendingChange = [ - ...state.docsWithPendingChanges.filter(d => d !== action.payload.documentId), - action.payload.documentId + const docsPendingChange = [ + ...state.docsWithPendingChanges.filter( + d => d !== action.payload.documentId + ), + action.payload.documentId, ]; state = setDocsWithPendingChanges(docsPendingChange, state); break; } case EditorActions.removeDocPendingChange: { - let docsPendingChange = [...state.docsWithPendingChanges].filter(d => d !== action.payload.documentId); + const docsPendingChange = [...state.docsWithPendingChanges].filter( + d => d !== action.payload.documentId + ); state = setDocsWithPendingChanges(docsPendingChange, state); break; } @@ -438,8 +524,12 @@ export const editor = (state: EditorState = DEFAULT_STATE, action: EditorAction const primaryDocs = { [tabToIsolate]: docToIsolate }; // move all documents but the one being dropped to the secondary tab group - const secondaryTabOrder = state.editors[primary].tabOrder.filter(tabId => tabId !== tabToIsolate); - const secondaryRecentTabs = state.editors[primary].recentTabs.filter(tabId => tabId !== tabToIsolate); + const secondaryTabOrder = state.editors[primary].tabOrder.filter( + tabId => tabId !== tabToIsolate + ); + const secondaryRecentTabs = state.editors[primary].recentTabs.filter( + tabId => tabId !== tabToIsolate + ); const secondaryDocs = state.editors[primary].documents; delete secondaryDocs[tabToIsolate]; @@ -472,47 +562,67 @@ function getNewEditor(): Editor { activeDocumentId: null, documents: {}, recentTabs: [], - tabOrder: [] + tabOrder: [], }; } /** Removes all trace of a document from a tab group and returns * the updated state, or a new editor if the tab group has no documents (empty) */ -export function removeDocumentFromTabGroup(tabGroup: Editor, documentId: string): Editor { - const newTabOrder = [...tabGroup.tabOrder].filter(docId => docId !== documentId); - const newRecentTabs = [...tabGroup.recentTabs].filter(docId => docId !== documentId); - const newDocs = Object.assign({}, tabGroup.documents); +export function removeDocumentFromTabGroup( + tabGroup: Editor, + documentId: string +): Editor { + const newTabOrder = [...tabGroup.tabOrder].filter( + docId => docId !== documentId + ); + const newRecentTabs = [...tabGroup.recentTabs].filter( + docId => docId !== documentId + ); + const newDocs = { ...tabGroup.documents }; delete newDocs[documentId]; const newActiveDocumentId = newRecentTabs[0] || null; - const newTabGroup: Editor = Object.keys(newDocs).length === 0 ? getNewEditor() : { - ...tabGroup, - activeDocumentId: newActiveDocumentId, - documents: newDocs, - recentTabs: newRecentTabs, - tabOrder: newTabOrder - }; + const newTabGroup: Editor = + Object.keys(newDocs).length === 0 + ? getNewEditor() + : { + ...tabGroup, + activeDocumentId: newActiveDocumentId, + documents: newDocs, + recentTabs: newRecentTabs, + tabOrder: newTabOrder, + }; return newTabGroup; } -export function setEditorState(editorKey: string, editorState: Editor, state: EditorState): EditorState { - let newState = deepCopySlow(state); +export function setEditorState( + editorKey: string, + editorState: Editor, + state: EditorState +): EditorState { + const newState = deepCopySlow(state); newState.editors[editorKey] = editorState; return newState; } -export function setActiveEditor(editorKey: string, state: EditorState): EditorState { - let newState = deepCopySlow(state); +export function setActiveEditor( + editorKey: string, + state: EditorState +): EditorState { + const newState = deepCopySlow(state); newState.activeEditor = editorKey; return newState; } /** Sets a new primary editor, and resets the secondary editor */ -export function setNewPrimaryEditor(newPrimaryEditor: Editor, state: EditorState): EditorState { - let newState = deepCopySlow(state); +export function setNewPrimaryEditor( + newPrimaryEditor: Editor, + state: EditorState +): EditorState { + const newState = deepCopySlow(state); newState.editors[Constants.EDITOR_KEY_SECONDARY] = getNewEditor(); newState.editors[Constants.EDITOR_KEY_PRIMARY] = newPrimaryEditor; @@ -520,8 +630,11 @@ export function setNewPrimaryEditor(newPrimaryEditor: Editor, state: EditorState return newState; } -export function setDraggingTab(dragging: boolean, state: EditorState): EditorState { - let newState = deepCopySlow(state); +export function setDraggingTab( + dragging: boolean, + state: EditorState +): EditorState { + const newState = deepCopySlow(state); newState.draggingTab = dragging; return newState; @@ -529,13 +642,20 @@ export function setDraggingTab(dragging: boolean, state: EditorState): EditorSta /** Sets the secondary tab group as the primary if the primary is now empty */ export function fixupTabGroups(state: EditorState): EditorState { - if (!tabGroupHasDocuments(state.editors[Constants.EDITOR_KEY_PRIMARY]) - && tabGroupHasDocuments(state.editors[Constants.EDITOR_KEY_SECONDARY])) { - state = setNewPrimaryEditor(state.editors[Constants.EDITOR_KEY_SECONDARY], state); + if ( + !tabGroupHasDocuments(state.editors[Constants.EDITOR_KEY_PRIMARY]) && + tabGroupHasDocuments(state.editors[Constants.EDITOR_KEY_SECONDARY]) + ) { + state = setNewPrimaryEditor( + state.editors[Constants.EDITOR_KEY_SECONDARY], + state + ); } - if (state.activeEditor === Constants.EDITOR_KEY_SECONDARY - && !tabGroupHasDocuments(state.editors[Constants.EDITOR_KEY_SECONDARY])) { + if ( + state.activeEditor === Constants.EDITOR_KEY_SECONDARY && + !tabGroupHasDocuments(state.editors[Constants.EDITOR_KEY_SECONDARY]) + ) { state = setActiveEditor(Constants.EDITOR_KEY_PRIMARY, state); } @@ -543,8 +663,11 @@ export function fixupTabGroups(state: EditorState): EditorState { } /** Sets the list of docs with pending changes */ -export function setDocsWithPendingChanges(docs: string[], state: EditorState): EditorState { - let newState: EditorState = deepCopySlow(state); +export function setDocsWithPendingChanges( + docs: string[], + state: EditorState +): EditorState { + const newState: EditorState = deepCopySlow(state); newState.docsWithPendingChanges = docs; return newState; diff --git a/packages/app/client/src/data/reducer/explorer.spec.ts b/packages/app/client/src/data/reducer/explorer.spec.ts index 5c063990a..8b8abcf62 100644 --- a/packages/app/client/src/data/reducer/explorer.spec.ts +++ b/packages/app/client/src/data/reducer/explorer.spec.ts @@ -32,12 +32,13 @@ // import { ExplorerAction, showExplorer } from '../action/explorerActions'; + import { explorer, ExplorerState } from './explorer'; describe('Explorer reducer tests', () => { const DEFAULT_STATE: ExplorerState = { showing: false, - sortSelectionByPanelId: {} + sortSelectionByPanelId: {}, }; it('should return unaltered state for non-matching action type', () => { diff --git a/packages/app/client/src/data/reducer/explorer.ts b/packages/app/client/src/data/reducer/explorer.ts index a9172187d..2715a4f20 100644 --- a/packages/app/client/src/data/reducer/explorer.ts +++ b/packages/app/client/src/data/reducer/explorer.ts @@ -35,7 +35,7 @@ import { CONNECTED_SERVICES_PANEL_ID, ExplorerAction, ExplorerActions, - ExplorerPayload + ExplorerPayload, } from '../action/explorerActions'; export interface ExplorerState { @@ -47,25 +47,26 @@ export declare type SortCriteria = string; const DEFAULT_STATE: ExplorerState = { showing: true, - sortSelectionByPanelId: {[CONNECTED_SERVICES_PANEL_ID]: 'name'} + sortSelectionByPanelId: { [CONNECTED_SERVICES_PANEL_ID]: 'name' }, }; -export function explorer(state: ExplorerState = DEFAULT_STATE, action: ExplorerAction) - : ExplorerState { - +export function explorer( + state: ExplorerState = DEFAULT_STATE, + action: ExplorerAction +): ExplorerState { switch (action.type) { - case ExplorerActions.Show: state = { ...state, showing: action.payload.show }; break; - case ExplorerActions.Sort: + case ExplorerActions.Sort: { const sortSelectionByPanelId = { ...state.sortSelectionByPanelId, - ...action.payload.sortSelectionByPanelId + ...action.payload.sortSelectionByPanelId, }; state = { ...state, sortSelectionByPanelId }; break; + } default: break; diff --git a/packages/app/client/src/data/reducer/navBar.spec.ts b/packages/app/client/src/data/reducer/navBar.spec.ts index a60a76359..8de065ec9 100644 --- a/packages/app/client/src/data/reducer/navBar.spec.ts +++ b/packages/app/client/src/data/reducer/navBar.spec.ts @@ -32,11 +32,12 @@ // import { NavBarAction, select } from '../action/navBarActions'; + import { navBar, NavBarState } from './navBar'; describe('NavBar reducer unit tests', () => { const DEFAULT_STATE: NavBarState = { - selection: null + selection: null, }; it('should return unaltered state for non-matching action type', () => { diff --git a/packages/app/client/src/data/reducer/navBar.ts b/packages/app/client/src/data/reducer/navBar.ts index 643708440..99f5df963 100644 --- a/packages/app/client/src/data/reducer/navBar.ts +++ b/packages/app/client/src/data/reducer/navBar.ts @@ -39,15 +39,18 @@ export interface NavBarState { } const DEFAULT_STATE: NavBarState = { - selection: constants.NAVBAR_BOT_EXPLORER + selection: constants.NAVBAR_BOT_EXPLORER, }; -export function navBar(state: NavBarState = DEFAULT_STATE, action: NavBarAction): NavBarState { +export function navBar( + state: NavBarState = DEFAULT_STATE, + action: NavBarAction +): NavBarState { switch (action.type) { case NavBarActions.select: { state = { ...state, - selection: action.payload.selection + selection: action.payload.selection, }; break; } diff --git a/packages/app/client/src/data/reducer/notification.spec.ts b/packages/app/client/src/data/reducer/notification.spec.ts index efc288c77..df9a55739 100644 --- a/packages/app/client/src/data/reducer/notification.spec.ts +++ b/packages/app/client/src/data/reducer/notification.spec.ts @@ -31,16 +31,23 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import notification, { NotificationState } from './notification'; -import { finishAdd, finishRemove, finishClear, NotificationAction } from '../action/notificationActions'; import { newNotification } from '@bfemulator/app-shared'; +import { + finishAdd, + finishRemove, + finishClear, + NotificationAction, +} from '../action/notificationActions'; + +import notification, { NotificationState } from './notification'; + describe('Notification reducer tests', () => { let defaultState: NotificationState; beforeEach(() => { defaultState = { - allIds: [] + allIds: [], }; }); @@ -68,7 +75,7 @@ describe('Notification reducer tests', () => { test('finishRemove', () => { const idToRemove = 'id1'; const startingState: NotificationState = { - allIds: [idToRemove, 'id2'] + allIds: [idToRemove, 'id2'], }; const action: NotificationAction = finishRemove(idToRemove); let endingState = notification(startingState, action); @@ -85,7 +92,7 @@ describe('Notification reducer tests', () => { test('finishClear', () => { const startingState: NotificationState = { - allIds: ['id1', 'id2', 'id3'] + allIds: ['id1', 'id2', 'id3'], }; const action: NotificationAction = finishClear(); const endingState = notification(startingState, action); diff --git a/packages/app/client/src/data/reducer/notification.ts b/packages/app/client/src/data/reducer/notification.ts index a41559a6d..ded2c05e0 100644 --- a/packages/app/client/src/data/reducer/notification.ts +++ b/packages/app/client/src/data/reducer/notification.ts @@ -31,17 +31,23 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { NotificationAction, NotificationActions } from '../action/notificationActions'; +import { + NotificationAction, + NotificationActions, +} from '../action/notificationActions'; export interface NotificationState { allIds: string[]; } const DEFAULT_STATE: NotificationState = { - allIds: [] + allIds: [], }; -export function notification(state: NotificationState = DEFAULT_STATE, action: NotificationAction): NotificationState { +export function notification( + state: NotificationState = DEFAULT_STATE, + action: NotificationAction +): NotificationState { switch (action.type) { case NotificationActions.finishAdd: { const { id: idToAdd } = action.payload.notification; @@ -52,7 +58,7 @@ export function notification(state: NotificationState = DEFAULT_STATE, action: N allIds = state.allIds; } state = { - allIds + allIds, }; break; } @@ -61,14 +67,14 @@ export function notification(state: NotificationState = DEFAULT_STATE, action: N const { id: idToRemove } = action.payload; const allIds = state.allIds.filter(id => id !== idToRemove); state = { - allIds + allIds, }; break; } case NotificationActions.finishClear: { state = { - allIds: [] + allIds: [], }; break; } diff --git a/packages/app/client/src/data/reducer/presentation.spec.ts b/packages/app/client/src/data/reducer/presentation.spec.ts index fe9d2556f..6be29aa8e 100644 --- a/packages/app/client/src/data/reducer/presentation.spec.ts +++ b/packages/app/client/src/data/reducer/presentation.spec.ts @@ -31,27 +31,31 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { disable, enable, PresentationAction } from '../action/presentationActions'; +import { + disable, + enable, + PresentationAction, +} from '../action/presentationActions'; + import { presentation, PresentationState } from './presentation'; jest.mock('../../ui/dialogs', () => ({ - AzureLoginPromptDialogContainer: function mock() { - return undefined; - }, - AzureLoginSuccessDialogContainer: function mock() { - return undefined; - }, - BotCreationDialog: function mock() { - return undefined; - }, - DialogService: { showDialog: () => Promise.resolve(true) }, - SecretPromptDialog: function mock() { - return undefined; - } - } -)); + AzureLoginPromptDialogContainer: function mock() { + return undefined; + }, + AzureLoginSuccessDialogContainer: function mock() { + return undefined; + }, + BotCreationDialog: function mock() { + return undefined; + }, + DialogService: { showDialog: () => Promise.resolve(true) }, + SecretPromptDialog: function mock() { + return undefined; + }, +})); describe('Presentation reducer tests', () => { const DEFAULT_STATE: PresentationState = { - enabled: null + enabled: null, }; it('should return unaltered state for non-matching action type', () => { diff --git a/packages/app/client/src/data/reducer/presentation.ts b/packages/app/client/src/data/reducer/presentation.ts index 757586941..45852d963 100644 --- a/packages/app/client/src/data/reducer/presentation.ts +++ b/packages/app/client/src/data/reducer/presentation.ts @@ -31,20 +31,26 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl'; -import { PresentationAction, PresentationActions } from '../action/presentationActions'; import { SharedConstants } from '@bfemulator/app-shared'; +import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl'; +import { + PresentationAction, + PresentationActions, +} from '../action/presentationActions'; + export interface PresentationState { enabled: boolean; } const DEFAULT_STATE: PresentationState = { - enabled: false + enabled: false, }; -export const presentation = (state: PresentationState = DEFAULT_STATE, action: PresentationAction) - : PresentationState => { +export const presentation = ( + state: PresentationState = DEFAULT_STATE, + action: PresentationAction +): PresentationState => { switch (action.type) { case PresentationActions.disable: state = setEnabled(false, state); @@ -61,11 +67,17 @@ export const presentation = (state: PresentationState = DEFAULT_STATE, action: P return state; }; -function setEnabled(enabled: boolean, state: PresentationState): PresentationState { - let newState = Object.assign({}, state); +function setEnabled( + enabled: boolean, + state: PresentationState +): PresentationState { + const newState = { ...state }; newState.enabled = enabled; - CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.SetFullscreen, enabled); + CommandServiceImpl.remoteCall( + SharedConstants.Commands.Electron.SetFullscreen, + enabled + ); return newState; } diff --git a/packages/app/client/src/data/reducer/progressIndicator.ts b/packages/app/client/src/data/reducer/progressIndicator.ts index b35b924cd..a74737870 100644 --- a/packages/app/client/src/data/reducer/progressIndicator.ts +++ b/packages/app/client/src/data/reducer/progressIndicator.ts @@ -1,8 +1,40 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { CANCEL_CURRENT_PROCESS, ProgressIndicatorAction, ProgressIndicatorPayload, - UPDATE_PROGRESS_INDICATOR + UPDATE_PROGRESS_INDICATOR, } from '../action/progressIndicatorActions'; export interface ProgressIndicatorState { @@ -14,20 +46,21 @@ export interface ProgressIndicatorState { export const initialState: ProgressIndicatorState = { progress: 0, label: '', - canceled: false + canceled: false, }; export function progressIndicator( state: ProgressIndicatorState = initialState, - action: ProgressIndicatorAction): ProgressIndicatorState { - + action: ProgressIndicatorAction +): ProgressIndicatorState { switch (action.type) { - case UPDATE_PROGRESS_INDICATOR: + case UPDATE_PROGRESS_INDICATOR: { const { label, progress } = action.payload; return { ...state, label, progress }; + } case CANCEL_CURRENT_PROCESS: - return {...state, canceled: true}; + return { ...state, canceled: true }; default: return state; diff --git a/packages/app/client/src/data/reducer/resourcesReducer.ts b/packages/app/client/src/data/reducer/resourcesReducer.ts index 634875b04..203b4f6e0 100644 --- a/packages/app/client/src/data/reducer/resourcesReducer.ts +++ b/packages/app/client/src/data/reducer/resourcesReducer.ts @@ -1,11 +1,44 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { IFileService } from 'botframework-config/lib/schema'; + import { CHAT_FILES_UPDATED, + CHATS_DIRECTORY_UPDATED, EDIT_RESOURCE, ResourcesAction, - TRANSCRIPTS_UPDATED, TRANSCRIPTS_DIRECTORY_UPDATED, - CHATS_DIRECTORY_UPDATED + TRANSCRIPTS_UPDATED, } from '../action/resourcesAction'; export interface ResourcesState { @@ -21,12 +54,17 @@ const initialState: ResourcesState = { transcriptsPath: '', chats: [], chatsPath: '', - resourceToRename: null + resourceToRename: null, }; -declare type ResourceActionType = ResourcesAction; +declare type ResourceActionType = ResourcesAction< + IFileService | IFileService[] | string +>; -export function resources(state: ResourcesState = initialState, action: ResourceActionType): ResourcesState { +export function resources( + state: ResourcesState = initialState, + action: ResourceActionType +): ResourcesState { switch (action.type) { case TRANSCRIPTS_UPDATED: return { ...state, transcripts: action.payload as IFileService[] }; diff --git a/packages/app/client/src/data/reducer/themeReducer.ts b/packages/app/client/src/data/reducer/themeReducer.ts index de36cf60d..d20a8fdad 100644 --- a/packages/app/client/src/data/reducer/themeReducer.ts +++ b/packages/app/client/src/data/reducer/themeReducer.ts @@ -1,3 +1,35 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { SwitchThemePayload, ThemeAction } from '../action/themeActions'; export interface ThemeState { @@ -6,11 +38,17 @@ export interface ThemeState { themeComponents: string[]; } -export const initialState: ThemeState = { themeName: null, themeHref: null, themeComponents: [] }; +export const initialState: ThemeState = { + themeName: null, + themeHref: null, + themeComponents: [], +}; -export function theme(state: ThemeState = initialState, action: ThemeAction): ThemeState { +export function theme( + state: ThemeState = initialState, + action: ThemeAction +): ThemeState { switch (action.type) { - case 'switchTheme': return { ...state, ...action.payload }; diff --git a/packages/app/client/src/data/sagas/azureAuthSaga.spec.ts b/packages/app/client/src/data/sagas/azureAuthSaga.spec.ts index 8bfc7d6b4..756805842 100644 --- a/packages/app/client/src/data/sagas/azureAuthSaga.spec.ts +++ b/packages/app/client/src/data/sagas/azureAuthSaga.spec.ts @@ -1,39 +1,79 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +import { CommandRegistryImpl } from '@bfemulator/sdk-shared'; +import { SharedConstants } from '@bfemulator/app-shared'; +import { ServiceTypes } from 'botframework-config/lib/schema'; + +import { store } from '../store'; +import { + azureArmTokenDataChanged, + beginAzureAuthWorkflow, +} from '../action/azureAuthActions'; +import { + AzureLoginFailedDialogContainer, + AzureLoginPromptDialogContainer, + AzureLoginSuccessDialogContainer, + DialogService, +} from '../../ui/dialogs'; +import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl'; +import { registerCommands } from '../../commands/uiCommands'; + +import { azureAuthSagas } from './azureAuthSaga'; + jest.mock('../../ui/dialogs', () => ({ AzureLoginPromptDialogContainer: () => undefined, AzureLoginSuccessDialogContainer: () => undefined, BotCreationDialog: () => undefined, DialogService: { showDialog: () => Promise.resolve(true) }, PostMigrationDialogContainer: () => undefined, - SecretPromptDialog: () => undefined + SecretPromptDialog: () => undefined, })); jest.mock('../../platform/commands/commandServiceImpl', () => ({ CommandServiceImpl: { - remoteCall: () => Promise.resolve(true) - } + remoteCall: () => Promise.resolve(true), + }, })); -import { store } from '../store'; -import { azureArmTokenDataChanged, beginAzureAuthWorkflow } from '../action/azureAuthActions'; -import { azureAuthSagas } from './azureAuthSaga'; -import { - AzureLoginFailedDialogContainer, - AzureLoginPromptDialogContainer, - AzureLoginSuccessDialogContainer, - DialogService -} from '../../ui/dialogs'; -import { CommandRegistryImpl } from '@bfemulator/sdk-shared'; -import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl'; -import { SharedConstants } from '@bfemulator/app-shared'; -import { registerCommands } from '../../commands/uiCommands'; -import { ServiceTypes } from 'botframework-config/lib/schema'; - describe('The azureAuthSaga', () => { it('should contain a single step if the token in the store is valid', () => { store.dispatch(azureArmTokenDataChanged('a valid access_token')); - const it = azureAuthSagas().next().value.FORK.args[1](); + const it = azureAuthSagas() + .next() + .value.FORK.args[1](); let val = undefined; let ct = 0; + // eslint-disable-next-line no-constant-condition while (true) { const next = it.next(val); if (next.done) { @@ -58,18 +98,21 @@ describe('The azureAuthSaga', () => { it('should contain just 2 steps when the Azure login dialog prompt is canceled', async () => { store.dispatch(azureArmTokenDataChanged('')); + // @ts-ignore DialogService.showDialog = () => Promise.resolve(false); const it = azureAuthSagas() .next() - .value - .FORK - .args[1](beginAzureAuthWorkflow( - AzureLoginPromptDialogContainer, - { serviceType: ServiceTypes.Luis }, - AzureLoginSuccessDialogContainer, - AzureLoginFailedDialogContainer)); + .value.FORK.args[1]( + beginAzureAuthWorkflow( + AzureLoginPromptDialogContainer, + { serviceType: ServiceTypes.Luis }, + AzureLoginSuccessDialogContainer, + AzureLoginFailedDialogContainer + ) + ); let val = undefined; let ct = 0; + // eslint-disable-next-line no-constant-condition while (true) { const next = it.next(val); if (next.done) { @@ -91,20 +134,23 @@ describe('The azureAuthSaga', () => { it('should contain 4 steps when the Azure login dialog prompt is confirmed but auth fails', async () => { store.dispatch(azureArmTokenDataChanged('')); + // @ts-ignore DialogService.showDialog = () => Promise.resolve(1); (CommandServiceImpl as any).remoteCall = () => Promise.resolve(false); const it = azureAuthSagas() .next() - .value - .FORK - .args[1](beginAzureAuthWorkflow( - AzureLoginPromptDialogContainer, - { serviceType: ServiceTypes.Luis }, - AzureLoginSuccessDialogContainer, - AzureLoginFailedDialogContainer)); + .value.FORK.args[1]( + beginAzureAuthWorkflow( + AzureLoginPromptDialogContainer, + { serviceType: ServiceTypes.Luis }, + AzureLoginSuccessDialogContainer, + AzureLoginFailedDialogContainer + ) + ); let val = undefined; let ct = 0; const remoteCallSpy = jest.spyOn(CommandServiceImpl, 'remoteCall'); + // eslint-disable-next-line no-constant-condition while (true) { const next = it.next(val); if (next.done) { @@ -127,7 +173,9 @@ describe('The azureAuthSaga', () => { if (ct === 2) { // Login was unsuccessful expect(val).toBe(false); - expect(remoteCallSpy).toHaveBeenCalledWith([SharedConstants.Commands.Azure.RetrieveArmToken]); + expect(remoteCallSpy).toHaveBeenCalledWith([ + SharedConstants.Commands.Azure.RetrieveArmToken, + ]); } } } @@ -138,10 +186,12 @@ describe('The azureAuthSaga', () => { it('should contain 6 steps when the Azure login dialog prompt is confirmed and auth succeeds', async () => { store.dispatch(azureArmTokenDataChanged('')); + // @ts-ignore DialogService.showDialog = () => Promise.resolve(1); (CommandServiceImpl as any).remoteCall = args => { switch (args[0]) { case SharedConstants.Commands.Azure.RetrieveArmToken: + // eslint-disable-next-line typescript/camelcase return Promise.resolve({ access_token: 'a valid access_token' }); case SharedConstants.Commands.Azure.PersistAzureLoginChanged: @@ -153,16 +203,18 @@ describe('The azureAuthSaga', () => { }; const it = azureAuthSagas() .next() - .value - .FORK - .args[1](beginAzureAuthWorkflow( - AzureLoginPromptDialogContainer, - { serviceType: ServiceTypes.Luis }, - AzureLoginSuccessDialogContainer, - AzureLoginFailedDialogContainer)); + .value.FORK.args[1]( + beginAzureAuthWorkflow( + AzureLoginPromptDialogContainer, + { serviceType: ServiceTypes.Luis }, + AzureLoginSuccessDialogContainer, + AzureLoginFailedDialogContainer + ) + ); let val = undefined; let ct = 0; const remoteCallSpy = jest.spyOn(CommandServiceImpl, 'remoteCall'); + // eslint-disable-next-line no-constant-condition while (true) { const next = it.next(val); if (next.done) { @@ -185,10 +237,15 @@ describe('The azureAuthSaga', () => { if (ct === 2) { // Login was successful expect(val.access_token).toBe('a valid access_token'); - expect(remoteCallSpy).toHaveBeenCalledWith([SharedConstants.Commands.Azure.RetrieveArmToken]); + expect(remoteCallSpy).toHaveBeenCalledWith([ + SharedConstants.Commands.Azure.RetrieveArmToken, + ]); } else if (ct === 4) { expect(val.persistLogin).toBe(true); - expect(remoteCallSpy).toHaveBeenCalledWith([SharedConstants.Commands.Azure.PersistAzureLoginChanged, 1]); + expect(remoteCallSpy).toHaveBeenCalledWith([ + SharedConstants.Commands.Azure.PersistAzureLoginChanged, + 1, + ]); } } } else if ('PUT' in val) { @@ -197,7 +254,9 @@ describe('The azureAuthSaga', () => { ct++; } expect(ct).toBe(6); - expect(store.getState().azureAuth.access_token).toBe('a valid access_token'); + expect(store.getState().azureAuth.access_token).toBe( + 'a valid access_token' + ); }); }); }); diff --git a/packages/app/client/src/data/sagas/azureAuthSaga.ts b/packages/app/client/src/data/sagas/azureAuthSaga.ts index 7fef7e48c..c93c8d088 100644 --- a/packages/app/client/src/data/sagas/azureAuthSaga.ts +++ b/packages/app/client/src/data/sagas/azureAuthSaga.ts @@ -30,35 +30,56 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { call, ForkEffect, put, select, takeEvery } from 'redux-saga/effects'; +import { SharedConstants } from '@bfemulator/app-shared'; + +import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl'; +import { DialogService } from '../../ui/dialogs'; import { AZURE_BEGIN_AUTH_WORKFLOW, azureArmTokenDataChanged, AzureAuthAction, - AzureAuthWorkflow + AzureAuthWorkflow, } from '../action/azureAuthActions'; -import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl'; -import { SharedConstants } from '@bfemulator/app-shared'; -import { RootState } from '../store'; -import { DialogService } from '../../ui/dialogs'; import { AzureAuthState } from '../reducer/azureAuthReducer'; +import { RootState } from '../store'; + +import { call, ForkEffect, put, select, takeEvery } from 'redux-saga/effects'; const getArmTokenFromState = (state: RootState) => state.azureAuth; -export function* getArmToken(action: AzureAuthAction): IterableIterator { +export function* getArmToken( + action: AzureAuthAction +): IterableIterator { let azureAuth: AzureAuthState = yield select(getArmTokenFromState); if (azureAuth.access_token) { return azureAuth; } - const result = yield DialogService.showDialog(action.payload.promptDialog, action.payload.promptDialogProps); - if (result !== 1) { // Result must be 1 which is a confirmation to sign in to Azure + const result = yield DialogService.showDialog( + action.payload.promptDialog, + action.payload.promptDialogProps + ); + if (result !== 1) { + // Result must be 1 which is a confirmation to sign in to Azure return result; } - const { RetrieveArmToken, PersistAzureLoginChanged } = SharedConstants.Commands.Azure; - azureAuth = yield call(CommandServiceImpl.remoteCall.bind(CommandServiceImpl), RetrieveArmToken); + const { + RetrieveArmToken, + PersistAzureLoginChanged, + } = SharedConstants.Commands.Azure; + azureAuth = yield call( + CommandServiceImpl.remoteCall.bind(CommandServiceImpl), + RetrieveArmToken + ); if (azureAuth && !('error' in azureAuth)) { - const persistLogin = yield DialogService.showDialog(action.payload.loginSuccessDialog, azureAuth); - yield call(CommandServiceImpl.remoteCall.bind(CommandServiceImpl), PersistAzureLoginChanged, persistLogin); + const persistLogin = yield DialogService.showDialog( + action.payload.loginSuccessDialog, + azureAuth + ); + yield call( + CommandServiceImpl.remoteCall.bind(CommandServiceImpl), + PersistAzureLoginChanged, + persistLogin + ); } else { yield DialogService.showDialog(action.payload.loginFailedDialog); } diff --git a/packages/app/client/src/data/sagas/botSagas.spec.ts b/packages/app/client/src/data/sagas/botSagas.spec.ts index c068e2ca9..7f527d4e3 100644 --- a/packages/app/client/src/data/sagas/botSagas.spec.ts +++ b/packages/app/client/src/data/sagas/botSagas.spec.ts @@ -31,31 +31,32 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { BotActions, botHashGenerated, SetActiveBotAction } from '../action/botActions'; import { BotConfigWithPath } from '@bfemulator/sdk-shared'; +import { SharedConstants } from '@bfemulator/app-shared'; + +import { + BotActions, + botHashGenerated, + SetActiveBotAction, +} from '../action/botActions'; +import { generateBotHash } from '../botHelpers'; + import { botSagas, browseForBot, editorSelector, - generateHashForActiveBot - } from './botSagas'; -import { - call, - put, - select, - takeEvery, - takeLatest - } from 'redux-saga/effects'; -import { generateBotHash } from '../botHelpers'; -import { SharedConstants } from '@bfemulator/app-shared'; + generateHashForActiveBot, +} from './botSagas'; import { refreshConversationMenu } from './sharedSagas'; +import { call, put, select, takeEvery, takeLatest } from 'redux-saga/effects'; + jest.mock('../../ui/dialogs', () => ({})); jest.mock('../store', () => ({ get store() { - return {}; - } + return {}; + }, })); const mockSharedConstants = SharedConstants; @@ -68,63 +69,51 @@ jest.mock('../../platform/commands/commandServiceImpl', () => ({ mockLocalCommandsCalled.push({ commandName, args: args }); switch (commandName) { - case mockSharedConstants.Commands.Bot.OpenBrowse: + case mockSharedConstants.Commands.Bot.OpenBrowse: return Promise.resolve(true); - default: + default: return Promise.resolve(true); - } + } }, - remoteCall: async (commandName: string, ... args: any[]) => { - mockRemoteCommandsCalled.push({ commandName, args: args}); + remoteCall: async (commandName: string, ...args: any[]) => { + mockRemoteCommandsCalled.push({ commandName, args: args }); return Promise.resolve(true); - } - } + }, + }, })); describe('The botSagas', () => { - beforeEach(() => { mockRemoteCommandsCalled = []; mockLocalCommandsCalled = []; }); it('should initialize the root saga', () => { - let gen = botSagas(); + const gen = botSagas(); const browseForBotYield = gen.next().value; expect(browseForBotYield).toEqual( - takeEvery( - BotActions.browse, - browseForBot - ) + takeEvery(BotActions.browse, browseForBot) ); const generateBotHashYield = gen.next().value; expect(generateBotHashYield).toEqual( - takeEvery( - BotActions.setActive, - generateHashForActiveBot - ) + takeEvery(BotActions.setActive, generateHashForActiveBot) ); const refreshConversationMenuYield = gen.next().value; expect(refreshConversationMenuYield).toEqual( takeLatest( - [ - BotActions.setActive, - BotActions.load, - BotActions.close - ], + [BotActions.setActive, BotActions.load, BotActions.close], refreshConversationMenu ) ); expect(gen.next().done).toBe(true); - }); it('should generate a hash for an active bot', () => { @@ -134,14 +123,14 @@ describe('The botSagas', () => { padlock: null, services: [], path: '/some/Path/something', - version: '0.1' + version: '0.1', }; - + const setActiveBotAction: SetActiveBotAction = { type: BotActions.setActive, payload: { - bot: botConfigPath - } + bot: botConfigPath, + }, }; const gen = generateHashForActiveBot(setActiveBotAction); const generatedHash = gen.next().value; diff --git a/packages/app/client/src/data/sagas/botSagas.ts b/packages/app/client/src/data/sagas/botSagas.ts index 29b834382..7ff34fad4 100644 --- a/packages/app/client/src/data/sagas/botSagas.ts +++ b/packages/app/client/src/data/sagas/botSagas.ts @@ -31,13 +31,26 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { call, ForkEffect, put, takeEvery, takeLatest } from 'redux-saga/effects'; -import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl'; import { SharedConstants } from '@bfemulator/app-shared'; -import { BotActions, botHashGenerated, SetActiveBotAction } from '../action/botActions'; + +import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl'; +import { + BotActions, + botHashGenerated, + SetActiveBotAction, +} from '../action/botActions'; import { generateBotHash } from '../botHelpers'; + import { refreshConversationMenu } from './sharedSagas'; +import { + call, + ForkEffect, + put, + takeEvery, + takeLatest, +} from 'redux-saga/effects'; + /** Opens up native open file dialog to browse for a .bot file */ export function* browseForBot(): IterableIterator { yield CommandServiceImpl.call(SharedConstants.Commands.Bot.OpenBrowse) @@ -45,7 +58,9 @@ export function* browseForBot(): IterableIterator { .catch(_err => null); } -export function* generateHashForActiveBot(action: SetActiveBotAction): IterableIterator { +export function* generateHashForActiveBot( + action: SetActiveBotAction +): IterableIterator { const { bot } = action.payload; const generatedHash = yield call(generateBotHash, bot); yield put(botHashGenerated(generatedHash)); @@ -55,11 +70,7 @@ export function* botSagas(): IterableIterator { yield takeEvery(BotActions.browse, browseForBot); yield takeEvery(BotActions.setActive, generateHashForActiveBot); yield takeLatest( - [ - BotActions.setActive, - BotActions.load, - BotActions.close - ], + [BotActions.setActive, BotActions.load, BotActions.close], refreshConversationMenu ); } diff --git a/packages/app/client/src/data/sagas/editorSagas.spec.ts b/packages/app/client/src/data/sagas/editorSagas.spec.ts index 1e0bec5b6..539ba5ba5 100644 --- a/packages/app/client/src/data/sagas/editorSagas.spec.ts +++ b/packages/app/client/src/data/sagas/editorSagas.spec.ts @@ -31,16 +31,23 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { call, put, select, takeEvery, takeLatest } from 'redux-saga/effects'; -import { checkActiveDocForPendingChanges, editorSagas, promptUserToReloadDocument } from './editorSagas'; -import { EditorActions, removeDocPendingChange } from '../action/editorActions'; import { SharedConstants } from '@bfemulator/app-shared'; + +import { EditorActions, removeDocPendingChange } from '../action/editorActions'; + +import { + checkActiveDocForPendingChanges, + editorSagas, + promptUserToReloadDocument, +} from './editorSagas'; import { refreshConversationMenu, editorSelector } from './sharedSagas'; +import { call, put, select, takeEvery, takeLatest } from 'redux-saga/effects'; + jest.mock('../store', () => ({ - get store() { - return {}; - } + get store() { + return {}; + }, })); jest.mock('../../ui/dialogs', () => ({})); @@ -48,145 +55,150 @@ jest.mock('../../ui/dialogs', () => ({})); const mockSharedConstants = SharedConstants; let mockRemoteCommandsCalled = []; let mockLocalCommandsCalled = []; -let mockMessageResponse = false; +const mockMessageResponse = false; jest.mock('../../platform/commands/commandServiceImpl', () => ({ - CommandServiceImpl: { - call: async (commandName: string, ...args: any[]) => { - mockLocalCommandsCalled.push({ commandName, args: args }); - }, - remoteCall: async (commandName: string, ...args: any[]) => { - mockRemoteCommandsCalled.push({ commandName, args: args }); - - switch (commandName) { - case mockSharedConstants.Commands.Electron.ShowMessageBox: - if (mockMessageResponse) { - return Promise.resolve(true); - } else { - return Promise.resolve(false); - } - default: - return Promise.resolve(true); - - } - } - } + CommandServiceImpl: { + call: async (commandName: string, ...args: any[]) => { + mockLocalCommandsCalled.push({ commandName, args: args }); + }, + remoteCall: async (commandName: string, ...args: any[]) => { + mockRemoteCommandsCalled.push({ commandName, args: args }); + + switch (commandName) { + case mockSharedConstants.Commands.Electron.ShowMessageBox: + if (mockMessageResponse) { + return Promise.resolve(true); + } else { + return Promise.resolve(false); + } + default: + return Promise.resolve(true); + } + }, + }, })); describe('The Editor Sagas', () => { - beforeEach(() => { - mockRemoteCommandsCalled = []; - mockLocalCommandsCalled = []; - }); - - it('should check the active doc for pending changes', () => { - const gen = checkActiveDocForPendingChanges(); - const stateData = gen.next().value; - - expect(stateData).toEqual(select(editorSelector)); - - const mockActiveDocId = 'doc1'; - const mockEditorState = { - editors: { - someEditor: { - activeDocumentId: mockActiveDocId - } - }, - activeEditor: 'someEditor', - docsWithPendingChanges: [mockActiveDocId] - }; - // should return the inner generator that we delegate to - const innerGen = gen.next(mockEditorState).value; - expect(innerGen).toEqual(call(promptUserToReloadDocument, mockActiveDocId)); - - expect(gen.next().done).toBe(true); - }); - - it('should prompt the user to reload the document when the file is chatdown', () => { - const mockChatFileName = 'doc1.chat'; - const options = { - buttons: ['Cancel', 'Reload'], - title: 'File change detected', - message: 'We have detected a change in this file on disk. Would you like to reload it in the Emulator?' - }; - const gen = promptUserToReloadDocument(mockChatFileName); - - gen.next(); - - const { ShowMessageBox } = SharedConstants.Commands.Electron; - expect(mockRemoteCommandsCalled).toHaveLength(1); - expect(mockRemoteCommandsCalled[0].commandName).toEqual(ShowMessageBox); - expect(mockRemoteCommandsCalled[0].args[0]).toEqual(options); - expect(gen.next(true).value).toEqual(put(removeDocPendingChange(mockChatFileName))); - - gen.next(); - - const { OpenChatFile } = SharedConstants.Commands.Emulator; - - expect(mockLocalCommandsCalled).toHaveLength(1); - expect(mockLocalCommandsCalled[0].commandName).toEqual(OpenChatFile); - expect(mockLocalCommandsCalled[0].args[0]).toBe(mockChatFileName); - expect(mockLocalCommandsCalled[0].args[1]).toBe(true); - expect(gen.next().done).toBe(true); - }); - - it('should prompt the user to reload the document when the file is a transcript', () => { - const mockTranscriptFile = 'doc2.transcript'; - const options = { - buttons: ['Cancel', 'Reload'], - title: 'File change detected', - message: 'We have detected a change in this file on disk. Would you like to reload it in the Emulator?' - }; - const gen = promptUserToReloadDocument(mockTranscriptFile); - - gen.next(); - - const { ShowMessageBox } = SharedConstants.Commands.Electron; - expect(mockRemoteCommandsCalled).toHaveLength(1); - expect(mockRemoteCommandsCalled[0].commandName).toEqual(ShowMessageBox); - expect(mockRemoteCommandsCalled[0].args[0]).toEqual(options); - expect(gen.next(true).value).toEqual(put(removeDocPendingChange(mockTranscriptFile))); - gen.next(); - - const { ReloadTranscript } = SharedConstants.Commands.Emulator; - - expect(mockLocalCommandsCalled).toHaveLength(1); - expect(mockLocalCommandsCalled[0].commandName).toEqual(ReloadTranscript); - expect(mockLocalCommandsCalled[0].args[0]).toBe(mockTranscriptFile); - expect(gen.next().done).toBe(true); - }); - - it('should initialize the root saga', () => { - let gen = editorSagas(); - - const checkActiveDocsYield = gen.next().value; - - expect(checkActiveDocsYield).toEqual( - takeEvery( - [ - EditorActions.addDocPendingChange, - EditorActions.setActiveEditor, - EditorActions.setActiveTab, - EditorActions.open - ], - checkActiveDocForPendingChanges - ) - ); - - const refreshConversationMenuYield = gen.next().value; - - expect(refreshConversationMenuYield).toEqual( - takeLatest( - [ - EditorActions.close, - EditorActions.open, - EditorActions.setActiveEditor, - EditorActions.setActiveTab - ], - refreshConversationMenu - ) - ); - - expect(gen.next().done).toBe(true); - }); + beforeEach(() => { + mockRemoteCommandsCalled = []; + mockLocalCommandsCalled = []; + }); + + it('should check the active doc for pending changes', () => { + const gen = checkActiveDocForPendingChanges(); + const stateData = gen.next().value; + + expect(stateData).toEqual(select(editorSelector)); + + const mockActiveDocId = 'doc1'; + const mockEditorState = { + editors: { + someEditor: { + activeDocumentId: mockActiveDocId, + }, + }, + activeEditor: 'someEditor', + docsWithPendingChanges: [mockActiveDocId], + }; + // should return the inner generator that we delegate to + const innerGen = gen.next(mockEditorState).value; + expect(innerGen).toEqual(call(promptUserToReloadDocument, mockActiveDocId)); + + expect(gen.next().done).toBe(true); + }); + + it('should prompt the user to reload the document when the file is chatdown', () => { + const mockChatFileName = 'doc1.chat'; + const options = { + buttons: ['Cancel', 'Reload'], + title: 'File change detected', + message: + 'We have detected a change in this file on disk. Would you like to reload it in the Emulator?', + }; + const gen = promptUserToReloadDocument(mockChatFileName); + + gen.next(); + + const { ShowMessageBox } = SharedConstants.Commands.Electron; + expect(mockRemoteCommandsCalled).toHaveLength(1); + expect(mockRemoteCommandsCalled[0].commandName).toEqual(ShowMessageBox); + expect(mockRemoteCommandsCalled[0].args[0]).toEqual(options); + expect(gen.next(true).value).toEqual( + put(removeDocPendingChange(mockChatFileName)) + ); + + gen.next(); + + const { OpenChatFile } = SharedConstants.Commands.Emulator; + + expect(mockLocalCommandsCalled).toHaveLength(1); + expect(mockLocalCommandsCalled[0].commandName).toEqual(OpenChatFile); + expect(mockLocalCommandsCalled[0].args[0]).toBe(mockChatFileName); + expect(mockLocalCommandsCalled[0].args[1]).toBe(true); + expect(gen.next().done).toBe(true); + }); + + it('should prompt the user to reload the document when the file is a transcript', () => { + const mockTranscriptFile = 'doc2.transcript'; + const options = { + buttons: ['Cancel', 'Reload'], + title: 'File change detected', + message: + 'We have detected a change in this file on disk. Would you like to reload it in the Emulator?', + }; + const gen = promptUserToReloadDocument(mockTranscriptFile); + + gen.next(); + + const { ShowMessageBox } = SharedConstants.Commands.Electron; + expect(mockRemoteCommandsCalled).toHaveLength(1); + expect(mockRemoteCommandsCalled[0].commandName).toEqual(ShowMessageBox); + expect(mockRemoteCommandsCalled[0].args[0]).toEqual(options); + expect(gen.next(true).value).toEqual( + put(removeDocPendingChange(mockTranscriptFile)) + ); + gen.next(); + + const { ReloadTranscript } = SharedConstants.Commands.Emulator; + + expect(mockLocalCommandsCalled).toHaveLength(1); + expect(mockLocalCommandsCalled[0].commandName).toEqual(ReloadTranscript); + expect(mockLocalCommandsCalled[0].args[0]).toBe(mockTranscriptFile); + expect(gen.next().done).toBe(true); + }); + + it('should initialize the root saga', () => { + const gen = editorSagas(); + + const checkActiveDocsYield = gen.next().value; + + expect(checkActiveDocsYield).toEqual( + takeEvery( + [ + EditorActions.addDocPendingChange, + EditorActions.setActiveEditor, + EditorActions.setActiveTab, + EditorActions.open, + ], + checkActiveDocForPendingChanges + ) + ); + + const refreshConversationMenuYield = gen.next().value; + + expect(refreshConversationMenuYield).toEqual( + takeLatest( + [ + EditorActions.close, + EditorActions.open, + EditorActions.setActiveEditor, + EditorActions.setActiveTab, + ], + refreshConversationMenu + ) + ); + + expect(gen.next().done).toBe(true); + }); }); diff --git a/packages/app/client/src/data/sagas/editorSagas.ts b/packages/app/client/src/data/sagas/editorSagas.ts index 361726f24..cb2338146 100644 --- a/packages/app/client/src/data/sagas/editorSagas.ts +++ b/packages/app/client/src/data/sagas/editorSagas.ts @@ -31,27 +31,47 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { call, ForkEffect, put, select, takeEvery, takeLatest } from 'redux-saga/effects'; +import { + isChatFile, + isTranscriptFile, + SharedConstants, +} from '@bfemulator/app-shared'; + import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl'; import { EditorActions, removeDocPendingChange } from '../action/editorActions'; -import { isChatFile, isTranscriptFile, SharedConstants } from '@bfemulator/app-shared'; + import { editorSelector, refreshConversationMenu } from './sharedSagas'; -export function* promptUserToReloadDocument(filename: string): IterableIterator { +import { + call, + ForkEffect, + put, + select, + takeEvery, + takeLatest, +} from 'redux-saga/effects'; + +export function* promptUserToReloadDocument( + filename: string +): IterableIterator { const { Commands } = SharedConstants; const options = { buttons: ['Cancel', 'Reload'], title: 'File change detected', - message: 'We have detected a change in this file on disk. Would you like to reload it in the Emulator?' + message: + 'We have detected a change in this file on disk. Would you like to reload it in the Emulator?', }; - const confirmation = yield CommandServiceImpl.remoteCall(Commands.Electron.ShowMessageBox, options); + const confirmation = yield CommandServiceImpl.remoteCall( + Commands.Electron.ShowMessageBox, + options + ); // clear the doc of pending changes yield put(removeDocPendingChange(filename)); // reload the file, otherwise proceed without reloading const { OpenChatFile, ReloadTranscript } = SharedConstants.Commands.Emulator; - + if (confirmation) { if (isChatFile(filename)) { yield CommandServiceImpl.call(OpenChatFile, filename, true); @@ -65,7 +85,8 @@ export function* checkActiveDocForPendingChanges(): IterableIterator { const stateData = yield select(editorSelector); // if currently active document has pending changes, prompt the user to reload it - const activeDocId = stateData.editors[stateData.activeEditor].activeDocumentId; + const activeDocId = + stateData.editors[stateData.activeEditor].activeDocumentId; if (stateData.docsWithPendingChanges.some(doc => doc === activeDocId)) { // TODO: active document ID is not always the filename yield call(promptUserToReloadDocument, activeDocId); @@ -81,7 +102,7 @@ export function* editorSagas(): IterableIterator { EditorActions.addDocPendingChange, EditorActions.setActiveEditor, EditorActions.setActiveTab, - EditorActions.open + EditorActions.open, ], checkActiveDocForPendingChanges ); diff --git a/packages/app/client/src/data/sagas/endpointSagas.spec.ts b/packages/app/client/src/data/sagas/endpointSagas.spec.ts index 66f6612a3..378fed018 100644 --- a/packages/app/client/src/data/sagas/endpointSagas.spec.ts +++ b/packages/app/client/src/data/sagas/endpointSagas.spec.ts @@ -1,27 +1,65 @@ -import { bot } from '../reducer/bot'; +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { applyMiddleware, combineReducers, createStore } from 'redux'; import sagaMiddlewareFactory from 'redux-saga'; -import { endpointSagas } from './endpointSagas'; -import { load, setActive } from '../action/botActions'; -import { launchEndpointEditor, openEndpointExplorerContextMenu } from '../action/endpointServiceActions'; import { Component } from 'react'; +import { SharedConstants } from '@bfemulator/app-shared'; + +import { bot } from '../reducer/bot'; +import { load, setActive } from '../action/botActions'; +import { + launchEndpointEditor, + openEndpointExplorerContextMenu, +} from '../action/endpointServiceActions'; import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl'; import { DialogService } from '../../ui/dialogs/service'; -import { SharedConstants } from '@bfemulator/app-shared'; + +import { endpointSagas } from './endpointSagas'; const sagaMiddleWare = sagaMiddlewareFactory(); -const mockStore = createStore(combineReducers({ bot }), {}, applyMiddleware(sagaMiddleWare)); +const mockStore = createStore( + combineReducers({ bot }), + {}, + applyMiddleware(sagaMiddleWare) +); sagaMiddleWare.run(endpointSagas); -const mockComponentClass = class extends Component<{}, {}> { - -}; +const mockComponentClass = class extends Component<{}, {}> {}; jest.mock('../store', () => ({ - get store() { - return mockStore; - } - }) -); -let mockBot = JSON.parse(`{ + get store() { + return mockStore; + }, +})); +const mockBot = JSON.parse(`{ "name": "TestBot", "description": "", "padlock": "", @@ -45,26 +83,24 @@ let mockBot = JSON.parse(`{ }`); jest.mock('../../ui/dialogs', () => ({ - AzureLoginPromptDialogContainer: function mock() { - return undefined; - }, - AzureLoginSuccessDialogContainer: function mock() { - return undefined; - }, - BotCreationDialog: function mock() { - return undefined; - }, - DialogService: { - showDialog: () => Promise.resolve(mockBot.services) - }, - SecretPromptDialog: function mock() { - return undefined; - } - } -)); + AzureLoginPromptDialogContainer: function mock() { + return undefined; + }, + AzureLoginSuccessDialogContainer: function mock() { + return undefined; + }, + BotCreationDialog: function mock() { + return undefined; + }, + DialogService: { + showDialog: () => Promise.resolve(mockBot.services), + }, + SecretPromptDialog: function mock() { + return undefined; + }, +})); describe('The endpoint sagas', () => { - beforeEach(() => { mockStore.dispatch(load([mockBot])); mockStore.dispatch(setActive(mockBot)); @@ -72,11 +108,21 @@ describe('The endpoint sagas', () => { it('should launch the endpoint editor and execute a command to save the edited services', async () => { const remoteCallSpy = jest.spyOn(CommandServiceImpl, 'remoteCall'); - const dialogServiceSpy = jest.spyOn(DialogService, 'showDialog').mockResolvedValue(mockBot.services); - await mockStore.dispatch(launchEndpointEditor(mockComponentClass, mockBot.services[0])); + const dialogServiceSpy = jest + .spyOn(DialogService, 'showDialog') + .mockResolvedValue(mockBot.services); + await mockStore.dispatch( + launchEndpointEditor(mockComponentClass, mockBot.services[0]) + ); const { AddOrUpdateService } = SharedConstants.Commands.Bot; - expect(dialogServiceSpy).toHaveBeenCalledWith(mockComponentClass, { endpointService: mockBot.services[0] }); - expect(remoteCallSpy).toHaveBeenCalledWith(AddOrUpdateService, 'abs', mockBot.services[1]); + expect(dialogServiceSpy).toHaveBeenCalledWith(mockComponentClass, { + endpointService: mockBot.services[0], + }); + expect(remoteCallSpy).toHaveBeenCalledWith( + AddOrUpdateService, + 'abs', + mockBot.services[1] + ); }); describe(' openEndpointContextMenu', () => { @@ -84,28 +130,54 @@ describe('The endpoint sagas', () => { { label: 'Open in Emulator', id: 'open' }, { label: 'Open in portal', id: 'absLink', enabled: jasmine.any(Boolean) }, { label: 'Edit configuration', id: 'edit' }, - { label: 'Remove', id: 'forget' } + { label: 'Remove', id: 'forget' }, ]; - const { DisplayContextMenu, ShowMessageBox } = SharedConstants.Commands.Electron; + const { + DisplayContextMenu, + ShowMessageBox, + } = SharedConstants.Commands.Electron; const { NewLiveChat } = SharedConstants.Commands.Emulator; it('should launch the endpoint editor when that menu option is chosen', () => { - const commandServiceSpy = jest.spyOn(CommandServiceImpl, 'remoteCall').mockResolvedValue({ id: 'edit' }); - const dialogServiceSpy = jest.spyOn(DialogService, 'showDialog').mockResolvedValue(mockBot.services); - mockStore.dispatch(openEndpointExplorerContextMenu(mockComponentClass, mockBot.services[0])); + const commandServiceSpy = jest + .spyOn(CommandServiceImpl, 'remoteCall') + .mockResolvedValue({ id: 'edit' }); + const dialogServiceSpy = jest + .spyOn(DialogService, 'showDialog') + .mockResolvedValue(mockBot.services); + mockStore.dispatch( + openEndpointExplorerContextMenu(mockComponentClass, mockBot.services[0]) + ); - expect(commandServiceSpy).toHaveBeenCalledWith(DisplayContextMenu, menuItems); - expect(dialogServiceSpy).toHaveBeenCalledWith(mockComponentClass, { endpointService: mockBot.services[0] }); + expect(commandServiceSpy).toHaveBeenCalledWith( + DisplayContextMenu, + menuItems + ); + expect(dialogServiceSpy).toHaveBeenCalledWith(mockComponentClass, { + endpointService: mockBot.services[0], + }); }); it('should open a deep link when that menu option is chosen', async () => { - const commandServiceRemoteCallSpy = jest.spyOn(CommandServiceImpl, 'remoteCall') + const commandServiceRemoteCallSpy = jest + .spyOn(CommandServiceImpl, 'remoteCall') .mockResolvedValue({ id: 'open' }); - const commandServiceCallSpy = jest.spyOn(CommandServiceImpl, 'call').mockResolvedValue(true); + const commandServiceCallSpy = jest + .spyOn(CommandServiceImpl, 'call') + .mockResolvedValue(true); - await mockStore.dispatch(openEndpointExplorerContextMenu(mockComponentClass, mockBot.services[0])); - expect(commandServiceRemoteCallSpy).toHaveBeenCalledWith(DisplayContextMenu, menuItems); - expect(commandServiceCallSpy).toHaveBeenCalledWith(NewLiveChat, mockBot.services[0], false); + await mockStore.dispatch( + openEndpointExplorerContextMenu(mockComponentClass, mockBot.services[0]) + ); + expect(commandServiceRemoteCallSpy).toHaveBeenCalledWith( + DisplayContextMenu, + menuItems + ); + expect(commandServiceCallSpy).toHaveBeenCalledWith( + NewLiveChat, + mockBot.services[0], + false + ); }); it('should forget the service when that menu item is chosen', async () => { @@ -118,28 +190,32 @@ describe('The endpoint sagas', () => { return true; }; const { RemoveService } = SharedConstants.Commands.Bot; - await mockStore.dispatch(openEndpointExplorerContextMenu(mockComponentClass, mockBot.services[0])); + await mockStore.dispatch( + openEndpointExplorerContextMenu(mockComponentClass, mockBot.services[0]) + ); await Promise.resolve(); - expect(remoteCallArgs[0]).toEqual({ commandName: DisplayContextMenu, args: [menuItems] }); + expect(remoteCallArgs[0]).toEqual({ + commandName: DisplayContextMenu, + args: [menuItems], + }); expect(remoteCallArgs[1]).toEqual({ - commandName: ShowMessageBox, args: [ + commandName: ShowMessageBox, + args: [ true, { - 'buttons': [ - 'Cancel', - 'OK' - ], - 'cancelId': 0, - 'defaultId': 1, - 'message': 'Remove endpoint https://testbot.botframework.com/api/messagesv3. Are you sure?', - 'type': 'question' - } + buttons: ['Cancel', 'OK'], + cancelId: 0, + defaultId: 1, + message: + 'Remove endpoint https://testbot.botframework.com/api/messagesv3. Are you sure?', + type: 'question', + }, ], }); expect(remoteCallArgs[2]).toEqual({ commandName: RemoveService, - args: [mockBot.services[0].type, mockBot.services[0].id] + args: [mockBot.services[0].type, mockBot.services[0].id], }); }); }); diff --git a/packages/app/client/src/data/sagas/endpointSagas.ts b/packages/app/client/src/data/sagas/endpointSagas.ts index 308d26ded..13486580f 100644 --- a/packages/app/client/src/data/sagas/endpointSagas.ts +++ b/packages/app/client/src/data/sagas/endpointSagas.ts @@ -32,9 +32,13 @@ // import { SharedConstants } from '@bfemulator/app-shared'; -import { IBotService, IEndpointService, ServiceTypes } from 'botframework-config/lib/schema'; +import { + IBotService, + IEndpointService, + ServiceTypes, +} from 'botframework-config/lib/schema'; import { ComponentClass } from 'react'; -import { call, ForkEffect, put, select, takeEvery, takeLatest } from 'redux-saga/effects'; + import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl'; import { DialogService } from '../../ui/dialogs/service'; import { openServiceDeepLink } from '../action/connectedServiceActions'; @@ -44,20 +48,36 @@ import { EndpointServicePayload, LAUNCH_ENDPOINT_EDITOR, OPEN_ENDPOINT_CONTEXT_MENU, - OPEN_ENDPOINT_IN_EMULATOR + OPEN_ENDPOINT_IN_EMULATOR, } from '../action/endpointServiceActions'; import { RootState } from '../store'; +import { + call, + ForkEffect, + put, + select, + takeEvery, + takeLatest, +} from 'redux-saga/effects'; + const getConnectedAbs = (state: RootState, endpointAppId: string) => { return (state.bot.activeBot.services || []).find(service => { - return service.type === ServiceTypes.Bot && (service as IBotService).appId === endpointAppId; + return ( + service.type === ServiceTypes.Bot && + (service as IBotService).appId === endpointAppId + ); }); }; -function* launchEndpointEditor(action: EndpointServiceAction): IterableIterator { +function* launchEndpointEditor( + action: EndpointServiceAction +): IterableIterator { const { endpointEditorComponent, endpointService = {} } = action.payload; - const servicesToUpdate = yield DialogService.showDialog, IEndpointService[]> - (endpointEditorComponent, { endpointService }); + const servicesToUpdate = yield DialogService.showDialog< + ComponentClass, + IEndpointService[] + >(endpointEditorComponent, { endpointService }); if (servicesToUpdate) { const { AddOrUpdateService, RemoveService } = SharedConstants.Commands.Bot; @@ -68,25 +88,43 @@ function* launchEndpointEditor(action: EndpointServiceAction) - : IterableIterator { - const connectedAbs = yield select(getConnectedAbs, action.payload.endpointService.appId); +function* openEndpointContextMenu( + action: EndpointServiceAction +): IterableIterator { + const connectedAbs = yield select( + getConnectedAbs, + action.payload.endpointService.appId + ); const menuItems = [ { label: 'Open in Emulator', id: 'open' }, { label: 'Open in portal', id: 'absLink', enabled: !!connectedAbs }, { label: 'Edit configuration', id: 'edit' }, - { label: 'Remove', id: 'forget' } + { label: 'Remove', id: 'forget' }, ]; const { DisplayContextMenu } = SharedConstants.Commands.Electron; - const response = yield call(CommandServiceImpl.remoteCall.bind(CommandServiceImpl), DisplayContextMenu, menuItems); + const response = yield call( + CommandServiceImpl.remoteCall.bind(CommandServiceImpl), + DisplayContextMenu, + menuItems + ); switch (response.id) { case 'edit': yield* launchEndpointEditor(action); @@ -104,27 +142,47 @@ function* openEndpointContextMenu(action: EndpointServiceAction): IterableIterator { - const { endpointService, focusExistingChatIfAvailable: focusExisting = false } = action.payload; - return CommandServiceImpl.call(SharedConstants.Commands.Emulator.NewLiveChat, endpointService, focusExisting); +// eslint-disable-next-line require-yield +function* openEndpointInEmulator( + action: EndpointServiceAction +): IterableIterator { + const { + endpointService, + focusExistingChatIfAvailable: focusExisting = false, + } = action.payload; + return CommandServiceImpl.call( + SharedConstants.Commands.Emulator.NewLiveChat, + endpointService, + focusExisting + ); } -function* removeEndpointServiceFromActiveBot(endpointService: IEndpointService): IterableIterator { - const result = yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.ShowMessageBox, true, { - type: 'question', - buttons: ['Cancel', 'OK'], - defaultId: 1, - message: `Remove endpoint ${endpointService.name}. Are you sure?`, - cancelId: 0, - }); +function* removeEndpointServiceFromActiveBot( + endpointService: IEndpointService +): IterableIterator { + const result = yield CommandServiceImpl.remoteCall( + SharedConstants.Commands.Electron.ShowMessageBox, + true, + { + type: 'question', + buttons: ['Cancel', 'OK'], + defaultId: 1, + message: `Remove endpoint ${endpointService.name}. Are you sure?`, + cancelId: 0, + } + ); if (result) { - yield CommandServiceImpl - .remoteCall(SharedConstants.Commands.Bot.RemoveService, endpointService.type, endpointService.id); + yield CommandServiceImpl.remoteCall( + SharedConstants.Commands.Bot.RemoveService, + endpointService.type, + endpointService.id + ); } } diff --git a/packages/app/client/src/data/sagas/index.ts b/packages/app/client/src/data/sagas/index.ts index ebbe77de8..b63d5f573 100644 --- a/packages/app/client/src/data/sagas/index.ts +++ b/packages/app/client/src/data/sagas/index.ts @@ -31,14 +31,14 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +import { azureAuthSagas } from './azureAuthSaga'; import { botSagas } from './botSagas'; import { editorSagas } from './editorSagas'; import { endpointSagas } from './endpointSagas'; -import { servicesExplorerSagas } from './servicesExplorerSagas'; import { navBarSagas } from './navBarSagas'; import { notificationSagas } from './notificationSagas'; -import { azureAuthSagas } from './azureAuthSaga'; import { resourceSagas } from './resourcesSagas'; +import { servicesExplorerSagas } from './servicesExplorerSagas'; import { welcomePageSagas } from './welcomePageSagas'; export const applicationSagas = [ @@ -50,5 +50,5 @@ export const applicationSagas = [ navBarSagas, notificationSagas, resourceSagas, - welcomePageSagas + welcomePageSagas, ]; diff --git a/packages/app/client/src/data/sagas/navBarSagas.spec.ts b/packages/app/client/src/data/sagas/navBarSagas.spec.ts index 4785767b3..140a177c5 100644 --- a/packages/app/client/src/data/sagas/navBarSagas.spec.ts +++ b/packages/app/client/src/data/sagas/navBarSagas.spec.ts @@ -31,31 +31,32 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { markNotificationsAsRead } from './navBarSagas'; import { select } from '../action/navBarActions'; import * as Constants from '../../constants'; import { markAllAsRead } from '../action/notificationActions'; + +import { markNotificationsAsRead } from './navBarSagas'; + import { put } from 'redux-saga/effects'; jest.mock('../../ui/dialogs', () => ({ - AzureLoginPromptDialogContainer: function mock() { - return undefined; - }, - AzureLoginSuccessDialogContainer: function mock() { - return undefined; - }, - BotCreationDialog: function mock() { - return undefined; - }, - DialogService: { showDialog: () => Promise.resolve(true) }, - SecretPromptDialog: function mock() { - return undefined; - } - } -)); + AzureLoginPromptDialogContainer: function mock() { + return undefined; + }, + AzureLoginSuccessDialogContainer: function mock() { + return undefined; + }, + BotCreationDialog: function mock() { + return undefined; + }, + DialogService: { showDialog: () => Promise.resolve(true) }, + SecretPromptDialog: function mock() { + return undefined; + }, +})); jest.mock('../../platform/commands/commandServiceImpl', () => ({ CommandServiceImpl: { - remoteCall: () => Promise.resolve(true) - } + remoteCall: () => Promise.resolve(true), + }, })); describe('Nav bar sagas', () => { test('markNotificationsAsRead()', () => { diff --git a/packages/app/client/src/data/sagas/navBarSagas.ts b/packages/app/client/src/data/sagas/navBarSagas.ts index 0d625863e..5516bec68 100644 --- a/packages/app/client/src/data/sagas/navBarSagas.ts +++ b/packages/app/client/src/data/sagas/navBarSagas.ts @@ -31,13 +31,16 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { ForkEffect, put, takeEvery, } from 'redux-saga/effects'; +import * as Constants from '../../constants'; import { NavBarActions, SelectNavBarAction } from '../action/navBarActions'; import { markAllAsRead } from '../action/notificationActions'; -import * as Constants from '../../constants'; + +import { ForkEffect, put, takeEvery } from 'redux-saga/effects'; /** Marks all notifications as read if the notifications pane is opened */ -export function* markNotificationsAsRead(action: SelectNavBarAction): IterableIterator { +export function* markNotificationsAsRead( + action: SelectNavBarAction +): IterableIterator { const navBarSelection = action.payload.selection; if (navBarSelection === Constants.NAVBAR_NOTIFICATIONS) { yield put(markAllAsRead()); diff --git a/packages/app/client/src/data/sagas/notificationSagas.spec.ts b/packages/app/client/src/data/sagas/notificationSagas.spec.ts index ada5f1593..5af2cf245 100644 --- a/packages/app/client/src/data/sagas/notificationSagas.spec.ts +++ b/packages/app/client/src/data/sagas/notificationSagas.spec.ts @@ -31,23 +31,26 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { put } from 'redux-saga/effects'; import { newNotification, NotificationType } from '@bfemulator/app-shared'; + import { NotificationManager } from '../../notificationManager'; import { beginAdd, finishAdd, finishClear, beginRemove, - finishRemove + finishRemove, } from '../action/notificationActions'; + import { addNotification, clearNotifications, removeNotification, - markAllAsRead + markAllAsRead, } from './notificationSagas'; +import { put } from 'redux-saga/effects'; + describe('Notification sagas', () => { test('addNotification()', () => { const notification = newNotification('someMessage', NotificationType.Info); diff --git a/packages/app/client/src/data/sagas/notificationSagas.ts b/packages/app/client/src/data/sagas/notificationSagas.ts index ad04e9f69..045bfad4e 100644 --- a/packages/app/client/src/data/sagas/notificationSagas.ts +++ b/packages/app/client/src/data/sagas/notificationSagas.ts @@ -34,18 +34,21 @@ import { NotificationManager } from '../../notificationManager'; import { BeginAddNotificationAction, - NotificationActions, BeginRemoveNotificationAction, finishAdd, finishClear, - finishRemove + finishRemove, + NotificationActions, } from '../action/notificationActions'; -import { ForkEffect, takeEvery, put } from 'redux-saga/effects'; + +import { ForkEffect, put, takeEvery } from 'redux-saga/effects'; /** Adds a notification to the notification manager then * adds it to the state store */ -export function* addNotification(action: BeginAddNotificationAction): IterableIterator { +export function* addNotification( + action: BeginAddNotificationAction +): IterableIterator { const { notification } = action.payload; NotificationManager.set(notification.id, notification); yield put(finishAdd(notification)); @@ -62,7 +65,9 @@ export function* clearNotifications(): IterableIterator { /** Removes a single notification from the notification manager then * removes it from the state store */ -export function* removeNotification(action: BeginRemoveNotificationAction): IterableIterator { +export function* removeNotification( + action: BeginRemoveNotificationAction +): IterableIterator { const { id: notificationId } = action.payload; NotificationManager.delete(notificationId); yield put(finishRemove(notificationId)); diff --git a/packages/app/client/src/data/sagas/resourceSagas.spec.ts b/packages/app/client/src/data/sagas/resourceSagas.spec.ts index 4dab0db05..49f6b61b4 100644 --- a/packages/app/client/src/data/sagas/resourceSagas.spec.ts +++ b/packages/app/client/src/data/sagas/resourceSagas.spec.ts @@ -1,32 +1,71 @@ -import { resources } from '../reducer/resourcesReducer'; +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + import { applyMiddleware, combineReducers, createStore } from 'redux'; -import { resourceSagas } from './resourcesSagas'; import { BotConfigWithPathImpl } from '@bfemulator/sdk-shared'; import { ServiceTypes } from 'botframework-config/lib/schema'; import sagaMiddlewareFactory from 'redux-saga'; +import { SharedConstants } from '@bfemulator/app-shared/built'; +import { Component } from 'react'; + import { openContextMenuForResource, openResource, openResourcesSettings, - renameResource + renameResource, } from '../action/resourcesAction'; -import { SharedConstants } from '@bfemulator/app-shared/built'; -import { Component } from 'react'; +import { resources } from '../reducer/resourcesReducer'; + +import { resourceSagas } from './resourcesSagas'; const sagaMiddleWare = sagaMiddlewareFactory(); -const mockStore = createStore(combineReducers({ resources }), {}, applyMiddleware(sagaMiddleWare)); +const mockStore = createStore( + combineReducers({ resources }), + {}, + applyMiddleware(sagaMiddleWare) +); sagaMiddleWare.run(resourceSagas); jest.mock('../store', () => ({ get store() { return mockStore; - } + }, })); jest.mock('../../ui/dialogs/service', () => ({ DialogService: { showDialog: () => Promise.resolve(true), hideDialog: () => Promise.resolve({ path: 'somePath' }), - } + }, })); const mockRemoteCommandsCalled = []; @@ -50,12 +89,11 @@ jest.mock('../../platform/commands/commandServiceImpl', () => ({ }, call: async (commandName: string, ...args: any[]) => { mockLocalCommandsCalled.push({ commandName, args: args }); - } - } + }, + }, })); describe('The ResourceSagas', () => { - beforeEach(() => { mockRemoteCommandsCalled.length = 0; mockLocalCommandsCalled.length = 0; @@ -67,7 +105,7 @@ describe('The ResourceSagas', () => { mockResource = BotConfigWithPathImpl.serviceFromJSON({ type: ServiceTypes.File, path: 'the/file/path', - name: 'testChat' + name: 'testChat', } as any); }); @@ -77,31 +115,31 @@ describe('The ResourceSagas', () => { expect(mockRemoteCommandsCalled.length).toBe(2); [ { - 'commandName': 'electron:display-context-menu', - 'args': [ + commandName: 'electron:display-context-menu', + args: [ [ { - 'label': 'Open file location', - 'id': 0 + label: 'Open file location', + id: 0, }, { - 'label': 'Rename', - 'id': 1 + label: 'Rename', + id: 1, }, { - 'label': 'Delete', - 'id': 2 - } - ] - ] + label: 'Delete', + id: 2, + }, + ], + ], }, { - 'commandName': 'shell:open-file-location', - 'args': [ - 'the/file/path' - ] - } - ].forEach((command, index) => expect(mockRemoteCommandsCalled[index]).toEqual(command)); + commandName: 'shell:open-file-location', + args: ['the/file/path'], + }, + ].forEach((command, index) => + expect(mockRemoteCommandsCalled[index]).toEqual(command) + ); }); it('and put the resource in the store as the "resourceToRename" property when "edit" is chosen', async () => { @@ -118,25 +156,40 @@ describe('The ResourceSagas', () => { expect(mockRemoteCommandsCalled.length).toBe(3); [ { - 'commandName': 'electron:display-context-menu', - 'args': [[{ 'label': 'Open file location', 'id': 0 }, { 'label': 'Rename', 'id': 1 }, { - 'label': 'Delete', - 'id': 2 - }]] - }, { - 'commandName': 'shell:showExplorer-message-box', - 'args': [true, { - 'type': 'info', - 'title': 'Delete this file', - 'buttons': ['Cancel', 'Delete'], - 'defaultId': 1, - 'message': 'This action cannot be undone. Are you sure you want to delete testChat?', - 'cancelId': 0 - }] - }, { - 'commandName': 'shell:unlink-file', - 'args': ['the/file/path'] - }].forEach((command, index) => expect(mockRemoteCommandsCalled[index]).toEqual(command)); + commandName: 'electron:display-context-menu', + args: [ + [ + { label: 'Open file location', id: 0 }, + { label: 'Rename', id: 1 }, + { + label: 'Delete', + id: 2, + }, + ], + ], + }, + { + commandName: 'shell:showExplorer-message-box', + args: [ + true, + { + type: 'info', + title: 'Delete this file', + buttons: ['Cancel', 'Delete'], + defaultId: 1, + message: + 'This action cannot be undone. Are you sure you want to delete testChat?', + cancelId: 0, + }, + ], + }, + { + commandName: 'shell:unlink-file', + args: ['the/file/path'], + }, + ].forEach((command, index) => + expect(mockRemoteCommandsCalled[index]).toEqual(command) + ); }); }); @@ -146,7 +199,7 @@ describe('The ResourceSagas', () => { mockResource = BotConfigWithPathImpl.serviceFromJSON({ type: ServiceTypes.File, path: 'the/file/path', - name: 'testChat' + name: 'testChat', } as any); }); @@ -155,15 +208,18 @@ describe('The ResourceSagas', () => { await mockStore.dispatch(renameResource(mockResource)); expect(mockRemoteCommandsCalled.length).toBe(1); expect(mockRemoteCommandsCalled[0]).toEqual({ - 'commandName': 'shell:showExplorer-message-box', - 'args': [true, { - 'type': 'error', - 'title': 'Invalid file name', - 'buttons': ['Ok'], - 'defaultId': 1, - 'message': 'A valid file name must be used', - 'cancelId': 0 - }] + commandName: 'shell:showExplorer-message-box', + args: [ + true, + { + type: 'error', + title: 'Invalid file name', + buttons: ['Ok'], + defaultId: 1, + message: 'A valid file name must be used', + cancelId: 0, + }, + ], }); }); @@ -171,11 +227,14 @@ describe('The ResourceSagas', () => { await mockStore.dispatch(renameResource(mockResource)); expect(mockRemoteCommandsCalled.length).toBe(1); expect(mockRemoteCommandsCalled[0]).toEqual({ - 'args': [{ - 'name': 'testChat', - 'path': 'the/file/path', - 'type': 'file' - }], 'commandName': 'shell:rename-file' + args: [ + { + name: 'testChat', + path: 'the/file/path', + type: 'file', + }, + ], + commandName: 'shell:rename-file', }); const { resourceToRename } = (mockStore.getState() as any).resources; expect(resourceToRename).toBeNull(); @@ -188,36 +247,39 @@ describe('The ResourceSagas', () => { mockResource = BotConfigWithPathImpl.serviceFromJSON({ type: ServiceTypes.File, path: 'the/file/path/chat.chat', - name: 'testChat' + name: 'testChat', } as any); }); it('should open a chat file', async () => { await mockStore.dispatch(openResource(mockResource as any)); - expect(mockLocalCommandsCalled).toEqual([{ - 'commandName': 'chat:open', - 'args': ['the/file/path/chat.chat', true] - }]); + expect(mockLocalCommandsCalled).toEqual([ + { + commandName: 'chat:open', + args: ['the/file/path/chat.chat', true], + }, + ]); }); it('should open a transcript file', async () => { mockResource.path = 'the/file/path/transcript.transcript'; await mockStore.dispatch(openResource(mockResource as any)); - expect(mockLocalCommandsCalled).toEqual([{ - 'commandName': 'transcript:open', - 'args': ['the/file/path/transcript.transcript'] - }]); + expect(mockLocalCommandsCalled).toEqual([ + { + commandName: 'transcript:open', + args: ['the/file/path/transcript.transcript'], + }, + ]); }); }); it('should open the resource settings dialog and process the results as expected', async () => { - const mockClass = class extends Component { - }; + const mockClass = class extends Component {}; await mockStore.dispatch(openResourcesSettings({ dialog: mockClass })); await Promise.resolve(); - expect(mockRemoteCommandsCalled).toEqual( - [{ commandName: 'bot:list:patch', args: [undefined, true] }] - ); + expect(mockRemoteCommandsCalled).toEqual([ + { commandName: 'bot:list:patch', args: [undefined, true] }, + ]); }); }); diff --git a/packages/app/client/src/data/sagas/resourcesSagas.ts b/packages/app/client/src/data/sagas/resourcesSagas.ts index f42ae6cf9..2dd0bbb6c 100644 --- a/packages/app/client/src/data/sagas/resourcesSagas.ts +++ b/packages/app/client/src/data/sagas/resourcesSagas.ts @@ -1,31 +1,79 @@ -import { ForkEffect, put, takeEvery } from 'redux-saga/effects'; +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +import { + BotInfo, + isChatFile, + isTranscriptFile, + NotificationType, + SharedConstants, +} from '@bfemulator/app-shared'; +import { newNotification } from '@bfemulator/app-shared/built'; +import { IFileService } from 'botframework-config/lib/schema'; +import { ComponentClass } from 'react'; + +import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl'; +import { DialogService } from '../../ui/dialogs/service'; +import { beginAdd } from '../action/notificationActions'; import { editResource, OPEN_CONTEXT_MENU_FOR_RESOURCE, OPEN_RESOURCE, OPEN_RESOURCE_SETTINGS, RENAME_RESOURCE, - ResourcesAction + ResourcesAction, } from '../action/resourcesAction'; -import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl'; -import { BotInfo, isChatFile, isTranscriptFile, NotificationType, SharedConstants } from '@bfemulator/app-shared'; -import { IFileService } from 'botframework-config/lib/schema'; -import { ComponentClass } from 'react'; -import { DialogService } from '../../ui/dialogs/service'; -import { beginAdd } from '../action/notificationActions'; -import { newNotification } from '@bfemulator/app-shared/built'; -function* openContextMenuForResource(action: ResourcesAction): IterableIterator { +import { ForkEffect, put, takeEvery } from 'redux-saga/effects'; + +function* openContextMenuForResource( + action: ResourcesAction +): IterableIterator { const menuItems = [ { label: 'Open file location', id: 0 }, { label: 'Rename', id: 1 }, - { label: 'Delete', id: 2 } + { label: 'Delete', id: 2 }, ]; - const result = yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.DisplayContextMenu, menuItems); + const result = yield CommandServiceImpl.remoteCall( + SharedConstants.Commands.Electron.DisplayContextMenu, + menuItems + ); switch (result.id) { case 0: - yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.OpenFileLocation, action.payload.path); + yield CommandServiceImpl.remoteCall( + SharedConstants.Commands.Electron.OpenFileLocation, + action.payload.path + ); break; case 1: @@ -42,7 +90,9 @@ function* openContextMenuForResource(action: ResourcesAction): Ite } } -function* deleteFile(action: ResourcesAction): IterableIterator { +function* deleteFile( + action: ResourcesAction +): IterableIterator { const { name, path } = action.payload; const { ShowMessageBox, UnlinkFile } = SharedConstants.Commands.Electron; const result = yield CommandServiceImpl.remoteCall(ShowMessageBox, true, { @@ -75,7 +125,9 @@ function* doRename(action: ResourcesAction) { yield put(editResource(null)); } -function* doOpenResource(action: ResourcesAction): IterableIterator { +function* doOpenResource( + action: ResourcesAction +): IterableIterator { const { OpenChatFile, OpenTranscript } = SharedConstants.Commands.Emulator; const { path } = action.payload; if (isChatFile(path)) { @@ -86,13 +138,24 @@ function* doOpenResource(action: ResourcesAction): IterableIterato // unknown types just fall into the abyss } -function* launchResourcesSettingsModal(action: ResourcesAction<{ dialog: ComponentClass }>) { - const result: Partial = yield DialogService.showDialog(action.payload.dialog); +function* launchResourcesSettingsModal( + action: ResourcesAction<{ dialog: ComponentClass }> +) { + const result: Partial = yield DialogService.showDialog( + action.payload.dialog + ); if (result) { try { - yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Bot.PatchBotList, result.path, result); + yield CommandServiceImpl.remoteCall( + SharedConstants.Commands.Bot.PatchBotList, + result.path, + result + ); } catch (e) { - const notification = newNotification('Unable to save resource settings', NotificationType.Error); + const notification = newNotification( + 'Unable to save resource settings', + NotificationType.Error + ); yield put(beginAdd(notification)); } } diff --git a/packages/app/client/src/data/sagas/servicesExplorerSagas.spec.ts b/packages/app/client/src/data/sagas/servicesExplorerSagas.spec.ts index c4d61ab7c..bc9a4d434 100644 --- a/packages/app/client/src/data/sagas/servicesExplorerSagas.spec.ts +++ b/packages/app/client/src/data/sagas/servicesExplorerSagas.spec.ts @@ -1,13 +1,46 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { ServiceCodes, SharedConstants } from '@bfemulator/app-shared'; import { ServiceTypes } from 'botframework-config/lib/schema'; import { applyMiddleware, combineReducers, createStore } from 'redux'; import sagaMiddlewareFactory from 'redux-saga'; + import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl'; import { AzureLoginFailedDialogContainer, AzureLoginSuccessDialogContainer, ConnectServicePromptDialogContainer, - GetStartedWithCSDialogContainer + GetStartedWithCSDialogContainer, } from '../../ui/dialogs'; import { DialogService } from '../../ui/dialogs/service'; // ☣☣ careful! ☣☣ import { ConnectedServicePickerContainer } from '../../ui/shell/explorer/servicesExplorer'; @@ -21,62 +54,71 @@ import { launchConnectedServicePicker, openAddServiceContextMenu, openContextMenuForConnectedService, - openServiceDeepLink + openServiceDeepLink, } from '../action/connectedServiceActions'; import { azureAuth } from '../reducer/azureAuthReducer'; import { bot } from '../reducer/bot'; + import { servicesExplorerSagas } from './servicesExplorerSagas'; const sagaMiddleWare = sagaMiddlewareFactory(); -const mockStore = createStore(combineReducers({ azureAuth, bot }), {}, applyMiddleware(sagaMiddleWare)); +const mockStore = createStore( + combineReducers({ azureAuth, bot }), + {}, + applyMiddleware(sagaMiddleWare) +); sagaMiddleWare.run(servicesExplorerSagas); -const mockArmToken = 'bm90aGluZw==.eyJ1cG4iOiJnbGFzZ293QHNjb3RsYW5kLmNvbSJ9.7gjdshgfdsk98458205jfds9843fjds'; +const mockArmToken = + 'bm90aGluZw==.eyJ1cG4iOiJnbGFzZ293QHNjb3RsYW5kLmNvbSJ9.7gjdshgfdsk98458205jfds9843fjds'; jest.mock('../../ui/dialogs', () => ({ - AzureLoginPromptDialogContainer: function mock() { - return undefined; - }, - AzureLoginSuccessDialogContainer: function mock() { - return undefined; - }, - BotCreationDialog: function mock() { - return undefined; - }, - DialogService: { - showDialog: () => Promise.resolve(true) - }, - SecretPromptDialog: function mock() { - return undefined; - } - } -)); - -jest.mock('../../ui/shell/explorer/servicesExplorer/connectedServiceEditor', () => ({ - ConnectedServiceEditorContainer: function mock() { + AzureLoginPromptDialogContainer: function mock() { return undefined; - } + }, + AzureLoginSuccessDialogContainer: function mock() { + return undefined; + }, + BotCreationDialog: function mock() { + return undefined; + }, + DialogService: { + showDialog: () => Promise.resolve(true), + }, + SecretPromptDialog: function mock() { + return undefined; + }, })); +jest.mock( + '../../ui/shell/explorer/servicesExplorer/connectedServiceEditor', + () => ({ + ConnectedServiceEditorContainer: function mock() { + return undefined; + }, + }) +); + jest.mock('../../ui/shell/explorer/servicesExplorer', () => ({ ConnectedServicePicker: function mock() { return undefined; - } + }, })); jest.mock('../store', () => ({ get store() { return mockStore; - } + }, })); jest.mock('./azureAuthSaga', () => ({ - getArmToken: function* () { + getArmToken: function*() { + // eslint-disable-next-line typescript/camelcase yield { access_token: mockArmToken }; - } + }, })); -CommandServiceImpl.remoteCall = async function (type: string) { +CommandServiceImpl.remoteCall = async function(type: string) { switch (type) { case SharedConstants.Commands.ConnectedService.GetConnectedServicesByType: return { services: [{ id: 'a luis service' }], code: ServiceCodes.OK }; @@ -96,24 +138,29 @@ describe('The ServiceExplorerSagas', () => { azureAuthWorkflowComponents: { loginFailedDialog: AzureLoginFailedDialogContainer, loginSuccessDialog: AzureLoginSuccessDialogContainer, - promptDialog: ConnectServicePromptDialogContainer + promptDialog: ConnectServicePromptDialogContainer, }, getStartedDialog: GetStartedWithCSDialogContainer, editorComponent: ConnectedServiceEditorContainer, pickerComponent: ConnectedServicePickerContainer, }; - launchConnectedServicePickerGen = servicesExplorerSagas().next().value.FORK.args[1]; + launchConnectedServicePickerGen = servicesExplorerSagas().next().value + .FORK.args[1]; mockStore.dispatch(azureArmTokenDataChanged(mockArmToken)); }); it('should retrieve the arm token from the store', () => { - const token = launchConnectedServicePickerGen().next().value.SELECT.selector(mockStore.getState()); + const token = launchConnectedServicePickerGen() + .next() + .value.SELECT.selector(mockStore.getState()); expect(token.access_token).toBe(mockArmToken); }); it('should prompt the user to login if the armToken does not exist in the store', () => { mockStore.dispatch(azureArmTokenDataChanged('')); - const it = launchConnectedServicePickerGen(launchConnectedServicePicker(payload)); + const it = launchConnectedServicePickerGen( + launchConnectedServicePicker(payload) + ); let token = it.next().value.SELECT.selector(mockStore.getState()); expect(token.access_token).toBe(''); token = it.next().value; @@ -133,7 +180,8 @@ describe('The ServiceExplorerSagas', () => { }); it('should launch the luis models picklist after the luis models are retrieved', async () => { - DialogService.showDialog = () => Promise.resolve([{ id: 'a new service to add' }]) as any; + DialogService.showDialog = () => + Promise.resolve([{ id: 'a new service to add' }]) as any; const action = launchConnectedServicePicker(payload); const it = launchConnectedServicePickerGen(action); let token = it.next().value.SELECT.selector(mockStore.getState()); @@ -162,7 +210,8 @@ describe('The ServiceExplorerSagas', () => { mockStore.dispatch(load([mockBot])); mockStore.dispatch(setActive(mockBot)); - DialogService.showDialog = () => Promise.resolve([{ id: 'a new service to add' }]) as any; + DialogService.showDialog = () => + Promise.resolve([{ id: 'a new service to add' }]) as any; const action = launchConnectedServicePicker(payload); const it = launchConnectedServicePickerGen(action); let token = it.next().value.SELECT.selector(mockStore.getState()); @@ -171,10 +220,12 @@ describe('The ServiceExplorerSagas', () => { const luisModels = await it.next(token).value; const newModels = await it.next(luisModels).value; - const botConfig = it.next(newModels).value.SELECT.selector(mockStore.getState()); + const botConfig = it + .next(newModels) + .value.SELECT.selector(mockStore.getState()); let _type; let _args; - CommandServiceImpl.remoteCall = function (type: string, ...args: any[]) { + CommandServiceImpl.remoteCall = function(type: string, ...args: any[]) { _type = type; _args = args; return Promise.resolve(true); @@ -202,8 +253,9 @@ describe('The ServiceExplorerSagas', () => { beforeEach(() => { const sagaIt = servicesExplorerSagas(); - action = openContextMenuForConnectedService> - (ConnectedServiceEditorContainer, mockService); + action = openContextMenuForConnectedService< + ConnectedServiceAction + >(ConnectedServiceEditorContainer, mockService); let i = 4; while (i--) { contextMenuGen = sagaIt.next().value.FORK.args[1]; @@ -222,7 +274,9 @@ describe('The ServiceExplorerSagas', () => { await it.next(result).value; expect(window.open).toHaveBeenCalledWith( - `https://luis.ai/applications/${ mockService.appId }/versions/${ mockService.version }/build` + `https://luis.ai/applications/${mockService.appId}/versions/${ + mockService.version + }/build` ); }); @@ -369,7 +423,7 @@ describe('The ServiceExplorerSagas', () => { azureAuthWorkflowComponents: { loginFailedDialog: AzureLoginFailedDialogContainer, loginSuccessDialog: AzureLoginSuccessDialogContainer, - promptDialog: ConnectServicePromptDialogContainer + promptDialog: ConnectServicePromptDialogContainer, }, getStartedDialog: GetStartedWithCSDialogContainer, editorComponent: ConnectedServiceEditorContainer, @@ -396,7 +450,12 @@ describe('The ServiceExplorerSagas', () => { }); describe('openConnectedServiceDeepLink', () => { - const mockModel = { type: ServiceTypes.Luis, appId: '1234', version: '0.1', region: 'westeurope' }; + const mockModel = { + type: ServiceTypes.Luis, + appId: '1234', + version: '0.1', + region: 'westeurope', + }; let openConnectedServiceGen; beforeEach(() => { const sagaIt = servicesExplorerSagas(); @@ -435,61 +494,67 @@ describe('The ServiceExplorerSagas', () => { it('should open the correct service url for CosmosDB services', () => { window.open = jest.fn(); const mock = { - 'type': ServiceTypes.CosmosDB, - 'collection': 'fdsa', - 'database': 'fsa', - 'endpoint': 'fsda', - 'id': '206', - 'name': 'Cosmos', - 'resourceGroup': 'db-service', - 'serviceName': 'cosmosdb', - 'subscriptionId': '0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd', - 'tenantId': 'microsoft.com' + type: ServiceTypes.CosmosDB, + collection: 'fdsa', + database: 'fsa', + endpoint: 'fsda', + id: '206', + name: 'Cosmos', + resourceGroup: 'db-service', + serviceName: 'cosmosdb', + subscriptionId: '0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd', + tenantId: 'microsoft.com', }; const action = openServiceDeepLink(mock as any); openConnectedServiceGen(action).next(); - expect(window.open).toHaveBeenCalledWith('https://ms.portal.azure.com/#@microsoft.com/resource/subscriptions/' + - '0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd/resourceGroups/db-service/providers/Microsoft.DocumentDb/' + - 'databaseAccounts/cosmosdb/overview'); + expect(window.open).toHaveBeenCalledWith( + 'https://ms.portal.azure.com/#@microsoft.com/resource/subscriptions/' + + '0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd/resourceGroups/db-service/providers/Microsoft.DocumentDb/' + + 'databaseAccounts/cosmosdb/overview' + ); }); it('should open the correct service url for BlobStorage services', () => { window.open = jest.fn(); const mock = { - 'type': ServiceTypes.BlobStorage, - 'id': '206', - 'name': 'Blob', - 'resourceGroup': 'blob-service', - 'serviceName': 'blob', - 'subscriptionId': '0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd', - 'tenantId': 'microsoft.com' + type: ServiceTypes.BlobStorage, + id: '206', + name: 'Blob', + resourceGroup: 'blob-service', + serviceName: 'blob', + subscriptionId: '0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd', + tenantId: 'microsoft.com', }; const action = openServiceDeepLink(mock as any); openConnectedServiceGen(action).next(); - expect(window.open).toHaveBeenCalledWith('https://ms.portal.azure.com/#@microsoft.com/resource/subscriptions/' + - '0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd/resourceGroups/blob-service/providers/Microsoft.DocumentDB/' + - 'storageAccounts/blob/overview'); + expect(window.open).toHaveBeenCalledWith( + 'https://ms.portal.azure.com/#@microsoft.com/resource/subscriptions/' + + '0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd/resourceGroups/blob-service/providers/Microsoft.DocumentDB/' + + 'storageAccounts/blob/overview' + ); }); it('should open the correct service url for AppInsights services', () => { window.open = jest.fn(); const mock = { - 'type': ServiceTypes.AppInsights, - 'id': '206', - 'name': 'appInsights', - 'resourceGroup': 'appInsights-service', - 'serviceName': 'appInsights', - 'subscriptionId': '0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd', - 'tenantId': 'microsoft.com' + type: ServiceTypes.AppInsights, + id: '206', + name: 'appInsights', + resourceGroup: 'appInsights-service', + serviceName: 'appInsights', + subscriptionId: '0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd', + tenantId: 'microsoft.com', }; const action = openServiceDeepLink(mock as any); openConnectedServiceGen(action).next(); - expect(window.open).toHaveBeenCalledWith('https://ms.portal.azure.com/#@microsoft.com/resource/subscriptions/' + - '0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd/resourceGroups/appInsights-service/providers/microsoft.insights/' + - 'components/appInsights/overview'); + expect(window.open).toHaveBeenCalledWith( + 'https://ms.portal.azure.com/#@microsoft.com/resource/subscriptions/' + + '0389857f-aaaa-ssss-fff-gfsgfdsfgssdgfd/resourceGroups/appInsights-service/providers/microsoft.insights/' + + 'components/appInsights/overview' + ); }); it('should open the correct service URL for qnaMaker services', () => { @@ -499,7 +564,9 @@ describe('The ServiceExplorerSagas', () => { const action = openServiceDeepLink(mockModel as any); openConnectedServiceGen(action).next(); - expect(window.open).toHaveBeenCalledWith('https://qnamaker.ai/Edit/KnowledgeBase?kbid=45432'); + expect(window.open).toHaveBeenCalledWith( + 'https://qnamaker.ai/Edit/KnowledgeBase?kbid=45432' + ); }); }); }); diff --git a/packages/app/client/src/data/sagas/servicesExplorerSagas.ts b/packages/app/client/src/data/sagas/servicesExplorerSagas.ts index a265cd3c7..0fe83012e 100644 --- a/packages/app/client/src/data/sagas/servicesExplorerSagas.ts +++ b/packages/app/client/src/data/sagas/servicesExplorerSagas.ts @@ -40,13 +40,16 @@ import { IGenericService, ILuisService, IQnAService, - ServiceTypes + ServiceTypes, } from 'botframework-config/lib/schema'; -import { ForkEffect, put, select, takeEvery, takeLatest } from 'redux-saga/effects'; + import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl'; import { DialogService } from '../../ui/dialogs/service'; import { serviceTypeLabels } from '../../utils/serviceTypeLables'; -import { ArmTokenData, beginAzureAuthWorkflow } from '../action/azureAuthActions'; +import { + ArmTokenData, + beginAzureAuthWorkflow, +} from '../action/azureAuthActions'; import { ConnectedServiceAction, ConnectedServicePayload, @@ -56,35 +59,57 @@ import { OPEN_ADD_CONNECTED_SERVICE_CONTEXT_MENU, OPEN_CONNECTED_SERVICE_SORT_CONTEXT_MENU, OPEN_CONTEXT_MENU_FOR_CONNECTED_SERVICE, - OPEN_SERVICE_DEEP_LINK + OPEN_SERVICE_DEEP_LINK, } from '../action/connectedServiceActions'; import { sortExplorerContents } from '../action/explorerActions'; import { SortCriteria } from '../reducer/explorer'; import { RootState } from '../store'; + import { getArmToken } from './azureAuthSaga'; -declare type ServicesPayload = { services: IConnectedService[], code: ServiceCodes }; +import { + ForkEffect, + put, + select, + takeEvery, + takeLatest, +} from 'redux-saga/effects'; + +declare interface ServicesPayload { + services: IConnectedService[]; + code: ServiceCodes; +} -const getArmTokenFromState = (state: RootState): ArmTokenData => state.azureAuth; -const geBotConfigFromState = (state: RootState): BotConfigWithPath => state.bot.activeBot; -const getSortSelection = (state: RootState): { [paneldId: string]: SortCriteria } => +const getArmTokenFromState = (state: RootState): ArmTokenData => + state.azureAuth; +const geBotConfigFromState = (state: RootState): BotConfigWithPath => + state.bot.activeBot; +const getSortSelection = ( + state: RootState +): { [paneldId: string]: SortCriteria } => state.explorer.sortSelectionByPanelId; -function* launchConnectedServicePicker(action: ConnectedServiceAction) - : IterableIterator { +function* launchConnectedServicePicker( + action: ConnectedServiceAction +): IterableIterator { // To retrieve azure services, luis models and KBs, // we must have the authoring key. // To get the authoring key, we need the arm token. let armTokenData: ArmTokenData & number = yield select(getArmTokenFromState); if (!armTokenData || !armTokenData.access_token) { - const { promptDialog, loginSuccessDialog, loginFailedDialog } = action.payload.azureAuthWorkflowComponents; + const { + promptDialog, + loginSuccessDialog, + loginFailedDialog, + } = action.payload.azureAuthWorkflowComponents; armTokenData = yield* getArmToken( beginAzureAuthWorkflow( promptDialog, { serviceType: action.payload.serviceType }, loginSuccessDialog, loginFailedDialog - )); + ) + ); } // 2 means the user has chosen to manually enter the connected service @@ -97,12 +122,15 @@ function* launchConnectedServicePicker(action: ConnectedServiceAction { - - const { pickerComponent, authenticatedUser, serviceType: type } = action.payload; + const { + pickerComponent, + authenticatedUser, + serviceType: type, + } = action.payload; let result = yield DialogService.showDialog(pickerComponent, { availableServices, authenticatedUser, - serviceType + serviceType, }); if (result === 1) { action.payload.connectedService = BotConfigurationBase.serviceFromJSON({ type, - hostname: '' /* defect workaround */ + hostname: '' /* defect workaround */, } as any); result = yield* launchConnectedServiceEditor(action); } @@ -158,35 +193,58 @@ function* launchConnectedServicePickList( return result; } -function* retrieveServicesByServiceType(serviceType: ServiceTypes): IterableIterator { - let armTokenData: ArmTokenData = yield select(getArmTokenFromState); +function* retrieveServicesByServiceType( + serviceType: ServiceTypes +): IterableIterator { + const armTokenData: ArmTokenData = yield select(getArmTokenFromState); if (!armTokenData || !armTokenData.access_token) { throw new Error('Auth credentials do not exist.'); } - const { GetConnectedServicesByType } = SharedConstants.Commands.ConnectedService; + const { + GetConnectedServicesByType, + } = SharedConstants.Commands.ConnectedService; let payload: ServicesPayload; try { - payload = yield CommandServiceImpl.remoteCall(GetConnectedServicesByType, armTokenData.access_token, serviceType); + payload = yield CommandServiceImpl.remoteCall( + GetConnectedServicesByType, + armTokenData.access_token, + serviceType + ); } catch (e) { payload = { services: [], code: ServiceCodes.Error }; } return payload; } -function* openConnectedServiceDeepLink(action: ConnectedServiceAction): IterableIterator { +// eslint-disable-next-line require-yield +function* openConnectedServiceDeepLink( + action: ConnectedServiceAction +): IterableIterator { const { connectedService } = action.payload; switch (connectedService.type) { case ServiceTypes.AppInsights: - return openAzureProviderDeepLink('microsoft.insights/components', connectedService as IAzureService); + return openAzureProviderDeepLink( + 'microsoft.insights/components', + connectedService as IAzureService + ); case ServiceTypes.BlobStorage: - return openAzureProviderDeepLink('Microsoft.DocumentDB/storageAccounts', connectedService as IAzureService); + return openAzureProviderDeepLink( + 'Microsoft.DocumentDB/storageAccounts', + connectedService as IAzureService + ); case ServiceTypes.Bot: - return openAzureProviderDeepLink('Microsoft.BotService/botServices', connectedService as IAzureService); + return openAzureProviderDeepLink( + 'Microsoft.BotService/botServices', + connectedService as IAzureService + ); case ServiceTypes.CosmosDB: - return openAzureProviderDeepLink('Microsoft.DocumentDb/databaseAccounts', connectedService as IAzureService); + return openAzureProviderDeepLink( + 'Microsoft.DocumentDb/databaseAccounts', + connectedService as IAzureService + ); case ServiceTypes.Generic: return window.open((connectedService as IGenericService).url); @@ -202,14 +260,18 @@ function* openConnectedServiceDeepLink(action: ConnectedServiceAction) - : IterableIterator { +function* openContextMenuForService( + action: ConnectedServiceAction +): IterableIterator { const menuItems = [ { label: 'Manage service', id: 'open' }, { label: 'Edit configuration', id: 'edit' }, - { label: 'Disconnect this service', id: 'forget' } + { label: 'Disconnect this service', id: 'forget' }, ]; - const response = yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.DisplayContextMenu, menuItems); + const response = yield CommandServiceImpl.remoteCall( + SharedConstants.Commands.Electron.DisplayContextMenu, + menuItems + ); const { connectedService } = action.payload; action.payload.serviceType = connectedService.type; switch (response.id) { @@ -225,13 +287,15 @@ function* openContextMenuForService(action: ConnectedServiceAction) - : IterableIterator { +function* openAddConnectedServiceContextMenu( + action: ConnectedServiceAction +): IterableIterator { const menuItems = [ { label: 'Add Language Understanding (LUIS)', id: ServiceTypes.Luis }, { label: 'Add QnA Maker', id: ServiceTypes.QnA }, @@ -244,69 +308,119 @@ function* openAddConnectedServiceContextMenu(action: ConnectedServiceAction): IterableIterator { +function* openSortContextMenu( + action: ConnectedServiceAction +): IterableIterator { const sortSelectionByPanelId = yield select(getSortSelection); const currentSort = sortSelectionByPanelId[action.payload.panelId]; const menuItems = [ - { label: 'Sort by name', id: 'name', type: 'checkbox', checked: currentSort === 'name' }, - { label: 'Sort by type', id: 'type', type: 'checkbox', checked: currentSort === 'type' }, + { + label: 'Sort by name', + id: 'name', + type: 'checkbox', + checked: currentSort === 'name', + }, + { + label: 'Sort by type', + id: 'type', + type: 'checkbox', + checked: currentSort === 'type', + }, ]; - const response = yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.DisplayContextMenu, menuItems); - yield response.id ? put(sortExplorerContents(action.payload.panelId, response.id)) : null; + const response = yield CommandServiceImpl.remoteCall( + SharedConstants.Commands.Electron.DisplayContextMenu, + menuItems + ); + yield response.id + ? put(sortExplorerContents(action.payload.panelId, response.id)) + : null; } -function* removeServiceFromActiveBot(connectedService: IConnectedService): IterableIterator { +function* removeServiceFromActiveBot( + connectedService: IConnectedService +): IterableIterator { // TODO - localization - const result = yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.ShowMessageBox, true, { - type: 'question', - buttons: ['Cancel', 'OK'], - defaultId: 1, - message: `Remove ${ serviceTypeLabels[connectedService.type] } service: ${ connectedService.name }. Are you sure?`, - cancelId: 0, - }); + const result = yield CommandServiceImpl.remoteCall( + SharedConstants.Commands.Electron.ShowMessageBox, + true, + { + type: 'question', + buttons: ['Cancel', 'OK'], + defaultId: 1, + message: `Remove ${serviceTypeLabels[connectedService.type]} service: ${ + connectedService.name + }. Are you sure?`, + cancelId: 0, + } + ); if (result) { const { RemoveService } = SharedConstants.Commands.Bot; - yield CommandServiceImpl.remoteCall(RemoveService, connectedService.type, connectedService.id); + yield CommandServiceImpl.remoteCall( + RemoveService, + connectedService.type, + connectedService.id + ); } } -function* launchConnectedServiceEditor(action: ConnectedServiceAction) - : IterableIterator { - const { editorComponent, authenticatedUser, connectedService, serviceType } = action.payload; - const servicesToUpdate: IConnectedService[] = yield DialogService.showDialog(editorComponent, { - connectedService, +function* launchConnectedServiceEditor( + action: ConnectedServiceAction +): IterableIterator { + const { + editorComponent, authenticatedUser, - serviceType - }); + connectedService, + serviceType, + } = action.payload; + const servicesToUpdate: IConnectedService[] = yield DialogService.showDialog( + editorComponent, + { + connectedService, + authenticatedUser, + serviceType, + } + ); if (servicesToUpdate) { let i = servicesToUpdate.length; while (i--) { const service = servicesToUpdate[i]; - yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Bot.AddOrUpdateService, service.type, service); + yield CommandServiceImpl.remoteCall( + SharedConstants.Commands.Bot.AddOrUpdateService, + service.type, + service + ); } } return null; } -function openAzureProviderDeepLink(provider: string, azureService: IAzureService): void { +function openAzureProviderDeepLink( + provider: string, + azureService: IAzureService +): void { const { tenantId, subscriptionId, resourceGroup, serviceName } = azureService; const bits = [ - `https://ms.portal.azure.com/#@${ tenantId }/resource/`, - `subscriptions/${ subscriptionId }/`, - `resourceGroups/${ encodeURI(resourceGroup) }/`, - `providers/${ provider }/${ encodeURI(serviceName) }/overview` + `https://ms.portal.azure.com/#@${tenantId}/resource/`, + `subscriptions/${subscriptionId}/`, + `resourceGroups/${encodeURI(resourceGroup)}/`, + `providers/${provider}/${encodeURI(serviceName)}/overview`, ]; window.open(bits.join('')); @@ -328,23 +442,49 @@ function openLuisDeepLink(luisService: ILuisService) { regionPrefix = ''; break; } - const linkArray = ['https://', `${ encodeURI(regionPrefix) }`, 'luis.ai/applications/']; - linkArray.push(`${ encodeURI(appId) }`, '/versions/', `${ encodeURI(version) }`, '/build'); + const linkArray = [ + 'https://', + `${encodeURI(regionPrefix)}`, + 'luis.ai/applications/', + ]; + linkArray.push( + `${encodeURI(appId)}`, + '/versions/', + `${encodeURI(version)}`, + '/build' + ); const link = linkArray.join(''); window.open(link); } function openQnaMakerDeepLink(service: IQnAService) { const { kbId } = service; - const link = `https://qnamaker.ai/Edit/KnowledgeBase?kbid=${ encodeURIComponent(kbId) }`; + const link = `https://qnamaker.ai/Edit/KnowledgeBase?kbid=${encodeURIComponent( + kbId + )}`; window.open(link); } export function* servicesExplorerSagas(): IterableIterator { - yield takeLatest(LAUNCH_CONNECTED_SERVICE_PICKER, launchConnectedServicePicker); - yield takeLatest(LAUNCH_CONNECTED_SERVICE_EDITOR, launchConnectedServiceEditor); + yield takeLatest( + LAUNCH_CONNECTED_SERVICE_PICKER, + launchConnectedServicePicker + ); + yield takeLatest( + LAUNCH_CONNECTED_SERVICE_EDITOR, + launchConnectedServiceEditor + ); yield takeEvery(OPEN_SERVICE_DEEP_LINK, openConnectedServiceDeepLink); - yield takeEvery(OPEN_CONTEXT_MENU_FOR_CONNECTED_SERVICE, openContextMenuForService); - yield takeEvery(OPEN_ADD_CONNECTED_SERVICE_CONTEXT_MENU, openAddConnectedServiceContextMenu); - yield takeEvery(OPEN_CONNECTED_SERVICE_SORT_CONTEXT_MENU, openSortContextMenu); + yield takeEvery( + OPEN_CONTEXT_MENU_FOR_CONNECTED_SERVICE, + openContextMenuForService + ); + yield takeEvery( + OPEN_ADD_CONNECTED_SERVICE_CONTEXT_MENU, + openAddConnectedServiceContextMenu + ); + yield takeEvery( + OPEN_CONNECTED_SERVICE_SORT_CONTEXT_MENU, + openSortContextMenu + ); } diff --git a/packages/app/client/src/data/sagas/sharedSagas.spec.ts b/packages/app/client/src/data/sagas/sharedSagas.spec.ts index 3d779304c..7b0b4f94d 100644 --- a/packages/app/client/src/data/sagas/sharedSagas.spec.ts +++ b/packages/app/client/src/data/sagas/sharedSagas.spec.ts @@ -31,24 +31,29 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { editorSelector, refreshConversationMenu } from './sharedSagas'; +import { SharedConstants } from '@bfemulator/app-shared'; + import { RootState } from '../store'; + +import { editorSelector, refreshConversationMenu } from './sharedSagas'; + import { select } from 'redux-saga/effects'; -import { SharedConstants } from '@bfemulator/app-shared'; let mockRemoteCommandsCalled = []; jest.mock('../../platform/commands/commandServiceImpl', () => ({ CommandServiceImpl: { remoteCall: async (commandName: string, ...args: any[]) => { mockRemoteCommandsCalled.push({ commandName, args: args }); - } - } + }, + }, })); describe('The sharedSagas', () => { const editorState = { activeEditor: 'primary' }; - beforeEach(() => { mockRemoteCommandsCalled = []; }); + beforeEach(() => { + mockRemoteCommandsCalled = []; + }); it('should select the editor state from the store', () => { const state: RootState = { editor: editorState }; @@ -65,7 +70,9 @@ describe('The sharedSagas', () => { gen.next(editorState); expect(mockRemoteCommandsCalled).toHaveLength(1); const refreshConversationCall = mockRemoteCommandsCalled[0]; - expect(refreshConversationCall.commandName).toBe(SharedConstants.Commands.Electron.UpdateConversationMenu); + expect(refreshConversationCall.commandName).toBe( + SharedConstants.Commands.Electron.UpdateConversationMenu + ); expect(refreshConversationCall.args).toHaveLength(1); expect(refreshConversationCall.args[0]).toEqual(editorState); diff --git a/packages/app/client/src/data/sagas/sharedSagas.ts b/packages/app/client/src/data/sagas/sharedSagas.ts index 943d4383c..bbbb4b415 100644 --- a/packages/app/client/src/data/sagas/sharedSagas.ts +++ b/packages/app/client/src/data/sagas/sharedSagas.ts @@ -31,10 +31,12 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { select } from 'redux-saga/effects'; -import { RootState } from '../store'; import { SharedConstants } from '@bfemulator/app-shared'; + import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl'; +import { RootState } from '../store'; + +import { select } from 'redux-saga/effects'; export function editorSelector(state: RootState) { return state.editor; @@ -42,5 +44,8 @@ export function editorSelector(state: RootState) { export function* refreshConversationMenu(): IterableIterator { const stateData = yield select(editorSelector); - yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.UpdateConversationMenu, stateData); + yield CommandServiceImpl.remoteCall( + SharedConstants.Commands.Electron.UpdateConversationMenu, + stateData + ); } diff --git a/packages/app/client/src/data/sagas/welcomePageSagas.spec.ts b/packages/app/client/src/data/sagas/welcomePageSagas.spec.ts index 0024d49ad..66ea9eca4 100644 --- a/packages/app/client/src/data/sagas/welcomePageSagas.spec.ts +++ b/packages/app/client/src/data/sagas/welcomePageSagas.spec.ts @@ -1,48 +1,83 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { SharedConstants } from '@bfemulator/app-shared'; +import sagaMiddlewareFactory from 'redux-saga'; +import { applyMiddleware, combineReducers, createStore } from 'redux'; + import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl'; import { openContextMenuForBot } from '../action/welcomePageActions'; import { bot } from '../reducer/bot'; import notification from '../reducer/notification'; + import { notificationSagas } from './notificationSagas'; import { welcomePageSagas } from './welcomePageSagas'; -import sagaMiddlewareFactory from 'redux-saga'; -import { applyMiddleware, combineReducers, createStore } from 'redux'; const mockBot = { path: '/some/path.bot', displayName: 'AuthBot', - secret: 'secret' + secret: 'secret', }; jest.mock('../../platform/commands/commandServiceImpl', () => ({ CommandServiceImpl: { - remoteCall: async () => null - } + remoteCall: async () => null, + }, })); const sagaMiddleWare = sagaMiddlewareFactory(); -const mockStore = createStore(combineReducers({ bot, notification }), { - bot: { botFiles: [mockBot] } -}, applyMiddleware(sagaMiddleWare)); +const mockStore = createStore( + combineReducers({ bot, notification }), + { + bot: { botFiles: [mockBot] }, + }, + applyMiddleware(sagaMiddleWare) +); jest.mock('../store', () => ({ get store() { return mockStore; - } + }, })); sagaMiddleWare.run(welcomePageSagas); sagaMiddleWare.run(notificationSagas); describe('The WelcomePageSagas', () => { - describe(', when invoking a context menu over a bot in the list', () => { - it('should call the series of commands that move the bot file to a new location.', async () => { const remoteCalls = []; - CommandServiceImpl.remoteCall = async function (...args: any[]) { + CommandServiceImpl.remoteCall = async function(...args: any[]) { remoteCalls.push(args); switch (args[0]) { - case SharedConstants.Commands.Electron.DisplayContextMenu: return { id: 0 }; @@ -60,58 +95,55 @@ describe('The WelcomePageSagas', () => { 'electron:display-context-menu', [ { - 'label': 'Move...', - 'id': 0 + label: 'Move...', + id: 0, }, { - 'label': 'Open file location', - 'id': 1 + label: 'Open file location', + id: 1, }, { - 'label': 'Forget this bot', - 'id': 2 - } - ] + label: 'Forget this bot', + id: 2, + }, + ], ]); expect(remoteCalls[1]).toEqual([ 'shell:showExplorer-save-dialog', { - 'defaultPath': '/some/path.bot', - 'buttonLabel': 'Move', - 'nameFieldLabel': 'Name', - 'filters': [ + defaultPath: '/some/path.bot', + buttonLabel: 'Move', + nameFieldLabel: 'Name', + filters: [ { - 'extensions': [ - '.bot' - ] - } - ] - } + extensions: ['.bot'], + }, + ], + }, ]); expect(remoteCalls[2]).toEqual([ 'shell:rename-file', { - 'path': '/some/path.bot', - 'newPath': 'this/is/a/new/location.bot' - } + path: '/some/path.bot', + newPath: 'this/is/a/new/location.bot', + }, ]); expect(remoteCalls[3]).toEqual([ 'bot:list:patch', '/some/path.bot', { - 'path': 'this/is/a/new/location.bot', - 'displayName': 'AuthBot', - 'secret': 'secret' - } + path: 'this/is/a/new/location.bot', + displayName: 'AuthBot', + secret: 'secret', + }, ]); }); it('should add a notification if a remote command fails when moving a bot file', async () => { - CommandServiceImpl.remoteCall = async function (...args: any[]) { + CommandServiceImpl.remoteCall = async function(...args: any[]) { switch (args[0]) { - case SharedConstants.Commands.Electron.DisplayContextMenu: return { id: 0 }; @@ -134,14 +166,13 @@ describe('The WelcomePageSagas', () => { it('should call the appropriate command when opening the bot file location', async () => { let openFileLocationArgs; - CommandServiceImpl.remoteCall = async function (...args: any[]) { + CommandServiceImpl.remoteCall = async function(...args: any[]) { switch (args[0]) { - case SharedConstants.Commands.Electron.DisplayContextMenu: return { id: 1 }; case SharedConstants.Commands.Electron.OpenFileLocation: - return openFileLocationArgs = args; + return (openFileLocationArgs = args); default: return null; @@ -150,19 +181,21 @@ describe('The WelcomePageSagas', () => { await mockStore.dispatch(openContextMenuForBot(mockBot)); await Promise.resolve(true); - expect(openFileLocationArgs).toEqual(['shell:open-file-location', 'this/is/a/new/location.bot']); + expect(openFileLocationArgs).toEqual([ + 'shell:open-file-location', + 'this/is/a/new/location.bot', + ]); }); it('should call the appropriate command when removing a bot from the list', async () => { let removeBotFromListArgs; - CommandServiceImpl.remoteCall = async function (...args: any[]) { + CommandServiceImpl.remoteCall = async function(...args: any[]) { switch (args[0]) { - case SharedConstants.Commands.Electron.DisplayContextMenu: return { id: 2 }; case SharedConstants.Commands.Bot.RemoveFromBotList: - return removeBotFromListArgs = args; + return (removeBotFromListArgs = args); default: return null; @@ -170,7 +203,10 @@ describe('The WelcomePageSagas', () => { }; await mockStore.dispatch(openContextMenuForBot(mockBot)); await Promise.resolve(true); - expect(removeBotFromListArgs).toEqual(['bot:list:remove', 'this/is/a/new/location.bot']); + expect(removeBotFromListArgs).toEqual([ + 'bot:list:remove', + 'this/is/a/new/location.bot', + ]); }); }); }); diff --git a/packages/app/client/src/data/sagas/welcomePageSagas.ts b/packages/app/client/src/data/sagas/welcomePageSagas.ts index ddb26b754..09f4e8df0 100644 --- a/packages/app/client/src/data/sagas/welcomePageSagas.ts +++ b/packages/app/client/src/data/sagas/welcomePageSagas.ts @@ -1,28 +1,80 @@ -import { BotInfo, newNotification, SharedConstants } from '@bfemulator/app-shared'; +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +import { + BotInfo, + newNotification, + SharedConstants, +} from '@bfemulator/app-shared'; + import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl'; import { beginAdd } from '../action/notificationActions'; -import { OPEN_CONTEXT_MENU_FOR_BOT, WelcomePageAction } from '../action/welcomePageActions'; +import { + OPEN_CONTEXT_MENU_FOR_BOT, + WelcomePageAction, +} from '../action/welcomePageActions'; + import { ForkEffect, put, takeEvery } from 'redux-saga/effects'; -function* openContextMenuForBot(action: WelcomePageAction): IterableIterator { +function* openContextMenuForBot( + action: WelcomePageAction +): IterableIterator { const menuItems = [ { label: 'Move...', id: 0 }, { label: 'Open file location', id: 1 }, - { label: 'Forget this bot', id: 2 } + { label: 'Forget this bot', id: 2 }, ]; - const result = yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.DisplayContextMenu, menuItems); + const result = yield CommandServiceImpl.remoteCall( + SharedConstants.Commands.Electron.DisplayContextMenu, + menuItems + ); switch (result.id) { case 0: yield* moveBotToNewLocation(action.payload); break; case 1: - yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.OpenFileLocation, action.payload.path); + yield CommandServiceImpl.remoteCall( + SharedConstants.Commands.Electron.OpenFileLocation, + action.payload.path + ); break; case 2: - yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Bot.RemoveFromBotList, action.payload.path); + yield CommandServiceImpl.remoteCall( + SharedConstants.Commands.Bot.RemoveFromBotList, + action.payload.path + ); break; default: @@ -32,20 +84,30 @@ function* openContextMenuForBot(action: WelcomePageAction): IterableIte } function* moveBotToNewLocation(bot: BotInfo): IterableIterator { - const newPath = yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.ShowSaveDialog, { - defaultPath: bot.path, - buttonLabel: 'Move', - nameFieldLabel: 'Name', - filters: [{ extensions: ['.bot'] }] - }); + const newPath = yield CommandServiceImpl.remoteCall( + SharedConstants.Commands.Electron.ShowSaveDialog, + { + defaultPath: bot.path, + buttonLabel: 'Move', + nameFieldLabel: 'Name', + filters: [{ extensions: ['.bot'] }], + } + ); if (!newPath) { return; } try { const { path: oldPath } = bot; bot.path = newPath; - yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.RenameFile, { path: oldPath, newPath }); - yield CommandServiceImpl.remoteCall(SharedConstants.Commands.Bot.PatchBotList, oldPath, bot); + yield CommandServiceImpl.remoteCall( + SharedConstants.Commands.Electron.RenameFile, + { path: oldPath, newPath } + ); + yield CommandServiceImpl.remoteCall( + SharedConstants.Commands.Bot.PatchBotList, + oldPath, + bot + ); } catch (e) { const errMsg = `Error occurred while moving the bot file: ${e}`; const notification = newNotification(errMsg); diff --git a/packages/app/client/src/data/store.ts b/packages/app/client/src/data/store.ts index ecd900ecb..47d2984bf 100644 --- a/packages/app/client/src/data/store.ts +++ b/packages/app/client/src/data/store.ts @@ -31,25 +31,28 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +import { ClientAwareSettings } from '@bfemulator/app-shared'; import { applyMiddleware, combineReducers, createStore, Store } from 'redux'; import promiseMiddleware from 'redux-promise-middleware'; import sagaMiddlewareFactory from 'redux-saga'; + +import { azureAuth, AzureAuthState } from './reducer/azureAuthReducer'; import { bot, BotState } from './reducer/bot'; import { chat, ChatState } from './reducer/chat'; +import { clientAwareSettings } from './reducer/clientAwareSettingsReducer'; import { dialog, DialogState } from './reducer/dialog'; import { editor, EditorState } from './reducer/editor'; import { explorer, ExplorerState } from './reducer/explorer'; -import { azureAuth, AzureAuthState } from './reducer/azureAuthReducer'; import { navBar, NavBarState } from './reducer/navBar'; import { notification, NotificationState } from './reducer/notification'; import { presentation, PresentationState } from './reducer/presentation'; -import { progressIndicator, ProgressIndicatorState } from './reducer/progressIndicator'; +import { + progressIndicator, + ProgressIndicatorState, +} from './reducer/progressIndicator'; import { resources, ResourcesState } from './reducer/resourcesReducer'; import { theme, ThemeState } from './reducer/themeReducer'; -import { clientAwareSettings } from './reducer/clientAwareSettingsReducer'; - import { applicationSagas } from './sagas'; -import { ClientAwareSettings } from '@bfemulator/app-shared'; export interface RootState { azureAuth?: AzureAuthState; @@ -70,28 +73,28 @@ export interface RootState { const sagaMiddleWare = sagaMiddlewareFactory(); const DEFAULT_STATE: RootState = {}; -const configureStore = (initialState: RootState = DEFAULT_STATE): Store => createStore( - combineReducers({ - azureAuth, - bot, - chat, - clientAwareSettings, - dialog, - editor, - explorer, - navBar, - notification, - presentation, - progressIndicator, - resources, - theme, - }), - initialState, - applyMiddleware( - sagaMiddleWare, - promiseMiddleware() - ) -); +const configureStore = ( + initialState: RootState = DEFAULT_STATE +): Store => + createStore( + combineReducers({ + azureAuth, + bot, + chat, + clientAwareSettings, + dialog, + editor, + explorer, + navBar, + notification, + presentation, + progressIndicator, + resources, + theme, + }), + initialState, + applyMiddleware(sagaMiddleWare, promiseMiddleware()) + ); const store = configureStore(); applicationSagas.forEach(saga => sagaMiddleWare.run(saga)); diff --git a/packages/app/client/src/extensions.ts b/packages/app/client/src/extensions.ts index bc150d5b2..fe084b823 100644 --- a/packages/app/client/src/extensions.ts +++ b/packages/app/client/src/extensions.ts @@ -31,29 +31,30 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +import { SharedConstants } from '@bfemulator/app-shared'; import { CommandRegistryImpl, CommandService, CommandServiceImpl, ExtensionConfig, - ExtensionInspector + ExtensionInspector, } from '@bfemulator/sdk-shared'; + import { ElectronIPC } from './ipc'; -import { SharedConstants } from '@bfemulator/app-shared'; // ============================================================================= export class Extension { private _ext: CommandService; - get unid(): string { + public get unid(): string { return this._unid; } - get config(): ExtensionConfig { + public get config(): ExtensionConfig { return this._config; } - constructor(private _config: ExtensionConfig, private _unid: string) { + public constructor(private _config: ExtensionConfig, private _unid: string) { this._ext = new CommandServiceImpl(ElectronIPC, `ext-${this._unid}`); /* this._ext.remoteCall('ext-ping') @@ -64,11 +65,15 @@ export class Extension { public inspectorForObject(obj: any): GetInspectorResult | null { const inspectors = this.config.client.inspectors || []; - const inspector = inspectors.find(inspectorArg => InspectorAPI.canInspect(inspectorArg, obj)); - return inspector ? { - extension: this, - inspector - } : null; + const inspector = inspectors.find(inspectorArg => + InspectorAPI.canInspect(inspectorArg, obj) + ); + return inspector + ? { + extension: this, + inspector, + } + : null; } public call(commandName: string, ...args: any[]): Promise { @@ -129,7 +134,10 @@ export class InspectorAPI { } } -export function getValueFromPath(source: { [prop: string]: any }, path: string): any { +export function getValueFromPath( + source: { [prop: string]: any }, + path: string +): any { const parts = path.split('.'); let val = source; for (let i = 0; i < parts.length; i++) { @@ -158,15 +166,19 @@ export interface ExtensionManager { getExtensions(): Extension[]; - inspectorForObject(obj: any, defaultToJson: boolean): GetInspectorResult | null; + inspectorForObject( + obj: any, + defaultToJson: boolean + ): GetInspectorResult | null; } // ============================================================================= -export const ExtensionManager = new class implements ExtensionManager { +class EmulatorExtensionManager implements ExtensionManager { private extensions: { [unid: string]: Extension } = {}; public addExtension(config: ExtensionConfig, unid: string) { this.removeExtension(unid); + // eslint-disable-next-line no-console console.log(`adding extension ${config.name}`); const ext = new Extension(config, unid); this.extensions[unid] = ext; @@ -174,30 +186,40 @@ export const ExtensionManager = new class implements ExtensionManager { public removeExtension(unid: string) { if (this.extensions[unid]) { + // eslint-disable-next-line no-console console.log(`removing extension ${this.extensions[unid].config.name}`); delete this.extensions[unid]; } } public findExtension(name: string): Extension { - return this.getExtensions().find(extension => extension.config.name === name); + return this.getExtensions().find( + extension => extension.config.name === name + ); } public getExtensions(): Extension[] { return Object.keys(this.extensions).map(key => this.extensions[key]) || []; } - public inspectorForObject(obj: any, defaultToJson: boolean): GetInspectorResult | null { + public inspectorForObject( + obj: any, + defaultToJson: boolean + ): GetInspectorResult | null { let result = this.getExtensions() .map(extension => extension.inspectorForObject(obj)) - .filter(resultArg => !!resultArg).shift(); + .filter(resultArg => !!resultArg) + .shift(); if (!result && defaultToJson) { // Default to the JSON inspector + // eslint-disable-next-line typescript/no-use-before-define const jsonExtension = ExtensionManager.findExtension('JSON'); if (jsonExtension) { result = { extension: jsonExtension, - inspector: jsonExtension.config.client.inspectors ? jsonExtension.config.client.inspectors[0] : null + inspector: jsonExtension.config.client.inspectors + ? jsonExtension.config.client.inspectors[0] + : null, }; } } @@ -207,11 +229,15 @@ export const ExtensionManager = new class implements ExtensionManager { public registerCommands(commandRegistry: CommandRegistryImpl) { const { Connect, Disconnect } = SharedConstants.Commands.Extension; commandRegistry.registerCommand(Connect, (config: ExtensionConfig) => { + // eslint-disable-next-line typescript/no-use-before-define ExtensionManager.addExtension(config, config.location); }); commandRegistry.registerCommand(Disconnect, (location: string) => { + // eslint-disable-next-line typescript/no-use-before-define ExtensionManager.removeExtension(location); }); } -}; +} + +export const ExtensionManager = new EmulatorExtensionManager(); diff --git a/packages/app/client/src/hyperlinkHandler.ts b/packages/app/client/src/hyperlinkHandler.ts index c43148e07..201f28f97 100644 --- a/packages/app/client/src/hyperlinkHandler.ts +++ b/packages/app/client/src/hyperlinkHandler.ts @@ -31,12 +31,14 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -const Electron = (window as any).require('electron'); -const { shell } = Electron; -import { uniqueId } from '@bfemulator/sdk-shared'; -import { CommandServiceImpl } from './platform/commands/commandServiceImpl'; import * as URL from 'url'; + import { SharedConstants } from '@bfemulator/app-shared'; +import { uniqueId } from '@bfemulator/sdk-shared'; + +import { CommandServiceImpl } from './platform/commands/commandServiceImpl'; +const Electron = (window as any).require('electron'); +const { shell } = Electron; export function navigate(url: string) { try { @@ -55,14 +57,21 @@ export function navigate(url: string) { function navigateEmulatedOAuthUrl(oauthParam: string) { const { Commands } = SharedConstants; - let parts = oauthParam.split('&&&'); - CommandServiceImpl - .remoteCall(Commands.OAuth.SendTokenResponse, parts[0], parts[1], 'emulatedToken_' + uniqueId()) - .catch(); + const parts = oauthParam.split('&&&'); + CommandServiceImpl.remoteCall( + Commands.OAuth.SendTokenResponse, + parts[0], + parts[1], + 'emulatedToken_' + uniqueId() + ).catch(); } function navigateOAuthUrl(oauthParam: string) { const { Commands } = SharedConstants; - let parts = oauthParam.split('&&&'); - CommandServiceImpl.remoteCall(Commands.OAuth.CreateOAuthWindow, parts[0], parts[1]).catch(); + const parts = oauthParam.split('&&&'); + CommandServiceImpl.remoteCall( + Commands.OAuth.CreateOAuthWindow, + parts[0], + parts[1] + ).catch(); } diff --git a/packages/app/client/src/index.tsx b/packages/app/client/src/index.tsx index fd1980ada..6d6067c87 100644 --- a/packages/app/client/src/index.tsx +++ b/packages/app/client/src/index.tsx @@ -32,17 +32,18 @@ // // for hot reloading import { Provider } from 'react-redux'; -import * as React from 'react'; import * as ReactDOM from 'react-dom'; +import * as React from 'react'; +import { newNotification, SharedConstants } from '@bfemulator/app-shared'; + +import { LogService } from './platform/log/logService'; import interceptError from './interceptError'; import interceptHyperlink from './interceptHyperlink'; import Main from './ui/shell/mainContainer'; import { store } from './data/store'; import { CommandServiceImpl } from './platform/commands/commandServiceImpl'; -import { LogService } from './platform/log/logService'; import { showWelcomePage } from './data/editorHelpers'; import { CommandRegistry, registerAllCommands } from './commands'; -import { SharedConstants, newNotification } from '@bfemulator/app-shared'; import { beginAdd } from './data/action/notificationActions'; import { globalHandlers } from './utils/eventHandlers'; import './ui/styles/globals.scss'; @@ -66,7 +67,9 @@ CommandServiceImpl.remoteCall(SharedConstants.Commands.ClientInit.Loaded) .then(() => { showWelcomePage(); // do actions on main side that might open a document, so that they will be active over the welcome screen - CommandServiceImpl.remoteCall(SharedConstants.Commands.ClientInit.PostWelcomeScreen); + CommandServiceImpl.remoteCall( + SharedConstants.Commands.ClientInit.PostWelcomeScreen + ); window.addEventListener('keydown', globalHandlers); }) .catch(err => { diff --git a/packages/app/client/src/inspector-preload.js b/packages/app/client/src/inspector-preload.js index 602f891ba..0dcc42258 100644 --- a/packages/app/client/src/inspector-preload.js +++ b/packages/app/client/src/inspector-preload.js @@ -31,6 +31,7 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +// eslint-disable-next-line typescript/no-var-requires const { ipcRenderer, remote } = require('electron'); ipcRenderer.on('inspect', (sender, obj) => { @@ -42,7 +43,7 @@ ipcRenderer.on('bot-updated', (sender, bot) => { window.host.dispatch('bot-updated', bot); }); -ipcRenderer.on('toggle-dev-tools', (sender) => { +ipcRenderer.on('toggle-dev-tools', () => { remote.getCurrentWebContents().toggleDevTools(); }); @@ -55,29 +56,35 @@ ipcRenderer.on('theme', (sender, ...args) => { }); window.host = { + bot: {}, handlers: { - 'inspect': [], - 'bot-updated': [], 'accessory-click': [], - 'theme': [] + 'bot-updated': [], + inspect: [], + theme: [], }, - bot: {}, logger: { + error: function(message) { + ipcRenderer.sendToHost('logger.error', message); + }, log: function(message) { ipcRenderer.sendToHost('logger.log', message); }, - error: function(message) { - ipcRenderer.sendToHost('logger.error', message); - } }, on: function(event, handler) { - if (handler && Array.isArray(this.handlers[event]) && !this.handlers[event].includes(handler)) { + if ( + handler && + Array.isArray(this.handlers[event]) && + !this.handlers[event].includes(handler) + ) { this.handlers[event].push(handler); } return () => { - this.handlers[event] = this.handlers[event].filter(item => item !== handler); - } + this.handlers[event] = this.handlers[event].filter( + item => item !== handler + ); + }; }, enableAccessory: function(id, enabled) { diff --git a/packages/app/client/src/interceptError.ts b/packages/app/client/src/interceptError.ts index 222ec610a..4e36b90da 100644 --- a/packages/app/client/src/interceptError.ts +++ b/packages/app/client/src/interceptError.ts @@ -34,13 +34,19 @@ // import * as log from './v1/log'; export default function interceptError() { - (process as NodeJS.EventEmitter).on('uncaughtException', _error => { - // log.error('[err-client]', error.message, error.stack); - }); + (process as NodeJS.EventEmitter).on('uncaughtException', _error => { + // log.error('[err-client]', error.message, error.stack); + }); - window.onerror = (_message: string, _filename?: string, _lineno?: number, _colno?: number, _error?: Error) => { - // log.error('[err-client]', message, filename, lineno, colno, error); + window.onerror = ( + _message: string, + _filename?: string, + _lineno?: number, + _colno?: number, + _error?: Error + ) => { + // log.error('[err-client]', message, filename, lineno, colno, error); - return true; // prevent default handler - }; + return true; // prevent default handler + }; } diff --git a/packages/app/client/src/ipc.ts b/packages/app/client/src/ipc.ts index a98ddbf34..000e964be 100644 --- a/packages/app/client/src/ipc.ts +++ b/packages/app/client/src/ipc.ts @@ -31,11 +31,11 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -const electron = (window as any).require('electron'); - import { Channel, Disposable, IPC } from '@bfemulator/sdk-shared'; -export const ElectronIPC = new class extends IPC { +const electron = (window as any).require('electron'); + +class ElectronIPCImpl extends IPC { constructor() { super(); electron.ipcRenderer.on('ipc:message', (_sender: any, ...args: any[]) => { @@ -47,11 +47,13 @@ export const ElectronIPC = new class extends IPC { }); } - send(...args: any[]): void { + public send(...args: any[]): void { electron.ipcRenderer.send('ipc:message', ...args); } - registerChannel(channel: Channel): Disposable { + public registerChannel(channel: Channel): Disposable { return super.registerChannel(channel); } -}; +} + +export const ElectronIPC = new ElectronIPCImpl(); diff --git a/packages/app/client/src/platform/commands/commandServiceImpl.ts b/packages/app/client/src/platform/commands/commandServiceImpl.ts index 8d7f76131..a49f49db2 100644 --- a/packages/app/client/src/platform/commands/commandServiceImpl.ts +++ b/packages/app/client/src/platform/commands/commandServiceImpl.ts @@ -36,35 +36,48 @@ import { CommandService, CommandServiceImpl as InternalSharedService, Disposable, - DisposableImpl + DisposableImpl, } from '@bfemulator/sdk-shared'; + import { CommandRegistry } from '../../commands'; import { ElectronIPC } from '../../ipc'; -export const CommandServiceImpl = new class extends DisposableImpl implements CommandService { - +class CServiceImpl extends DisposableImpl implements CommandService { private readonly _service: InternalSharedService; - init() { return null; } + public init() { + return null; + } - public get registry() { return this._service.registry; } + public get registry() { + return this._service.registry; + } constructor() { super(); - this._service = new InternalSharedService(ElectronIPC, 'command-service', CommandRegistry); + this._service = new InternalSharedService( + ElectronIPC, + 'command-service', + CommandRegistry + ); super.toDispose(this._service); } - call(commandName: string, ...args: any[]): Promise { + public call(commandName: string, ...args: any[]): Promise { return this._service.call(commandName, ...args); } - remoteCall(commandName: string, ...args: any[]): Promise { + public remoteCall(commandName: string, ...args: any[]): Promise { return this._service.remoteCall(commandName, ...args); } - on(event: string, handler?: CommandHandler): Disposable; - on(event: 'command-not-found', handler?: (commandName: string, ...args: any[]) => any) { + public on(event: string, handler?: CommandHandler): Disposable; + public on( + event: 'command-not-found', + handler?: (commandName: string, ...args: any[]) => any + ) { return this._service.on(event, handler); } -}; +} + +export const CommandServiceImpl = new CServiceImpl(); diff --git a/packages/app/client/src/platform/log/logService.ts b/packages/app/client/src/platform/log/logService.ts index 6f5734ec7..5c7295eda 100644 --- a/packages/app/client/src/platform/log/logService.ts +++ b/packages/app/client/src/platform/log/logService.ts @@ -33,31 +33,37 @@ import { SharedConstants } from '@bfemulator/app-shared'; import LogEntry from '@bfemulator/emulator-core/lib/types/log/entry'; -import { DisposableImpl, CommandRegistryImpl } from '@bfemulator/sdk-shared'; +import { CommandRegistryImpl, DisposableImpl } from '@bfemulator/sdk-shared'; + import * as ChatActions from '../../data/action/chatActions'; -import { store } from '../../data/store'; import * as chatHelpers from '../../data/chatHelpers'; +import { store } from '../../data/store'; -export function registerCommands(commandRegistry: CommandRegistryImpl) { - commandRegistry.registerCommand( - SharedConstants.Commands.Emulator.AppendToLog, - (conversationId: string, entry: LogEntry): any => { - LogService.logToChat(conversationId, entry); - }); -} - -export const LogService = new class extends DisposableImpl { - - init() { return null; } +class LogServiceImpl extends DisposableImpl { + public init() { + return null; + } - logToChat(conversationId: string, entry: LogEntry): void { + public logToChat(conversationId: string, entry: LogEntry): void { const documentId = chatHelpers.documentIdForConversation(conversationId); if (documentId) { + // eslint-disable-next-line typescript/no-use-before-define LogService.logToDocument(documentId, entry); } } - logToDocument(documentId: string, entry: LogEntry): void { + public logToDocument(documentId: string, entry: LogEntry): void { store.dispatch(ChatActions.appendToLog(documentId, entry)); } -}; +} + +export const LogService = new LogServiceImpl(); + +export function registerCommands(commandRegistry: CommandRegistryImpl) { + commandRegistry.registerCommand( + SharedConstants.Commands.Emulator.AppendToLog, + (conversationId: string, entry: LogEntry): any => { + LogService.logToChat(conversationId, entry); + } + ); +} diff --git a/packages/app/client/src/platform/settings/settingsService.ts b/packages/app/client/src/platform/settings/settingsService.ts index ecbecdbe2..483fcfbc9 100644 --- a/packages/app/client/src/platform/settings/settingsService.ts +++ b/packages/app/client/src/platform/settings/settingsService.ts @@ -31,20 +31,8 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { DisposableImpl, CommandRegistryImpl } from '@bfemulator/sdk-shared'; import { SharedConstants } from '@bfemulator/app-shared'; - -export function registerCommands(commandRegistry: CommandRegistryImpl) { - commandRegistry.registerCommand( - SharedConstants.Commands.Settings.ReceiveGlobalSettings, - (settings: { - url: string, - cwd: string - }): any => { - SettingsService.emulator.url = (settings.url || '').replace('[::]', 'localhost'); - SettingsService.emulator.cwd = (settings.cwd || '').replace(/\\/g, '/'); - }); -} +import { CommandRegistryImpl, DisposableImpl } from '@bfemulator/sdk-shared'; export interface EmulatorSettings { url?: string; @@ -87,16 +75,34 @@ class EmulatorSettingsImpl implements EmulatorSettings { } } -export const SettingsService = new class extends DisposableImpl { - +class EmulatorSettingsService extends DisposableImpl { private _emulator: EmulatorSettingsImpl; - get emulator(): EmulatorSettingsImpl { return this._emulator; } + get emulator(): EmulatorSettingsImpl { + return this._emulator; + } - init() { return null; } + public init() { + return null; + } constructor() { super(); this._emulator = new EmulatorSettingsImpl(); } -}; +} + +export const SettingsService = new EmulatorSettingsService(); + +export function registerCommands(commandRegistry: CommandRegistryImpl) { + commandRegistry.registerCommand( + SharedConstants.Commands.Settings.ReceiveGlobalSettings, + (settings: { url: string; cwd: string }): any => { + SettingsService.emulator.url = (settings.url || '').replace( + '[::]', + 'localhost' + ); + SettingsService.emulator.cwd = (settings.cwd || '').replace(/\\/g, '/'); + } + ); +} diff --git a/packages/app/client/src/registerServiceWorker.ts b/packages/app/client/src/registerServiceWorker.ts index 96eaff88e..217ffe168 100644 --- a/packages/app/client/src/registerServiceWorker.ts +++ b/packages/app/client/src/registerServiceWorker.ts @@ -31,7 +31,6 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -// tslint:disable:no-console // In production, we register a service worker to serve assets from local cache. // This lets the app load faster on subsequent visits in production, and gives @@ -43,105 +42,109 @@ // This link also includes instructions on opting out of this behavior. const isLocalhost = Boolean( - window.location.hostname === 'localhost' || - // [::1] is the IPv6 localhost address. - window.location.hostname === '[::1]' || - // 127.0.0.1/8 is considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) ); export default function register() { - if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { - // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL( - process.env.PUBLIC_URL!, - window.location.toString() - ); - if (publicUrl.origin !== window.location.origin) { - // Our service worker won't work if PUBLIC_URL is on a different origin - // from what our page is served on. This might happen if a CDN is used to - // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 - return; - } + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL( + process.env.PUBLIC_URL, + window.location.toString() + ); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 + return; + } - window.addEventListener('load', () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; - if (!isLocalhost) { - // Is not local host. Just register service worker - registerValidSW(swUrl); - } else { - // This is running on localhost. Lets check if a service worker still exists or not. - checkValidServiceWorker(swUrl); - } - }); - } + if (!isLocalhost) { + // Is not local host. Just register service worker + registerValidSW(swUrl); + } else { + // This is running on localhost. Lets check if a service worker still exists or not. + checkValidServiceWorker(swUrl); + } + }); + } } function registerValidSW(swUrl: string) { - navigator.serviceWorker - .register(swUrl) - .then(registration => { - registration.onupdatefound = () => { - const installingWorker = registration.installing; - if (installingWorker) { - installingWorker.onstatechange = () => { - if (installingWorker.state === 'installed') { - if (navigator.serviceWorker.controller) { - // At this point, the old content will have been purged and - // the fresh content will have been added to the cache. - // It's the perfect time to display a 'New content is - // available; please refresh.' message in your web app. - console.log('New content is available; please refresh.'); - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // 'Content is cached for offline use.' message. - console.log('Content is cached for offline use.'); - } - } - }; - } - }; - }) - .catch(error => { - console.error('Error during service worker registration:', error); - }); + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker) { + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the old content will have been purged and + // the fresh content will have been added to the cache. + // It's the perfect time to display a 'New content is + // available; please refresh.' message in your web app. + // eslint-disable-next-line no-console + console.log('New content is available; please refresh.'); + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // 'Content is cached for offline use.' message. + // eslint-disable-next-line no-console + console.log('Content is cached for offline use.'); + } + } + }; + } + }; + }) + .catch(error => { + // eslint-disable-next-line no-console + console.error('Error during service worker registration:', error); + }); } function checkValidServiceWorker(swUrl: string) { - // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl) - .then(response => { - // Ensure service worker exists, and that we really are getting a JS file. - if ( - response.status === 404 || - response.headers.get('content-type')!.indexOf('javascript') === -1 - ) { - // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then(registration => { - registration.unregister().then(() => { - window.location.reload(); - }); - }); - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl); - } - }) - .catch(() => { - console.log( - 'No internet connection found. App is running in offline mode.' - ); + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + if ( + response.status === 404 || + response.headers.get('content-type').indexOf('javascript') === -1 + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl); + } + }) + .catch(() => { + // eslint-disable-next-line no-console + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); } export function unregister() { - if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready.then(registration => { - registration.unregister(); - }); - } + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } } diff --git a/packages/app/client/src/shared.ts b/packages/app/client/src/shared.ts index 0c29de777..be0f8a07a 100644 --- a/packages/app/client/src/shared.ts +++ b/packages/app/client/src/shared.ts @@ -1,4 +1,36 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import '@bfemulator/app-shared'; -import '@bfemulator/ui-react'; -import '@bfemulator/sdk-shared'; import '@bfemulator/sdk-client'; +import '@bfemulator/sdk-shared'; +import '@bfemulator/ui-react'; diff --git a/packages/app/client/src/style.d.ts b/packages/app/client/src/style.d.ts index d408362e6..c17d975df 100644 --- a/packages/app/client/src/style.d.ts +++ b/packages/app/client/src/style.d.ts @@ -1,3 +1,35 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// declare module '*.scss' { let _: any; export default _; diff --git a/packages/app/client/src/ui/debug/storeVisualizer.tsx b/packages/app/client/src/ui/debug/storeVisualizer.tsx index 85031d9fe..36a6dbae2 100644 --- a/packages/app/client/src/ui/debug/storeVisualizer.tsx +++ b/packages/app/client/src/ui/debug/storeVisualizer.tsx @@ -31,15 +31,24 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +import { PrimaryButton } from '@bfemulator/ui-react'; import * as React from 'react'; import { connect } from 'react-redux'; -import * as styles from './storeVisualizer.scss'; import { RootState } from '../../data/store'; -import { PrimaryButton } from '@bfemulator/ui-react'; -type StateSlice = 'assetExplorer' | 'bot' | 'chat' | 'dialog' | 'editor' - | 'explorer' | 'navBar' | 'presentation' | 'server'; +import * as styles from './storeVisualizer.scss'; + +type StateSlice = + | 'assetExplorer' + | 'bot' + | 'chat' + | 'dialog' + | 'editor' + | 'explorer' + | 'navBar' + | 'presentation' + | 'server'; interface StoreVisualizerProps { enabled?: boolean; @@ -52,7 +61,10 @@ interface StoreVisualizerState { } /** Transparent overlay that helps visualize a selected slice of the state */ -class StoreVisualizerComponent extends React.Component { +class StoreVisualizerComponent extends React.Component< + StoreVisualizerProps, + StoreVisualizerState +> { constructor(props: StoreVisualizerProps) { super(props); @@ -61,15 +73,15 @@ class StoreVisualizerComponent extends React.Component - @@ -92,29 +104,36 @@ class StoreVisualizerComponent extends React.ComponentPresentation -
{ prettyState }
+
{prettyState}
+ className={styles.visualizerButton} + onClick={this.toggleShowing} + /> ); } else { return ( ); } } - render(): JSX.Element { - return this.props.enabled ? (
{ this.content }
) : null; + public render(): JSX.Element { + return this.props.enabled ? ( +
{this.content}
+ ) : null; } } -const mapStateToProps = (state: RootState): StoreVisualizerProps => ({ rootState: state }); +const mapStateToProps = (state: RootState): StoreVisualizerProps => ({ + rootState: state, +}); -export const StoreVisualizer = connect(mapStateToProps)(StoreVisualizerComponent) as any; +export const StoreVisualizer = connect(mapStateToProps)( + StoreVisualizerComponent +) as any; diff --git a/packages/app/client/src/ui/dialogs/azureLoginFailedDialog/azureLoginFailedDialog.spec.tsx b/packages/app/client/src/ui/dialogs/azureLoginFailedDialog/azureLoginFailedDialog.spec.tsx index 67247cb2b..30e9218fc 100644 --- a/packages/app/client/src/ui/dialogs/azureLoginFailedDialog/azureLoginFailedDialog.spec.tsx +++ b/packages/app/client/src/ui/dialogs/azureLoginFailedDialog/azureLoginFailedDialog.spec.tsx @@ -1,16 +1,50 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import * as React from 'react'; import { Provider } from 'react-redux'; import { mount } from 'enzyme'; -import { AzureLoginFailedDialogContainer } from './azureLoginFailedDialogContainer'; import { combineReducers, createStore } from 'redux'; + import { azureAuth } from '../../../data/reducer/azureAuthReducer'; + +import { AzureLoginFailedDialogContainer } from './azureLoginFailedDialogContainer'; import { AzureLoginFailedDialog } from './azureLoginFailedDialog'; jest.mock('../service', () => ({ DialogService: { showDialog: () => Promise.resolve(true), hideDialog: () => Promise.resolve(false), - } + }, })); jest.mock('../dialogStyles.scss', () => ({})); @@ -19,9 +53,11 @@ describe('The AzureLoginFailedDialogContainer component should', () => { let parent; let node; beforeEach(() => { - parent = mount( - - ); + parent = mount( + + + + ); node = parent.find(AzureLoginFailedDialog); }); @@ -40,10 +76,10 @@ describe('The AzureLoginFailedDialogContainer component should', () => { const { cancel } = instance.props; Object.defineProperty(instance, 'props', { value: { - cancel + cancel, }, writable: true, - configurable: true + configurable: true, }); const spy = spyOn(instance.props, 'cancel'); instance.onDialogCancel(); diff --git a/packages/app/client/src/ui/dialogs/azureLoginFailedDialog/azureLoginFailedDialog.tsx b/packages/app/client/src/ui/dialogs/azureLoginFailedDialog/azureLoginFailedDialog.tsx index 9ff2db3a7..4721bb929 100644 --- a/packages/app/client/src/ui/dialogs/azureLoginFailedDialog/azureLoginFailedDialog.tsx +++ b/packages/app/client/src/ui/dialogs/azureLoginFailedDialog/azureLoginFailedDialog.tsx @@ -1,6 +1,39 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +import { Dialog, DialogFooter, PrimaryButton } from '@bfemulator/ui-react'; import * as React from 'react'; import { Component } from 'react'; -import { Dialog, DialogFooter, PrimaryButton } from '@bfemulator/ui-react'; + import * as styles from '../dialogStyles.scss'; export interface AzureLoginSuccessDialogState { @@ -12,25 +45,31 @@ export interface AzureLoginSuccessDialogProps { persistLogin: boolean; } -export class AzureLoginFailedDialog extends Component { - - constructor(props: AzureLoginSuccessDialogProps = {} as any, state: AzureLoginSuccessDialogState) { +export class AzureLoginFailedDialog extends Component< + AzureLoginSuccessDialogProps, + AzureLoginSuccessDialogState +> { + constructor( + props: AzureLoginSuccessDialogProps = {} as any, + state: AzureLoginSuccessDialogState + ) { super(props, state); this.state = { rememberMeChecked: !!props.persistLogin }; } public render() { return ( - +

- { 'Your authentication attempt was canceled or was not completed successfully. ' + - 'You will need to authenticate with Azure before some services can be added to the emulator.' } + {'Your authentication attempt was canceled or was not completed successfully. ' + + 'You will need to authenticate with Azure before some services can be added to the emulator.'}

- +
); @@ -38,5 +77,5 @@ export class AzureLoginFailedDialog extends Component { this.props.cancel(this.state.rememberMeChecked); - } + }; } diff --git a/packages/app/client/src/ui/dialogs/azureLoginFailedDialog/azureLoginFailedDialogContainer.ts b/packages/app/client/src/ui/dialogs/azureLoginFailedDialog/azureLoginFailedDialogContainer.ts index 3be715f06..dfe786969 100644 --- a/packages/app/client/src/ui/dialogs/azureLoginFailedDialog/azureLoginFailedDialogContainer.ts +++ b/packages/app/client/src/ui/dialogs/azureLoginFailedDialog/azureLoginFailedDialogContainer.ts @@ -32,19 +32,24 @@ // import { connect } from 'react-redux'; + import { RootState } from '../../../data/store'; import { DialogService } from '../service'; + import { AzureLoginFailedDialog } from './azureLoginFailedDialog'; -const mapStateToProps = (state: RootState, ownProps: { [propName: string]: any }) => { +const mapStateToProps = ( + state: RootState, + ownProps: { [propName: string]: any } +) => { return { - ...ownProps + ...ownProps, }; }; const mapDispatchToProps = (_dispatch: () => void) => { return { - cancel: persistLogin => DialogService.hideDialog(persistLogin) + cancel: persistLogin => DialogService.hideDialog(persistLogin), }; }; diff --git a/packages/app/client/src/ui/dialogs/azureLoginPromptDialog/azureLoginPromptDialog.spec.tsx b/packages/app/client/src/ui/dialogs/azureLoginPromptDialog/azureLoginPromptDialog.spec.tsx index 45d02c5b4..b28cf9f80 100644 --- a/packages/app/client/src/ui/dialogs/azureLoginPromptDialog/azureLoginPromptDialog.spec.tsx +++ b/packages/app/client/src/ui/dialogs/azureLoginPromptDialog/azureLoginPromptDialog.spec.tsx @@ -1,16 +1,50 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import * as React from 'react'; import { Provider } from 'react-redux'; import { mount } from 'enzyme'; -import { AzureLoginPromptDialogContainer } from './azureLoginPromptDialogContainer'; import { createStore } from 'redux'; + import { azureAuth } from '../../../data/reducer/azureAuthReducer'; + +import { AzureLoginPromptDialogContainer } from './azureLoginPromptDialogContainer'; import { AzureLoginPromptDialog } from './azureLoginPromptDialog'; jest.mock('../service', () => ({ DialogService: { showDialog: () => Promise.resolve(1), hideDialog: () => Promise.resolve(0), - } + }, })); jest.mock('../dialogStyles.scss', () => ({})); @@ -19,22 +53,25 @@ jest.mock('../../dialogs/', () => ({ AzureLoginSuccessDialogContainer: () => undefined, BotCreationDialog: () => undefined, DialogService: { showDialog: () => Promise.resolve(true) }, - SecretPromptDialog: () => undefined + SecretPromptDialog: () => undefined, })); describe('The AzureLoginPromptDialog component should', () => { - it('should render deeply', () => { - const parent = mount( - - ); + const parent = mount( + + + + ); expect(parent.find(AzureLoginPromptDialogContainer)).not.toBe(null); }); it('should contain both a cancel and confirm function in the props', () => { - const parent = mount( - - ); + const parent = mount( + + + + ); const prompt = parent.find(AzureLoginPromptDialog); expect(typeof (prompt.props() as any).cancel).toBe('function'); diff --git a/packages/app/client/src/ui/dialogs/azureLoginPromptDialog/azureLoginPromptDialog.tsx b/packages/app/client/src/ui/dialogs/azureLoginPromptDialog/azureLoginPromptDialog.tsx index 2220d9842..44dd22833 100644 --- a/packages/app/client/src/ui/dialogs/azureLoginPromptDialog/azureLoginPromptDialog.tsx +++ b/packages/app/client/src/ui/dialogs/azureLoginPromptDialog/azureLoginPromptDialog.tsx @@ -1,6 +1,44 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +import { + DefaultButton, + Dialog, + DialogFooter, + PrimaryButton, +} from '@bfemulator/ui-react'; import * as React from 'react'; import { Component } from 'react'; -import { DefaultButton, Dialog, DialogFooter, PrimaryButton } from '@bfemulator/ui-react'; + import * as styles from '../dialogStyles.scss'; export interface AzureLoginPromptDialogProps { @@ -8,26 +46,39 @@ export interface AzureLoginPromptDialogProps { confirm: () => void; } -export class AzureLoginPromptDialog extends Component { +export class AzureLoginPromptDialog extends Component< + AzureLoginPromptDialogProps, + {} +> { public render() { return ( - -

{ 'Use your Azure account to sign in to all your Azure services, ' + - 'such as Azure Bot Service, Dispatch, LUIS, and QnA Maker.' } +

+

+ {'Use your Azure account to sign in to all your Azure services, ' + + 'such as Azure Bot Service, Dispatch, LUIS, and QnA Maker.'} - Don't have an Azure Account? Sign up. + {"Don't have an Azure Account? Sign up."}

-

{ 'By signing in to your services, you can register any app in that ' + - 'service with your bot without having to enter in credentials manually.' }

+

+ {'By signing in to your services, you can register any app in that ' + + 'service with your bot without having to enter in credentials manually.'} +

Learn more about registering services

- - + +
); diff --git a/packages/app/client/src/ui/dialogs/azureLoginPromptDialog/azureLoginPromptDialogContainer.ts b/packages/app/client/src/ui/dialogs/azureLoginPromptDialog/azureLoginPromptDialogContainer.ts index fcbe0a5b8..8885aa937 100644 --- a/packages/app/client/src/ui/dialogs/azureLoginPromptDialog/azureLoginPromptDialogContainer.ts +++ b/packages/app/client/src/ui/dialogs/azureLoginPromptDialog/azureLoginPromptDialogContainer.ts @@ -32,13 +32,20 @@ // import { connect } from 'react-redux'; + import { DialogService } from '../service'; -import { AzureLoginPromptDialog, AzureLoginPromptDialogProps } from './azureLoginPromptDialog'; -const mapDispatchToProps = (_dispatch: () => void): AzureLoginPromptDialogProps => { +import { + AzureLoginPromptDialog, + AzureLoginPromptDialogProps, +} from './azureLoginPromptDialog'; + +const mapDispatchToProps = ( + _dispatch: () => void +): AzureLoginPromptDialogProps => { return { cancel: () => DialogService.hideDialog(0), - confirm: () => DialogService.hideDialog(1) + confirm: () => DialogService.hideDialog(1), }; }; diff --git a/packages/app/client/src/ui/dialogs/azureLoginSuccessDialog/azureLoginSuccessDialog.spec.tsx b/packages/app/client/src/ui/dialogs/azureLoginSuccessDialog/azureLoginSuccessDialog.spec.tsx index 5fd42b2ce..5df66e352 100644 --- a/packages/app/client/src/ui/dialogs/azureLoginSuccessDialog/azureLoginSuccessDialog.spec.tsx +++ b/packages/app/client/src/ui/dialogs/azureLoginSuccessDialog/azureLoginSuccessDialog.spec.tsx @@ -1,16 +1,50 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import * as React from 'react'; import { Provider } from 'react-redux'; import { mount } from 'enzyme'; -import { AzureLoginSuccessDialogContainer } from './azureLoginSuccessDialogContainer'; import { combineReducers, createStore } from 'redux'; + import { azureAuth } from '../../../data/reducer/azureAuthReducer'; + +import { AzureLoginSuccessDialogContainer } from './azureLoginSuccessDialogContainer'; import { AzureLoginSuccessDialog } from './azureLoginSuccessDialog'; jest.mock('../service', () => ({ DialogService: { showDialog: () => Promise.resolve(true), hideDialog: () => Promise.resolve(false), - } + }, })); jest.mock('../dialogStyles.scss', () => ({})); @@ -19,9 +53,11 @@ describe('The AzureLoginSuccessDialogContainer component should', () => { let parent; let node; beforeEach(() => { - parent = mount( - - ); + parent = mount( + + + + ); node = parent.find(AzureLoginSuccessDialog); }); @@ -48,10 +84,10 @@ describe('The AzureLoginSuccessDialogContainer component should', () => { const { cancel } = instance.props; Object.defineProperty(instance, 'props', { value: { - cancel + cancel, }, writable: true, - configurable: true + configurable: true, }); const spy = spyOn(instance.props, 'cancel'); instance.onDialogCancel(); diff --git a/packages/app/client/src/ui/dialogs/azureLoginSuccessDialog/azureLoginSuccessDialog.tsx b/packages/app/client/src/ui/dialogs/azureLoginSuccessDialog/azureLoginSuccessDialog.tsx index 21bbafedd..e611a6a1d 100644 --- a/packages/app/client/src/ui/dialogs/azureLoginSuccessDialog/azureLoginSuccessDialog.tsx +++ b/packages/app/client/src/ui/dialogs/azureLoginSuccessDialog/azureLoginSuccessDialog.tsx @@ -1,6 +1,44 @@ -import { Checkbox, Dialog, DialogFooter, PrimaryButton } from '@bfemulator/ui-react'; +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +import { + Checkbox, + Dialog, + DialogFooter, + PrimaryButton, +} from '@bfemulator/ui-react'; import * as React from 'react'; import { ChangeEvent, Component } from 'react'; + import * as styles from '../dialogStyles.scss'; export interface AzureLoginSuccessDialogState { @@ -14,27 +52,33 @@ export interface AzureLoginSuccessDialogProps { [propName: string]: any; } -export class AzureLoginSuccessDialog extends Component { - - constructor(props: AzureLoginSuccessDialogProps = {} as any, state: AzureLoginSuccessDialogState) { +export class AzureLoginSuccessDialog extends Component< + AzureLoginSuccessDialogProps, + AzureLoginSuccessDialogState +> { + constructor( + props: AzureLoginSuccessDialogProps = {} as any, + state: AzureLoginSuccessDialogState + ) { super(props, state); this.state = { rememberMeChecked: !!props.persistLogin }; } public render() { return ( - +

You are now signed in with your Azure account

- +
); @@ -42,10 +86,10 @@ export class AzureLoginSuccessDialog extends Component { this.props.cancel(this.state.rememberMeChecked); - } + }; private checkBoxChanged = (event: ChangeEvent) => { const { checked } = event.target; this.setState({ rememberMeChecked: checked }); - } + }; } diff --git a/packages/app/client/src/ui/dialogs/azureLoginSuccessDialog/azureLoginSuccessDialogContainer.ts b/packages/app/client/src/ui/dialogs/azureLoginSuccessDialog/azureLoginSuccessDialogContainer.ts index 57087a755..0baa16303 100644 --- a/packages/app/client/src/ui/dialogs/azureLoginSuccessDialog/azureLoginSuccessDialogContainer.ts +++ b/packages/app/client/src/ui/dialogs/azureLoginSuccessDialog/azureLoginSuccessDialogContainer.ts @@ -32,21 +32,31 @@ // import { connect } from 'react-redux'; + import { RootState } from '../../../data/store'; import { DialogService } from '../service'; -import { AzureLoginSuccessDialog, AzureLoginSuccessDialogProps } from './azureLoginSuccessDialog'; -const mapStateToProps = (state: RootState, ownProps: { [propName: string]: any }): AzureLoginSuccessDialogProps => { +import { + AzureLoginSuccessDialog, + AzureLoginSuccessDialogProps, +} from './azureLoginSuccessDialog'; + +const mapStateToProps = ( + state: RootState, + ownProps: { [propName: string]: any } +): AzureLoginSuccessDialogProps => { const { persistLogin } = state.azureAuth; return { persistLogin, - ...ownProps + ...ownProps, }; }; -const mapDispatchToProps = (_dispatch: () => void): AzureLoginSuccessDialogProps => { +const mapDispatchToProps = ( + _dispatch: () => void +): AzureLoginSuccessDialogProps => { return { - cancel: persistLogin => DialogService.hideDialog(persistLogin) + cancel: persistLogin => DialogService.hideDialog(persistLogin), }; }; diff --git a/packages/app/client/src/ui/dialogs/botCreationDialog/botCreationDialog.spec.tsx b/packages/app/client/src/ui/dialogs/botCreationDialog/botCreationDialog.spec.tsx index e8b3ddc74..f20b54d09 100644 --- a/packages/app/client/src/ui/dialogs/botCreationDialog/botCreationDialog.spec.tsx +++ b/packages/app/client/src/ui/dialogs/botCreationDialog/botCreationDialog.spec.tsx @@ -33,8 +33,10 @@ import { mount } from 'enzyme'; import * as React from 'react'; + import { CommandServiceImpl } from '../../../platform/commands/commandServiceImpl'; import { ActiveBotHelper } from '../../helpers/activeBotHelper'; + import { BotCreationDialog, BotCreationDialogState } from './botCreationDialog'; jest.mock('./botCreationDialog.scss', () => ({})); @@ -42,19 +44,19 @@ jest.mock('../index', () => null); jest.mock('../../../utils', () => ({ generateBotSecret: () => { return Math.random() + ''; - } + }, })); jest.mock('../../helpers/activeBotHelper', () => ({ ActiveBotHelper: { - confirmAndCreateBot: async () => true - } + confirmAndCreateBot: async () => true, + }, })); describe('BotCreationDialog tests', () => { let testWrapper; beforeEach(() => { - testWrapper = mount(); + testWrapper = mount(); }); it('should render without throwing an error', () => { @@ -65,12 +67,18 @@ describe('BotCreationDialog tests', () => { const initialState = testWrapper.state as Partial; expect(initialState.secret).toBeFalsy(); // toggle on encryption - (testWrapper.instance() as any).onEncryptKeyChange({ target: { checked: true } } as any); + (testWrapper.instance() as any).onEncryptKeyChange({ + target: { checked: true }, + } as any); const state1 = testWrapper.state() as Partial; expect(state1.secret).not.toBeFalsy(); // toggle encryption off and then on again - (testWrapper.instance() as any).onEncryptKeyChange({ target: { checked: false } } as any); - (testWrapper.instance() as any).onEncryptKeyChange({ target: { checked: true } } as any); + (testWrapper.instance() as any).onEncryptKeyChange({ + target: { checked: false }, + } as any); + (testWrapper.instance() as any).onEncryptKeyChange({ + target: { checked: true }, + } as any); const state2 = testWrapper.state() as Partial; expect(state2.secret).not.toBeFalsy(); expect(state1.secret).not.toEqual(state2.secret); @@ -95,10 +103,10 @@ describe('BotCreationDialog tests', () => { const backupExec = window.document.execCommand; const mockExec = jest.fn((_command: string) => null); const backupGetElementById = window.document.getElementById; - const mockGetElementById = (_selector) => ({ + const mockGetElementById = _selector => ({ removeAttribute: () => null, select: () => null, - setAttribute: () => null + setAttribute: () => null, }); (window.document.getElementById as any) = mockGetElementById; window.document.execCommand = mockExec; @@ -112,7 +120,9 @@ describe('BotCreationDialog tests', () => { }); it('should set state via input change handlers', () => { - const mockEvent = { target: { value: 'someEndpoint', dataset: { prop: 'endpoint' } } }; + const mockEvent = { + target: { value: 'someEndpoint', dataset: { prop: 'endpoint' } }, + }; (testWrapper.instance() as any).onInputChange(mockEvent as any); mockEvent.target.dataset.prop = 'appId'; @@ -145,7 +155,9 @@ describe('BotCreationDialog tests', () => { (testWrapper.instance() as any).onChannelServiceChange(mockCheck as any); state = testWrapper.state() as Partial; - expect((state.endpoint as any).channelService).toBe('https://botframework.azure.us'); + expect((state.endpoint as any).channelService).toBe( + 'https://botframework.azure.us' + ); // unchecked mockCheck.target.checked = false; @@ -156,39 +168,56 @@ describe('BotCreationDialog tests', () => { }); it('should validate the endpoint', () => { - expect((testWrapper.instance() as any).validateEndpoint('http://localhost:3000/api/messages')).toBe(''); - expect((testWrapper.instance() as any).validateEndpoint('http://localhost:3000')) - .toBe(`Please include route if necessary: "/api/messages"`); + expect( + (testWrapper.instance() as any).validateEndpoint( + 'http://localhost:3000/api/messages' + ) + ).toBe(''); + expect( + (testWrapper.instance() as any).validateEndpoint('http://localhost:3000') + ).toBe(`Please include route if necessary: "/api/messages"`); }); it('should save and connect', async () => { const instance = testWrapper.instance(); - const remoteCallSpy = jest.spyOn(CommandServiceImpl, 'remoteCall').mockResolvedValue('some/path'); - const confirmAndCreateSpy = jest.spyOn(ActiveBotHelper, 'confirmAndCreateBot').mockResolvedValue(true); + const remoteCallSpy = jest + .spyOn(CommandServiceImpl, 'remoteCall') + .mockResolvedValue('some/path'); + const confirmAndCreateSpy = jest + .spyOn(ActiveBotHelper, 'confirmAndCreateBot') + .mockResolvedValue(true); await instance.onSaveAndConnect(); - expect(remoteCallSpy).toHaveBeenCalledWith('shell:showExplorer-save-dialog', { - buttonLabel: 'Save', - defaultPath: 'some/path', - filters: [{ 'extensions': ['bot'], 'name': 'Bot Files' }], - showsTagField: false, - title: 'Save as' - }); - expect(confirmAndCreateSpy).toHaveBeenCalledWith({ - 'description': '', - 'name': '', - 'overrides': null, - 'padlock': '', - 'path': 'some/path', - 'services': [{ - 'appId': '', - 'appPassword': '', - 'channelService': undefined, - 'endpoint': '', - 'id': jasmine.any(String), - 'name': '', - 'type': 'endpoint' - }], - 'version': '2.0' - }, null ); + expect(remoteCallSpy).toHaveBeenCalledWith( + 'shell:showExplorer-save-dialog', + { + buttonLabel: 'Save', + defaultPath: 'some/path', + filters: [{ extensions: ['bot'], name: 'Bot Files' }], + showsTagField: false, + title: 'Save as', + } + ); + expect(confirmAndCreateSpy).toHaveBeenCalledWith( + { + description: '', + name: '', + overrides: null, + padlock: '', + path: 'some/path', + services: [ + { + appId: '', + appPassword: '', + channelService: undefined, + endpoint: '', + id: jasmine.any(String), + name: '', + type: 'endpoint', + }, + ], + version: '2.0', + }, + null + ); }); }); diff --git a/packages/app/client/src/ui/dialogs/botCreationDialog/botCreationDialog.tsx b/packages/app/client/src/ui/dialogs/botCreationDialog/botCreationDialog.tsx index 0364111ed..f84c7985a 100644 --- a/packages/app/client/src/ui/dialogs/botCreationDialog/botCreationDialog.tsx +++ b/packages/app/client/src/ui/dialogs/botCreationDialog/botCreationDialog.tsx @@ -31,7 +31,12 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { BotConfigWithPath, BotConfigWithPathImpl, uniqueId } from '@bfemulator/sdk-shared'; +import { newNotification, SharedConstants } from '@bfemulator/app-shared'; +import { + BotConfigWithPath, + BotConfigWithPathImpl, + uniqueId, +} from '@bfemulator/sdk-shared'; import { Checkbox, DefaultButton, @@ -46,14 +51,15 @@ import { EndpointService } from 'botframework-config/lib/models'; import { IEndpointService, ServiceTypes } from 'botframework-config/lib/schema'; import { ChangeEvent } from 'react'; import * as React from 'react'; -import * as styles from './botCreationDialog.scss'; + +import { beginAdd } from '../../../data/action/notificationActions'; +import { store } from '../../../data/store'; import { CommandServiceImpl } from '../../../platform/commands/commandServiceImpl'; +import { generateBotSecret } from '../../../utils'; import { ActiveBotHelper } from '../../helpers/activeBotHelper'; import { DialogService } from '../service'; -import { newNotification, SharedConstants } from '@bfemulator/app-shared'; -import { store } from '../../../data/store'; -import { beginAdd } from '../../../data/action/notificationActions'; -import { generateBotSecret } from '../../../utils'; + +import * as styles from './botCreationDialog.scss'; export interface BotCreationDialogState { bot: BotConfigWithPath; @@ -63,8 +69,11 @@ export interface BotCreationDialogState { revealSecret: boolean; } -export class BotCreationDialog extends React.Component<{}, BotCreationDialogState> { - constructor(props: {}, context: BotCreationDialogState) { +export class BotCreationDialog extends React.Component< + {}, + BotCreationDialogState +> { + public constructor(props: {}, context: BotCreationDialogState) { super(props, context); this.state = { @@ -73,7 +82,7 @@ export class BotCreationDialog extends React.Component<{}, BotCreationDialogStat description: '', padlock: '', services: [], - path: '' + path: '', }), endpoint: new EndpointService({ type: ServiceTypes.Endpoint, @@ -81,120 +90,132 @@ export class BotCreationDialog extends React.Component<{}, BotCreationDialogStat id: uniqueId(), appId: '', appPassword: '', - endpoint: '' + endpoint: '', }), secret: '', encryptKey: false, - revealSecret: true + revealSecret: true, }; } - render(): JSX.Element { + public render(): JSX.Element { const { secret, bot, endpoint, encryptKey, revealSecret } = this.state; const secretCriteria = encryptKey ? secret : true; - const requiredFieldsCompleted = bot - && endpoint.endpoint - && bot.name - && secretCriteria; + const requiredFieldsCompleted = + bot && endpoint.endpoint && bot.name && secretCriteria; const endpointWarning = this.validateEndpoint(endpoint.endpoint); - const endpointPlaceholder = 'Your bot\'s endpoint (ex: http://localhost:3978/api/messages)'; + const endpointPlaceholder = + "Your bot's endpoint (ex: http://localhost:3978/api/messages)"; // TODO - localization return ( - -
+ +
+ onChange={this.onInputChange} + label={'Bot name'} + required={true} + /> - { endpointWarning && { endpointWarning } } - + placeholder={endpointPlaceholder} + label={'Endpoint URL'} + required={true} + value={this.state.endpoint.endpoint} + /> + {endpointWarning && ( + {endpointWarning} + )} + + value={endpoint.appId} + /> + value={endpoint.appPassword} + /> - + + checked={encryptKey} + onChange={this.onEncryptKeyChange} + />  Learn more. -
- +
@@ -208,38 +229,47 @@ export class BotCreationDialog extends React.Component<{}, BotCreationDialogStat // attach to bot this.setState({ bot: { ...this.state.bot, name: value } }); } else { - this.setState({ endpoint: { ...this.state.endpoint, ...{ [prop]: value } } } as any); + this.setState({ + endpoint: { ...this.state.endpoint, ...{ [prop]: value } }, + } as any); } - } + }; private onChannelServiceChange = (event: ChangeEvent) => { const { checked } = event.target; const channelService = checked ? 'https://botframework.azure.us' : ''; - this.setState({ endpoint: { ...this.state.endpoint, ...{ channelService: channelService } } } as any); - } + this.setState({ + endpoint: { + ...this.state.endpoint, + ...{ channelService }, + }, + } as any); + }; private onCancel = () => { DialogService.hideDialog(); - } + }; private onEncryptKeyChange = (event: ChangeEvent) => { const { checked } = event.target; const secret = checked ? generateBotSecret() : ''; this.setState({ encryptKey: checked, secret, revealSecret: true }); - } + }; private onRevealSecretClick = () => { if (!this.state.encryptKey) { return null; } this.setState({ revealSecret: !this.state.revealSecret }); - } + }; private onCopyClick = (): void => { if (!this.state.encryptKey) { return null; } - const input: HTMLInputElement = window.document.getElementById('key-input') as HTMLInputElement; + const input: HTMLInputElement = window.document.getElementById( + 'key-input' + ) as HTMLInputElement; input.removeAttribute('disabled'); const { type } = input; input.type = 'text'; @@ -247,7 +277,7 @@ export class BotCreationDialog extends React.Component<{}, BotCreationDialogStat window.document.execCommand('copy'); input.type = type; input.setAttribute('disabled', ''); - } + }; // TODO: Re-enable ability to re-generate secret after 4.1 // See 'https://github.com/Microsoft/BotFramework-Emulator/issues/964' for more information @@ -268,12 +298,14 @@ export class BotCreationDialog extends React.Component<{}, BotCreationDialogStat await this.performCreate(path); } else { // user cancelled out of the save dialog + // eslint-disable-next-line no-console console.log('Bot creation save dialog was cancelled.'); } } catch (e) { + // eslint-disable-next-line no-console console.error('Error while trying to select a bot file location: ', e); } - } + }; private performCreate = async (botPath: string) => { const endpoint: IEndpointService = { @@ -282,19 +314,21 @@ export class BotCreationDialog extends React.Component<{}, BotCreationDialogStat id: this.state.endpoint.id.trim(), appId: this.state.endpoint.appId.trim(), appPassword: this.state.endpoint.appPassword.trim(), - endpoint: this.state.endpoint.endpoint.trim() + endpoint: this.state.endpoint.endpoint.trim(), }; - (endpoint as any).channelService = (this.state.endpoint as any).channelService; + (endpoint as any).channelService = (this.state + .endpoint as any).channelService; const bot: BotConfigWithPath = BotConfigWithPathImpl.fromJSON({ ...this.state.bot, name: this.state.bot.name.trim(), description: this.state.bot.description.trim(), services: [endpoint], - path: botPath.trim() + path: botPath.trim(), }); - const secret = this.state.encryptKey && this.state.secret ? this.state.secret : null; + const secret = + this.state.encryptKey && this.state.secret ? this.state.secret : null; try { await ActiveBotHelper.confirmAndCreateBot(bot, secret); @@ -305,32 +339,40 @@ export class BotCreationDialog extends React.Component<{}, BotCreationDialogStat } finally { DialogService.hideDialog(); } - } + }; private showBotSaveDialog = async (): Promise => { const { Commands } = SharedConstants; // get a safe bot file name - const botFileName = await CommandServiceImpl.remoteCall(Commands.File.SanitizeString, this.state.bot.name); + const botFileName = await CommandServiceImpl.remoteCall( + Commands.File.SanitizeString, + this.state.bot.name + ); // TODO - Localization const dialogOptions = { filters: [ { name: 'Bot Files', - extensions: ['bot'] - } + extensions: ['bot'], + }, ], defaultPath: botFileName, showsTagField: false, title: 'Save as', - buttonLabel: 'Save' + buttonLabel: 'Save', }; - return CommandServiceImpl.remoteCall(Commands.Electron.ShowSaveDialog, dialogOptions); - } + return CommandServiceImpl.remoteCall( + Commands.Electron.ShowSaveDialog, + dialogOptions + ); + }; /** Checks the endpoint to see if it has the correct route syntax at the end (/api/messages) */ private validateEndpoint(endpoint: string): string { const controllerRegEx = /api\/messages\/?$/; - return controllerRegEx.test(endpoint) ? '' : `Please include route if necessary: "/api/messages"`; + return controllerRegEx.test(endpoint) + ? '' + : `Please include route if necessary: "/api/messages"`; } } diff --git a/packages/app/client/src/ui/dialogs/botSettingsEditor/botSettingsEditor.spec.tsx b/packages/app/client/src/ui/dialogs/botSettingsEditor/botSettingsEditor.spec.tsx index 6bb3246d2..8da279e2c 100644 --- a/packages/app/client/src/ui/dialogs/botSettingsEditor/botSettingsEditor.spec.tsx +++ b/packages/app/client/src/ui/dialogs/botSettingsEditor/botSettingsEditor.spec.tsx @@ -1,14 +1,49 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +import * as crypto from 'crypto'; + import * as React from 'react'; import { Provider } from 'react-redux'; import { mount } from 'enzyme'; import { combineReducers, createStore } from 'redux'; +import { BotConfigWithPathImpl } from '@bfemulator/sdk-shared'; +import { SharedConstants } from '@bfemulator/app-shared'; + import { bot } from '../../../data/reducer/bot'; +import { setActive } from '../../../data/action/botActions'; + import { BotSettingsEditor } from './botSettingsEditor'; import { BotSettingsEditorContainer } from './botSettingsEditorContainer'; -import { BotConfigWithPathImpl } from '@bfemulator/sdk-shared'; -import { setActive } from '../../../data/action/botActions'; -import { SharedConstants } from '@bfemulator/app-shared'; -import * as crypto from 'crypto'; const mockStore = createStore(combineReducers({ bot })); const mockBot = BotConfigWithPathImpl.fromJSON({}); @@ -21,38 +56,38 @@ const mockElement = { }, select: () => { // mock - } + }, }; const mockWindow = { crypto: { getRandomValues: (array: Uint8Array) => { array.set(crypto.randomBytes(32)); - } + }, }, btoa: (bytes: any) => Buffer.from(bytes).toString('base64'), document: { getElementById: () => mockElement, execCommand: () => { // mock - } - } + }, + }, }; jest.mock('./botSettingsEditor.scss', () => ({})); jest.mock('../../../data/store', () => ({ get store() { return mockStore; - } + }, })); jest.mock('../service', () => ({ DialogService: { showDialog: () => Promise.resolve(true), hideDialog: () => Promise.resolve(false), - } + }, })); -let mockRemoteCommandsCalled = []; +const mockRemoteCommandsCalled = []; const mockSharedConstants = SharedConstants; // thanks Jest! jest.mock('../../../platform/commands/commandServiceImpl', () => ({ CommandServiceImpl: { @@ -68,14 +103,14 @@ jest.mock('../../../platform/commands/commandServiceImpl', () => ({ default: return true; } - } - } + }, + }, })); jest.mock('../../../utils', () => ({ generateBotSecret: () => { return Math.random() + ''; - } + }, })); describe('The BotSettingsEditor dialog should', () => { @@ -84,9 +119,11 @@ describe('The BotSettingsEditor dialog should', () => { beforeEach(() => { mockStore.dispatch(setActive(mockBot)); mockRemoteCommandsCalled.length = 0; - parent = mount( - - ); + parent = mount( + + + + ); node = parent.find(BotSettingsEditor); }); @@ -111,16 +148,17 @@ describe('The BotSettingsEditor dialog should', () => { const instance = node.instance(); instance.setState({ encryptKey: true, - secret: 'MsKgJGZJw7Vqw51YwpZhw7LCk2MzwpZZwoLDkMKPIWfCq8K7wobDp8OvwqvCmsO+EAY=' + secret: + 'MsKgJGZJw7Vqw51YwpZhw7LCk2MzwpZZwoLDkMKPIWfCq8K7wobDp8OvwqvCmsO+EAY=', }); const elementSpies = { select: jest.spyOn(mockElement, 'select'), setAttribute: jest.spyOn(mockElement, 'setAttribute'), - removeAttribute: jest.spyOn(mockElement, 'removeAttribute') + removeAttribute: jest.spyOn(mockElement, 'removeAttribute'), }; const documentSpies = { execCommand: jest.spyOn(mockWindow.document, 'execCommand'), - getElementById: jest.spyOn(mockWindow.document, 'getElementById') + getElementById: jest.spyOn(mockWindow.document, 'getElementById'), }; instance.onCopyClick(); @@ -146,97 +184,106 @@ describe('The BotSettingsEditor dialog should', () => { const instance = node.instance(); instance.setState({ path: SharedConstants.TEMP_BOT_IN_MEMORY_PATH, - secret: 'MsKgJGZJw7Vqw51YwpZhw7LCk2MzwpZZwoLDkMKPIWfCq8K7wobDp8OvwqvCmsO+EAY=' + secret: + 'MsKgJGZJw7Vqw51YwpZhw7LCk2MzwpZZwoLDkMKPIWfCq8K7wobDp8OvwqvCmsO+EAY=', }); await instance.onSaveClick(); expect(mockRemoteCommandsCalled.length).toBe(7); [ { - 'commandName': 'file:sanitize-string', - 'args': [ - '' - ] + commandName: 'file:sanitize-string', + args: [''], }, { - 'commandName': 'shell:showExplorer-save-dialog', - 'args': [ + commandName: 'shell:showExplorer-save-dialog', + args: [ { - 'filters': [ + filters: [ { - 'name': 'Bot Files', - 'extensions': [ - 'bot' - ] - } + name: 'Bot Files', + extensions: ['bot'], + }, ], - 'defaultPath': '', - 'showsTagField': false, - 'title': 'Save as', - 'buttonLabel': 'Save' - } - ] + defaultPath: '', + showsTagField: false, + title: 'Save as', + buttonLabel: 'Save', + }, + ], }, { - 'args': ['TEMP_BOT_IN_MEMORY', { - 'displayName': '', - 'path': '/test/path', - 'secret': 'MsKgJGZJw7Vqw51YwpZhw7LCk2MzwpZZwoLDkMKPIWfCq8K7wobDp8OvwqvCmsO+EAY=' - }], 'commandName': 'bot:list:patch' + args: [ + 'TEMP_BOT_IN_MEMORY', + { + displayName: '', + path: '/test/path', + secret: + 'MsKgJGZJw7Vqw51YwpZhw7LCk2MzwpZZwoLDkMKPIWfCq8K7wobDp8OvwqvCmsO+EAY=', + }, + ], + commandName: 'bot:list:patch', }, { - 'commandName': 'bot:save', - 'args': [{ - 'description': '', - 'name': '', - 'overrides': null, - 'path': '/test/path', - 'padlock': '', - 'services': [], - 'version': '2.0' - }] + commandName: 'bot:save', + args: [ + { + description: '', + name: '', + overrides: null, + path: '/test/path', + padlock: '', + services: [], + version: '2.0', + }, + ], }, { - 'args': [{ - 'description': '', - 'name': '', - 'overrides': null, - 'path': '/test/path', - 'padlock': '', - 'services': [], - 'version': '2.0' - }], 'commandName': 'bot:set-active' + args: [ + { + description: '', + name: '', + overrides: null, + path: '/test/path', + padlock: '', + services: [], + version: '2.0', + }, + ], + commandName: 'bot:set-active', }, { - 'commandName': 'menu:update-file-menu', - 'args': [] + commandName: 'menu:update-file-menu', + args: [], }, { - 'commandName': 'electron:set-title-bar', - 'args': [ - '/test/path' - ] - } - ].forEach((command, index) => expect(mockRemoteCommandsCalled[index]).toEqual(command)); + commandName: 'electron:set-title-bar', + args: ['/test/path'], + }, + ].forEach((command, index) => + expect(mockRemoteCommandsCalled[index]).toEqual(command) + ); }); it('should make the expected calls when saving a bot', async () => { const instance = node.instance(); instance.setState({ path: 'a/test/path', - secret: 'MsKgJGZJw7Vqw51YwpZhw7LCk2MzwpZZwoLDkMKPIWfCq8K7wobDp8OvwqvCmsO+EAY=' + secret: + 'MsKgJGZJw7Vqw51YwpZhw7LCk2MzwpZZwoLDkMKPIWfCq8K7wobDp8OvwqvCmsO+EAY=', }); await instance.onSaveClick(); expect(mockRemoteCommandsCalled.length).toBe(3); [ { - 'commandName': 'bot:list:patch', - 'args': [ + commandName: 'bot:list:patch', + args: [ 'a/test/path', { - 'secret': 'MsKgJGZJw7Vqw51YwpZhw7LCk2MzwpZZwoLDkMKPIWfCq8K7wobDp8OvwqvCmsO+EAY=' - } - ] - } + secret: + 'MsKgJGZJw7Vqw51YwpZhw7LCk2MzwpZZwoLDkMKPIWfCq8K7wobDp8OvwqvCmsO+EAY=', + }, + ], + }, ].forEach((command, index) => { expect(mockRemoteCommandsCalled[index]).toEqual(command); }); diff --git a/packages/app/client/src/ui/dialogs/botSettingsEditor/botSettingsEditor.tsx b/packages/app/client/src/ui/dialogs/botSettingsEditor/botSettingsEditor.tsx index 00bab66c8..45ba5f302 100644 --- a/packages/app/client/src/ui/dialogs/botSettingsEditor/botSettingsEditor.tsx +++ b/packages/app/client/src/ui/dialogs/botSettingsEditor/botSettingsEditor.tsx @@ -31,26 +31,40 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { BotInfo, newNotification, Notification, NotificationType, SharedConstants } from '@bfemulator/app-shared'; -import { BotConfigWithPath, BotConfigWithPathImpl } from '@bfemulator/sdk-shared'; +import { + BotInfo, + newNotification, + Notification, + NotificationType, + SharedConstants, +} from '@bfemulator/app-shared'; +import { + BotConfigWithPath, + BotConfigWithPathImpl, +} from '@bfemulator/sdk-shared'; import { Checkbox, DefaultButton, Dialog, DialogFooter, PrimaryButton, - TextField, Row, - RowAlignment + RowAlignment, + TextField, } from '@bfemulator/ui-react'; -import { IConnectedService, ServiceTypes } from 'botframework-config/lib/schema'; +import { + IConnectedService, + ServiceTypes, +} from 'botframework-config/lib/schema'; import { ChangeEvent } from 'react'; import * as React from 'react'; + import { getBotInfoByPath } from '../../../data/botHelpers'; import { CommandServiceImpl } from '../../../platform/commands/commandServiceImpl'; +import { generateBotSecret } from '../../../utils'; import { ActiveBotHelper } from '../../helpers/activeBotHelper'; + import * as styles from './botSettingsEditor.scss'; -import { generateBotSecret } from '../../../utils'; export interface BotSettingsEditorProps { bot: BotConfigWithPath; @@ -67,7 +81,10 @@ export interface BotSettingsEditorState extends BotConfigWithPath { encryptKey?: boolean; } -export class BotSettingsEditor extends React.Component { +export class BotSettingsEditor extends React.Component< + BotSettingsEditorProps, + BotSettingsEditorState +> { private _generatedSecret: string; constructor(props: BotSettingsEditorProps, context: BotSettingsEditorState) { @@ -75,16 +92,16 @@ export class BotSettingsEditor extends React.Component + + label="Name" + value={name} + required={true} + onChange={this.onInputChange} + errorMessage={error} + /> - + + checked={encryptKey} + onChange={this.onEncryptKeyChange} + /> + onClick={this.onLearnMoreEncryptionClick} + >  Learn more. - ); @@ -168,38 +201,49 @@ export class BotSettingsEditor extends React.Component { this.props.cancel(); - } + }; private onInputChange = (event: ChangeEvent) => { const { value: name } = event.target; this.setState({ name, dirty: true }); - } + }; private onEncryptKeyChange = (event: ChangeEvent) => { const { checked } = event.target; this.setState({ encryptKey: checked, - secret: (checked ? this.generatedSecret : ''), + secret: checked ? this.generatedSecret : '', dirty: true, - revealSecret: (checked ? checked : false) + revealSecret: checked ? checked : false, }); - } + }; private onLearnMoreEncryptionClick = (): void => { - this.props.onAnchorClick('https://aka.ms/bot-framework-bot-file-encryption'); - } + this.props.onAnchorClick( + 'https://aka.ms/bot-framework-bot-file-encryption' + ); + }; private onSaveClick = async () => { - const { name: botName = '', description = '', path, services, padlock = '', secret } = this.state; - let bot: BotConfigWithPath = BotConfigWithPathImpl.fromJSON({ + const { + name: botName = '', + description = '', + path, + services, + padlock = '', + secret, + } = this.state; + const bot: BotConfigWithPath = BotConfigWithPathImpl.fromJSON({ name: botName.trim(), description: description.trim(), padlock: secret ? padlock : '', path: path.trim(), - services + services, }); - const endpointService: IConnectedService = bot.services.find(service => service.type === ServiceTypes.Endpoint); + const endpointService: IConnectedService = bot.services.find( + service => service.type === ServiceTypes.Endpoint + ); if (bot.path === SharedConstants.TEMP_BOT_IN_MEMORY_PATH) { // we are currently using a mocked bot for livechat opened via protocol URI @@ -208,14 +252,17 @@ export class BotSettingsEditor extends React.Component => { + private saveBotFromProtocol = async ( + bot: BotConfigWithPath, + endpointService: IConnectedService, + connectArg: boolean + ): Promise => { const { Save, PatchBotList } = SharedConstants.Commands.Bot; // need to establish a location for the .bot file - let newPath = await this.showBotSaveDialog(); + const newPath = await this.showBotSaveDialog(); if (!newPath) { return null; } @@ -224,19 +271,26 @@ export class BotSettingsEditor extends React.Component => { @@ -250,49 +304,59 @@ export class BotSettingsEditor extends React.Component => { // get a safe bot file name // TODO - localization const { SanitizeString } = SharedConstants.Commands.File; - const botFileName = await CommandServiceImpl.remoteCall(SanitizeString, this.state.name); + const botFileName = await CommandServiceImpl.remoteCall( + SanitizeString, + this.state.name + ); const dialogOptions = { filters: [ { name: 'Bot Files', - extensions: ['bot'] - } + extensions: ['bot'], + }, ], defaultPath: botFileName, showsTagField: false, title: 'Save as', - buttonLabel: 'Save' + buttonLabel: 'Save', }; - return CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.ShowSaveDialog, dialogOptions); - } + return CommandServiceImpl.remoteCall( + SharedConstants.Commands.Electron.ShowSaveDialog, + dialogOptions + ); + }; private onRevealSecretClick = (): void => { if (!this.state.encryptKey) { return null; } this.setState({ revealSecret: !this.state.revealSecret }); - } + }; private onCopyClick = (): void => { if (!this.state.encryptKey) { return null; } const { window } = this.props; - const input: HTMLInputElement = window.document.getElementById('key-input') as HTMLInputElement; + const input: HTMLInputElement = window.document.getElementById( + 'key-input' + ) as HTMLInputElement; input.removeAttribute('disabled'); const { type } = input; input.type = 'text'; @@ -300,7 +364,7 @@ export class BotSettingsEditor extends React.Component => { +const mapStateToProps = ( + state: RootState, + ownProps: {} +): Partial => { return { window, bot: BotConfigWithPathImpl.fromJSON(state.bot.activeBot), // Copy only - ...ownProps + ...ownProps, }; }; @@ -19,8 +56,14 @@ const mapDispatchToProps = dispatch => ({ cancel: () => DialogService.hideDialog(0), sendNotification: notification => dispatch(beginAdd(notification)), onAnchorClick: (url: string) => { - CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.OpenExternal, url).catch(); - } + CommandServiceImpl.remoteCall( + SharedConstants.Commands.Electron.OpenExternal, + url + ).catch(); + }, }); -export const BotSettingsEditorContainer = connect(mapStateToProps, mapDispatchToProps)(BotSettingsEditor); +export const BotSettingsEditorContainer = connect( + mapStateToProps, + mapDispatchToProps +)(BotSettingsEditor); diff --git a/packages/app/client/src/ui/dialogs/connectServicePromptDialog/connectServicePromptDialog.spec.tsx b/packages/app/client/src/ui/dialogs/connectServicePromptDialog/connectServicePromptDialog.spec.tsx index bc7308425..52b02433f 100644 --- a/packages/app/client/src/ui/dialogs/connectServicePromptDialog/connectServicePromptDialog.spec.tsx +++ b/packages/app/client/src/ui/dialogs/connectServicePromptDialog/connectServicePromptDialog.spec.tsx @@ -1,18 +1,52 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import * as React from 'react'; import { Provider } from 'react-redux'; import { mount } from 'enzyme'; import { createStore } from 'redux'; +import { ServiceTypes } from 'botframework-config'; + import { azureAuth } from '../../../data/reducer/azureAuthReducer'; +import { DialogService } from '../service'; + import { ConnectServicePromptDialog } from './connectServicePromptDialog'; import { ConnectServicePromptDialogContainer } from './connectServicePromptDialogContainer'; -import { DialogService } from '../service'; -import { ServiceTypes } from 'botframework-config'; jest.mock('../service', () => ({ DialogService: { showDialog: () => Promise.resolve(true), hideDialog: () => Promise.resolve(false), - } + }, })); jest.mock('../dialogStyles.scss', () => ({})); @@ -22,7 +56,7 @@ jest.mock('../../dialogs/', () => ({ AzureLoginSuccessDialogContainer: () => undefined, BotCreationDialog: () => undefined, DialogService: { showDialog: () => Promise.resolve(true) }, - SecretPromptDialog: () => undefined + SecretPromptDialog: () => undefined, })); describe('The ConnectServicePromptDialog component should', () => { @@ -30,9 +64,11 @@ describe('The ConnectServicePromptDialog component should', () => { let node; beforeEach(() => { - parent = mount( - - ); + parent = mount( + + + + ); node = parent.find(ConnectServicePromptDialog); }); diff --git a/packages/app/client/src/ui/dialogs/connectServicePromptDialog/connectServicePromptDialog.tsx b/packages/app/client/src/ui/dialogs/connectServicePromptDialog/connectServicePromptDialog.tsx index 831f4f5da..58a1e2e20 100644 --- a/packages/app/client/src/ui/dialogs/connectServicePromptDialog/connectServicePromptDialog.tsx +++ b/packages/app/client/src/ui/dialogs/connectServicePromptDialog/connectServicePromptDialog.tsx @@ -1,7 +1,45 @@ -import { DefaultButton, Dialog, DialogFooter, PrimaryButton } from '@bfemulator/ui-react'; +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +import { + DefaultButton, + Dialog, + DialogFooter, + PrimaryButton, +} from '@bfemulator/ui-react'; import { ServiceTypes } from 'botframework-config/lib/schema'; import * as React from 'react'; import { Component, ReactNode } from 'react'; + import * as styles from '../dialogStyles.scss'; export interface ConnectServicePromptDialogProps { @@ -15,23 +53,30 @@ const titleMap = { [ServiceTypes.Luis]: 'Connect your bot to a LUIS application', [ServiceTypes.Dispatch]: 'Connect your bot to a Dispatch model', [ServiceTypes.QnA]: 'Connect your bot to a QnA Maker knowledge base', - [ServiceTypes.AppInsights]: 'Connect to an Azure Application Insights resource', + [ServiceTypes.AppInsights]: + 'Connect to an Azure Application Insights resource', [ServiceTypes.BlobStorage]: 'Connect your bot to an Azure Storage account', - [ServiceTypes.CosmosDB]: 'Connect your bot to an Azure Cosmos DB account' + [ServiceTypes.CosmosDB]: 'Connect your bot to an Azure Cosmos DB account', }; -export class ConnectServicePromptDialog extends Component { - +export class ConnectServicePromptDialog extends Component< + ConnectServicePromptDialogProps, + {} +> { public render() { return ( - { this.dialogContent } + className={styles.dialogMedium} + cancel={this.props.cancel} + title={titleMap[this.props.serviceType]} + > + {this.dialogContent} - - + + ); @@ -67,14 +112,15 @@ export class ConnectServicePromptDialog extends Component

- { `Sign in to your Azure account to select the LUIS applications you'd like to associate with this bot. ` } - - + {`Sign in to your Azure account to select the LUIS applications you'd like to associate with this bot. `} +

- { `Alternatively, you can ` } - add a LUIS app manually - { ` with the app ID, version, and authoring key.` } + {`Alternatively, you can `} + + add a LUIS app manually + + {` with the app ID, version, and authoring key.`}

); @@ -84,16 +130,16 @@ export class ConnectServicePromptDialog extends Component

- { 'Sign in to your Azure account to select the QnA ' + - 'Maker knowledge bases you\'d like to associate with this bot. ' } - - + {'Sign in to your Azure account to select the QnA ' + + "Maker knowledge bases you'd like to associate with this bot. "} +

- { `Alternatively, you can ` } - connect to a QnA Maker knowledge base manually - - { ' with the app ID, version, and authoring key.' } + {`Alternatively, you can `}{' '} + + connect to a QnA Maker knowledge base manually + + {' with the app ID, version, and authoring key.'}

); @@ -103,16 +149,17 @@ export class ConnectServicePromptDialog extends Component

- { `Sign in to your Azure account to select the Dispatch model you'd like to associate with this bot. ` } - Learn more about Dispatch models. + {`Sign in to your Azure account to select the Dispatch model you'd like to associate with this bot. `} + + Learn more about Dispatch models.

- { `Alternatively, you can ` } - + {`Alternatively, you can `} + connect to a Dispatch model manually - { ` with the app ID, version, and authoring key.` } + {` with the app ID, version, and authoring key.`}

); @@ -122,18 +169,18 @@ export class ConnectServicePromptDialog extends Component

- { 'Sign in to your Azure account to select the Azure Application ' + - 'Insights you\'d like to associate with this bot. ' } + {'Sign in to your Azure account to select the Azure Application ' + + "Insights you'd like to associate with this bot. "} Learn more about Azure Application Insights.

- { `Alternatively, you can ` } - + {`Alternatively, you can `} + connect to a Azure Application Insights manually - { ` with the app ID, version, and authoring key.` } + {` with the app ID, version, and authoring key.`}

); @@ -143,15 +190,15 @@ export class ConnectServicePromptDialog extends Component

- { 'Sign in to your Azure account to select the Azure Storage ' + - 'accounts you\'d like to associate with this bot. ' } + {'Sign in to your Azure account to select the Azure Storage ' + + "accounts you'd like to associate with this bot. "} Learn more about Azure Storage.

- { `Alternatively, you can ` } - + {`Alternatively, you can `} + connect to a Azure Storage account manually.

@@ -163,15 +210,15 @@ export class ConnectServicePromptDialog extends Component

- { 'Sign in to your Azure account to select the Azure Cosmos DB ' + - 'accounts you\'d like to associate with this bot. ' } + {'Sign in to your Azure account to select the Azure Cosmos DB ' + + "accounts you'd like to associate with this bot. "} Learn more about Azure Cosmos DB.

- { `Alternatively, you can ` } - + {`Alternatively, you can `} + connect to a Azure Cosmos DB account manually.

diff --git a/packages/app/client/src/ui/dialogs/connectServicePromptDialog/connectServicePromptDialogContainer.ts b/packages/app/client/src/ui/dialogs/connectServicePromptDialog/connectServicePromptDialogContainer.ts index 116a356c8..3c5ce4b93 100644 --- a/packages/app/client/src/ui/dialogs/connectServicePromptDialog/connectServicePromptDialogContainer.ts +++ b/packages/app/client/src/ui/dialogs/connectServicePromptDialog/connectServicePromptDialogContainer.ts @@ -32,17 +32,23 @@ // import { connect } from 'react-redux'; + import { DialogService } from '../service'; -import { ConnectServicePromptDialog, ConnectServicePromptDialogProps } from './connectServicePromptDialog'; + +import { + ConnectServicePromptDialog, + ConnectServicePromptDialogProps, +} from './connectServicePromptDialog'; const mapDispatchToProps = ( - _dispatch: () => void, ownProps: { [propName: string]: any } + _dispatch: () => void, + ownProps: { [propName: string]: any } ): ConnectServicePromptDialogProps => { return { ...ownProps, cancel: () => DialogService.hideDialog(0), confirm: () => DialogService.hideDialog(1), - addServiceManually: () => DialogService.hideDialog(2) + addServiceManually: () => DialogService.hideDialog(2), }; }; diff --git a/packages/app/client/src/ui/dialogs/getStartedWithCSDialog/getStartedWithCSDialog.spec.tsx b/packages/app/client/src/ui/dialogs/getStartedWithCSDialog/getStartedWithCSDialog.spec.tsx index 27fdb96ce..502f0f5d8 100644 --- a/packages/app/client/src/ui/dialogs/getStartedWithCSDialog/getStartedWithCSDialog.spec.tsx +++ b/packages/app/client/src/ui/dialogs/getStartedWithCSDialog/getStartedWithCSDialog.spec.tsx @@ -1,11 +1,45 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { ServiceTypes } from 'botframework-config/lib/schema'; import { mount } from 'enzyme'; import * as React from 'react'; import { Provider } from 'react-redux'; import { combineReducers, createStore } from 'redux'; + import { azureArmTokenDataChanged } from '../../../data/action/azureAuthActions'; import { azureAuth } from '../../../data/reducer/azureAuthReducer'; import { DialogService } from '../service'; + import { GetStartedWithCSDialog } from './getStartedWithCSDialog'; import { GetStartedWithCSDialogContainer } from './getStartedWithCSDialogContainer'; @@ -13,7 +47,7 @@ jest.mock('../service', () => ({ DialogService: { showDialog: () => Promise.resolve(true), hideDialog: () => Promise.resolve(false), - } + }, })); jest.mock('../dialogStyles.scss', () => ({})); @@ -23,12 +57,13 @@ jest.mock('../../dialogs/', () => ({ AzureLoginSuccessDialogContainer: () => undefined, BotCreationDialog: () => undefined, DialogService: { showDialog: () => Promise.resolve(true) }, - SecretPromptDialog: () => undefined + SecretPromptDialog: () => undefined, })); describe('The GetStartedWithCSDialog component should', () => { let mockStore; - const mockArmToken = 'bm90aGluZw==.eyJ1cG4iOiJnbGFzZ293QHNjb3RsYW5kLmNvbSJ9.7gjdshgfdsk98458205jfds9843fjds'; + const mockArmToken = + 'bm90aGluZw==.eyJ1cG4iOiJnbGFzZ293QHNjb3RsYW5kLmNvbSJ9.7gjdshgfdsk98458205jfds9843fjds'; beforeEach(() => { mockStore = createStore(combineReducers({ azureAuth })); mockStore.dispatch(azureArmTokenDataChanged(mockArmToken)); @@ -36,8 +71,8 @@ describe('The GetStartedWithCSDialog component should', () => { it('should render deeply', () => { const parent = mount( - - + + ); expect(parent.find(GetStartedWithCSDialogContainer)).not.toEqual(null); @@ -45,48 +80,61 @@ describe('The GetStartedWithCSDialog component should', () => { it('should contain both a cancel, confirm and launchConnectedServiceEditor function in the props', () => { const parent = mount( - - + + ); const prompt = parent.find(GetStartedWithCSDialog); expect(typeof (prompt.props() as any).cancel).toEqual('function'); expect(typeof (prompt.props() as any).confirm).toEqual('function'); - expect(typeof (prompt.props() as any).launchConnectedServiceEditor).toEqual('function'); - }); - - it('should call DialogService.hideDialog with the appropriate values when cancel, ' + - 'confirm and launchConnectedServiceEditor are called', () => { - const parent = mount( - - - + expect(typeof (prompt.props() as any).launchConnectedServiceEditor).toEqual( + 'function' ); - const spy = jest.spyOn(DialogService, 'hideDialog'); - const prompt = parent.find(GetStartedWithCSDialog); - (prompt.props() as any).cancel(); - expect(spy).toHaveBeenLastCalledWith(0); - (prompt.props() as any).confirm(); - expect(spy).toHaveBeenLastCalledWith(1); - (prompt.props() as any).launchConnectedServiceEditor(); - expect(spy).toHaveBeenLastCalledWith(2); }); - it('should display luisNoModelsFoundContent when the ServiceTypes.Luis and ' + - 'showNoModelsFoundContent is provided in the props', () => { - const parent: any = mount( - - - - ); - const prompt = parent.find(GetStartedWithCSDialog); - expect(prompt.instance().content).toEqual(prompt.instance().luisNoModelsFoundContent); - }); + it( + 'should call DialogService.hideDialog with the appropriate values when cancel, ' + + 'confirm and launchConnectedServiceEditor are called', + () => { + const parent = mount( + + + + ); + const spy = jest.spyOn(DialogService, 'hideDialog'); + const prompt = parent.find(GetStartedWithCSDialog); + (prompt.props() as any).cancel(); + expect(spy).toHaveBeenLastCalledWith(0); + (prompt.props() as any).confirm(); + expect(spy).toHaveBeenLastCalledWith(1); + (prompt.props() as any).launchConnectedServiceEditor(); + expect(spy).toHaveBeenLastCalledWith(2); + } + ); + + it( + 'should display luisNoModelsFoundContent when the ServiceTypes.Luis and ' + + 'showNoModelsFoundContent is provided in the props', + () => { + const parent: any = mount( + + + + ); + const prompt = parent.find(GetStartedWithCSDialog); + expect(prompt.instance().content).toEqual( + prompt.instance().luisNoModelsFoundContent + ); + } + ); it('should display luisContent when the ServiceTypes.Luis is provided in the props', () => { const parent: any = mount( - - + + ); const prompt = parent.find(GetStartedWithCSDialog); @@ -94,50 +142,76 @@ describe('The GetStartedWithCSDialog component should', () => { }); it('should display dispatchContent when the ServiceTypes.Dispatch is provided in the props', () => { - const parent: any = mount( - - ); + const parent: any = mount( + + + + ); const prompt = parent.find(GetStartedWithCSDialog); - expect(prompt.instance().content).toEqual(prompt.instance().dispatchContent); + expect(prompt.instance().content).toEqual( + prompt.instance().dispatchContent + ); }); - it('should display dispatchNoModelsFoundContent when the ServiceTypes.Dispatch and ' + - 'showNoModelsFoundContent is provided in the props', () => { - const parent: any = mount( - - ); - const prompt = parent.find(GetStartedWithCSDialog); - expect(prompt.instance().content).toEqual(prompt.instance().dispatchNoModelsFoundContent); - }); + it( + 'should display dispatchNoModelsFoundContent when the ServiceTypes.Dispatch and ' + + 'showNoModelsFoundContent is provided in the props', + () => { + const parent: any = mount( + + + + ); + const prompt = parent.find(GetStartedWithCSDialog); + expect(prompt.instance().content).toEqual( + prompt.instance().dispatchNoModelsFoundContent + ); + } + ); it('should display qnaContent when the ServiceTypes.QnA is provided in the props', () => { - const parent: any = mount( - - ); + const parent: any = mount( + + + + ); const prompt = parent.find(GetStartedWithCSDialog); expect(prompt.instance().content).toEqual(prompt.instance().qnaContent); }); it('should display blobContent when the ServiceTypes.BlobStorage is provided in the props', () => { - const parent: any = mount( - - ); + const parent: any = mount( + + + + ); const prompt = parent.find(GetStartedWithCSDialog); expect(prompt.instance().content).toEqual(prompt.instance().blobContent); }); it('should display cosmosContent when the ServiceTypes.CosmosDB is provided in the props', () => { - const parent: any = mount( - - ); + const parent: any = mount( + + + + ); const prompt = parent.find(GetStartedWithCSDialog); - expect(prompt.instance().content).toEqual(prompt.instance().cosmosDbContent); + expect(prompt.instance().content).toEqual( + prompt.instance().cosmosDbContent + ); }); it('should display no when no service type provided in the props', () => { - const parent: any = mount( - - ); + const parent: any = mount( + + + + ); const prompt = parent.find(GetStartedWithCSDialog); expect(prompt.instance().content).toBeNull(); }); diff --git a/packages/app/client/src/ui/dialogs/getStartedWithCSDialog/getStartedWithCSDialog.tsx b/packages/app/client/src/ui/dialogs/getStartedWithCSDialog/getStartedWithCSDialog.tsx index 23cc511f1..4b9ec9505 100644 --- a/packages/app/client/src/ui/dialogs/getStartedWithCSDialog/getStartedWithCSDialog.tsx +++ b/packages/app/client/src/ui/dialogs/getStartedWithCSDialog/getStartedWithCSDialog.tsx @@ -1,7 +1,45 @@ -import { DefaultButton, Dialog, DialogFooter, PrimaryButton } from '@bfemulator/ui-react'; +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +import { + DefaultButton, + Dialog, + DialogFooter, + PrimaryButton, +} from '@bfemulator/ui-react'; import { ServiceTypes } from 'botframework-config/lib/schema'; import * as React from 'react'; import { Component, ReactNode } from 'react'; + import { serviceTypeLabels } from '../../../utils/serviceTypeLables'; import * as styles from '../dialogStyles.scss'; @@ -18,28 +56,34 @@ const titleMap = { [ServiceTypes.Luis]: 'Create a LUIS app', [ServiceTypes.Dispatch]: 'Connect to a Dispatch model', [ServiceTypes.QnA]: 'Create a QnA Maker knowledge base', - [ServiceTypes.BlobStorage]: 'Create a Blob Storage Container' + [ServiceTypes.BlobStorage]: 'Create a Blob Storage Container', }; const buttonTextMap = { [ServiceTypes.Luis]: 'LUIS', [ServiceTypes.Dispatch]: 'LUIS', [ServiceTypes.QnA]: 'QnA Maker', - [ServiceTypes.BlobStorage]: 'Blob Storage' + [ServiceTypes.BlobStorage]: 'Blob Storage', }; -export class GetStartedWithCSDialog extends Component { +export class GetStartedWithCSDialog extends Component< + GetStartedWithCSDialogProps, + {} +> { public render() { return ( - { this.content } + className={styles.dialogMedium} + cancel={this.props.cancel} + title={titleMap[this.props.serviceType]} + > + {this.content} - - + + ); @@ -76,21 +120,26 @@ export class GetStartedWithCSDialog extends Component

- { 'Language Understanding Service (LUIS) is a matching learning-based service for adding language ' + - 'understanding to bots, applications and IoT devices.' } + {'Language Understanding Service (LUIS) is a matching learning-based service for adding language ' + + 'understanding to bots, applications and IoT devices.'}

- { `You have not signed up for a LUIS account under ${ this.props.authenticatedUser } ` } + {`You have not signed up for a LUIS account under ${ + this.props.authenticatedUser + } `} Learn more about LUIS

- { 'Alternatively, you can ' } - + {'Alternatively, you can '} + connect to a LUIS app manually - { ' if you know the app ID, version, and authoring key.' } + {' if you know the app ID, version, and authoring key.'}

); @@ -100,19 +149,21 @@ export class GetStartedWithCSDialog extends Component +

Signed in as {this.props.authenticatedUser}.

- Signed in as { this.props.authenticatedUser }. -

-

- { 'You do not have any {label} models associated with this account. ' } - Connect to a { label } model manually by entering the app ID and key. + {'You do not have any {label} models associated with this account. '} + + Connect to a {label} model manually + {' '} + by entering the app ID and key.

- Learn more about { label } models
+ Learn more about {label} models +

- { 'You can link apps from a different {label} account to this Azure account by adding ' + - 'yourself as a collaborator.' } + {'You can link apps from a different {label} account to this Azure account by adding ' + + 'yourself as a collaborator.'} Learn more about collaborating @@ -129,24 +180,29 @@ export class GetStartedWithCSDialog extends Component

- { 'A Dispatch model is a LUIS model that enables your bot to dispatch intents across multiple LUIS ' + - 'apps and QnAMaker knowledge bases. ' } + {'A Dispatch model is a LUIS model that enables your bot to dispatch intents across multiple LUIS ' + + 'apps and QnAMaker knowledge bases. '} Learn more about Dispatch models

- { `You have not signed up for a LUIS account under ${ this.props.authenticatedUser } ` } + {`You have not signed up for a LUIS account under ${ + this.props.authenticatedUser + } `} Learn more about LUIS

-
- { 'Alternatively, you can ' } - +
+ {'Alternatively, you can '} +
connect to a Dispatch app manually - { ' if you know the app ID, version, and authoring key.' } + {' if you know the app ID, version, and authoring key.'} ); } @@ -154,22 +210,25 @@ export class GetStartedWithCSDialog extends Component +

Signed in as {this.props.authenticatedUser}.

- Signed in as { this.props.authenticatedUser }. -

-

- { 'You do not have any Dispatch models associated with this account. ' } - + {'You do not have any Dispatch models associated with this account. '} + Connect to a Dispatch model manually - { ' by entering this app ID and key.' } + {' by entering this app ID and key.'}

- Learn more about Dispatch models + + Learn more about Dispatch models +

- { 'You can link apps from a different Dispatch account to this Azure account by adding ' + - 'yourself as a collaborator. ' } + {'You can link apps from a different Dispatch account to this Azure account by adding ' + + 'yourself as a collaborator. '} Learn more about collaborating @@ -182,19 +241,27 @@ export class GetStartedWithCSDialog extends Component

- { 'QnA Maker is a service that creates a question-and-answer knowledge base from FAQs and product manuals.' } + { + 'QnA Maker is a service that creates a question-and-answer knowledge base from FAQs and product manuals.' + }

- { `You have not signed up for a QnA Maker account under ${ this.props.authenticatedUser }. ` } - Get started with QnA Maker + {`You have not signed up for a QnA Maker account under ${ + this.props.authenticatedUser + }. `} + + Get started with QnA Maker +

- { ' Alternatively, you can ' } - + {' Alternatively, you can '} + connect to a knowledge base manually - { ' if you know the ID and subscription key.' } + {' if you know the ID and subscription key.'}

); @@ -204,19 +271,28 @@ export class GetStartedWithCSDialog extends Component

- { 'Blob Storage is a service that allows you to store unstructured ' + - 'data and is commonly used to store a Bot\'s transcripts.' } + {'Blob Storage is a service that allows you to store unstructured ' + + "data and is commonly used to store a Bot's transcripts."}

- { `You have do not have a Blob container under ${ this.props.authenticatedUser }. ` } - Get started with Blob Storage + {`You have do not have a Blob container under ${ + this.props.authenticatedUser + }. `} + + Get started with Blob Storage +

- { ' Alternatively, you can ' } - + {' Alternatively, you can '} + connect to a Blob container manually - { ' if you know the ID, subscription key, container name and connection string.' } + { + ' if you know the ID, subscription key, container name and connection string.' + }

); @@ -226,18 +302,29 @@ export class GetStartedWithCSDialog extends Component

- { 'CosmosDB is a multi-model database service commonly used to store a bot\'s state.' } + { + "CosmosDB is a multi-model database service commonly used to store a bot's state." + }

- { `You have do not have any CosmosDB collections under ${ this.props.authenticatedUser }. ` } - Get started with CosmosDB + {`You have do not have any CosmosDB collections under ${ + this.props.authenticatedUser + }. `} + + Get started with CosmosDB +

- { ' Alternatively, you can ' } - + {' Alternatively, you can '} + connect to a CosmosDB collection manually - { ' if you know the ID, subscription key, collection and database name.' } + { + ' if you know the ID, subscription key, collection and database name.' + }

); diff --git a/packages/app/client/src/ui/dialogs/getStartedWithCSDialog/getStartedWithCSDialogContainer.ts b/packages/app/client/src/ui/dialogs/getStartedWithCSDialog/getStartedWithCSDialogContainer.ts index ec5931b70..52083fb0c 100644 --- a/packages/app/client/src/ui/dialogs/getStartedWithCSDialog/getStartedWithCSDialogContainer.ts +++ b/packages/app/client/src/ui/dialogs/getStartedWithCSDialog/getStartedWithCSDialogContainer.ts @@ -32,16 +32,23 @@ // import { connect } from 'react-redux'; -import { DialogService } from '../service'; -import { GetStartedWithCSDialog, GetStartedWithCSDialogProps } from './getStartedWithCSDialog'; + import { RootState } from '../../../data/store'; +import { DialogService } from '../service'; + +import { + GetStartedWithCSDialog, + GetStartedWithCSDialogProps, +} from './getStartedWithCSDialog'; -const mapDispatchToProps = (_dispatch: () => void): GetStartedWithCSDialogProps => ({ +const mapDispatchToProps = ( + _dispatch: () => void +): GetStartedWithCSDialogProps => ({ cancel: () => DialogService.hideDialog(0), confirm: () => DialogService.hideDialog(1), launchConnectedServiceEditor: () => { DialogService.hideDialog(2); - } + }, }); const mapStateToProps = (state: RootState, ownProps) => { @@ -49,7 +56,10 @@ const mapStateToProps = (state: RootState, ownProps) => { const [, payload] = token.split('.'); const pJson = JSON.parse(atob(payload)); - return { ...ownProps, user: pJson.upn || pJson.unique_name || pJson.name || pJson.email }; + return { + ...ownProps, + user: pJson.upn || pJson.unique_name || pJson.name || pJson.email, + }; }; export const GetStartedWithCSDialogContainer = connect( diff --git a/packages/app/client/src/ui/dialogs/host/host.spec.tsx b/packages/app/client/src/ui/dialogs/host/host.spec.tsx index a4a018226..d9d5a1a9e 100644 --- a/packages/app/client/src/ui/dialogs/host/host.spec.tsx +++ b/packages/app/client/src/ui/dialogs/host/host.spec.tsx @@ -35,6 +35,7 @@ import * as React from 'react'; import { mount } from 'enzyme'; import { createStore } from 'redux'; import { Provider } from 'react-redux'; + import { DialogHostContainer } from './hostContainer'; import { DialogHost } from './host'; @@ -44,11 +45,11 @@ let mockSetHost; let mockHideDialog; jest.mock('../service', () => ({ get DialogService() { - return { - setHost: mockSetHost, - hideDialog: mockHideDialog + return { + setHost: mockSetHost, + hideDialog: mockHideDialog, }; - } + }, })); describe('', () => { @@ -62,8 +63,8 @@ describe('', () => { mockHideDialog = jest.fn(() => null); mockState = { dialog: { showing: true } }; wrapper = mount( - mockState, {}) }> - + mockState, {})}> + ); node = wrapper.find(DialogHost); @@ -79,14 +80,20 @@ describe('', () => { const mockRemoveEventLister = jest.fn(() => null); instance._hostRef = { addEventListener: mockAddEventListener, - removeEventListener: mockRemoveEventLister + removeEventListener: mockRemoveEventLister, }; instance.componentDidMount(); - expect(mockAddEventListener).toHaveBeenCalledWith('dialogRendered', instance.initFocusTrap); + expect(mockAddEventListener).toHaveBeenCalledWith( + 'dialogRendered', + instance.initFocusTrap + ); instance.componentWillUnmount(); - expect(mockRemoveEventLister).toHaveBeenCalledWith('dialogRendered', instance.initFocusTrap); + expect(mockRemoveEventLister).toHaveBeenCalledWith( + 'dialogRendered', + instance.initFocusTrap + ); }); it('should handle an overlay click', () => { @@ -113,7 +120,7 @@ describe('', () => { expect(mockSetHost).toHaveBeenCalledWith(mockElem); expect(instance._hostRef).toBe(mockElem); }); - + it('should get all focusable elements in the modal', () => { // create mock inner dialog //
@@ -141,7 +148,7 @@ describe('', () => { return [ { elem: 'elem1', focus: mockFocus }, // should be focused { elem: 'elem2' }, - { elem: 'elem3' } + { elem: 'elem3' }, ]; }); instance.getFocusableElementsInModal = mockGetFocusableElementsInModal; @@ -158,12 +165,16 @@ describe('', () => { const mockGetFocusableElementsInModal = jest.fn(() => { return [ { elem: 'elem1' }, - { elem: 'elem2', hasAttribute: () => false, focus: mockFocusEnabledElement }, // should be focused + { + elem: 'elem2', + hasAttribute: () => false, + focus: mockFocusEnabledElement, + }, // should be focused { elem: 'disabledElem', hasAttribute: () => true, // should be skipped because disabled - focus: mockFocusDisabledElement - } + focus: mockFocusDisabledElement, + }, ]; }); instance.getFocusableElementsInModal = mockGetFocusableElementsInModal; @@ -183,9 +194,13 @@ describe('', () => { { elem: 'disabledElem', hasAttribute: () => true, // should be skipped because disabled - focus: mockFocusDisabledElement + focus: mockFocusDisabledElement, }, - { elem: 'elem1', hasAttribute: () => false, focus: mockFocusEnabledElement }, // should be focused + { + elem: 'elem1', + hasAttribute: () => false, + focus: mockFocusEnabledElement, + }, // should be focused { elem: 'elem2' }, ]; }); diff --git a/packages/app/client/src/ui/dialogs/host/host.tsx b/packages/app/client/src/ui/dialogs/host/host.tsx index e04798caf..10d098138 100644 --- a/packages/app/client/src/ui/dialogs/host/host.tsx +++ b/packages/app/client/src/ui/dialogs/host/host.tsx @@ -31,11 +31,13 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import * as styles from './host.scss'; import * as React from 'react'; import { EventHandler, SyntheticEvent } from 'react'; + import { DialogService } from '../service'; +import * as styles from './host.scss'; + export interface DialogHostProps { saveHostRef?: (elem: HTMLElement) => void; showing?: boolean; @@ -62,19 +64,25 @@ export class DialogHost extends React.Component { const sentinelTabIndex = this.props.showing ? 0 : -1; return ( -
+
- -
-
+ tabIndex={sentinelTabIndex} + onFocus={this.onFocusStartingSentinel} + className={styles.focusSentinel} + /> +
- + tabIndex={sentinelTabIndex} + onFocus={this.onFocusEndingSentinel} + className={styles.focusSentinel} + />
); } @@ -82,24 +90,24 @@ export class DialogHost extends React.Component { private handleOverlayClick: EventHandler = (event: MouseEvent) => { event.stopPropagation(); DialogService.hideDialog(); - } + }; private handleContentClick: EventHandler = (event: MouseEvent) => { // need to stop clicks inside the dialog from bubbling up to the overlay event.stopPropagation(); - } + }; private saveHostRef = (elem: HTMLElement) => { DialogService.setHost(elem); this._hostRef = elem; - } + }; private getFocusableElementsInModal = (): NodeList => { if (this._hostRef) { return this._hostRef.querySelectorAll('[tabIndex]:not([tabIndex="-1"])'); } return new NodeList(); - } + }; private initFocusTrap = () => { const allFocusableElements = this.getFocusableElementsInModal(); @@ -107,7 +115,7 @@ export class DialogHost extends React.Component { const firstChild: HTMLElement = allFocusableElements[0] as HTMLElement; firstChild.focus(); } - } + }; // Reached beginning of focusable items inside the modal host; re-focus the last item private onFocusStartingSentinel = (e: SyntheticEvent) => { @@ -115,7 +123,9 @@ export class DialogHost extends React.Component { const allFocusableElements = this.getFocusableElementsInModal(); if (allFocusableElements.length) { - let lastChild: HTMLElement = allFocusableElements[allFocusableElements.length - 1] as HTMLElement; + let lastChild: HTMLElement = allFocusableElements[ + allFocusableElements.length - 1 + ] as HTMLElement; if (lastChild.hasAttribute('disabled')) { // focus the last element in the list that isn't disabled @@ -130,7 +140,7 @@ export class DialogHost extends React.Component { lastChild.focus(); } } - } + }; // Reached end of focusable items inside the modal host; re-focus the first item private onFocusEndingSentinel = (e: SyntheticEvent) => { @@ -153,5 +163,5 @@ export class DialogHost extends React.Component { firstChild.focus(); } } - } + }; } diff --git a/packages/app/client/src/ui/dialogs/host/hostContainer.ts b/packages/app/client/src/ui/dialogs/host/hostContainer.ts index 87225c871..24c024baf 100644 --- a/packages/app/client/src/ui/dialogs/host/hostContainer.ts +++ b/packages/app/client/src/ui/dialogs/host/hostContainer.ts @@ -32,11 +32,13 @@ // import { connect } from 'react-redux'; -import { DialogHost, DialogHostProps } from './host'; + import { RootState } from '../../../data/store'; +import { DialogHost, DialogHostProps } from './host'; + function mapStateToProps(state: RootState): DialogHostProps { - return ({ showing: state.dialog.showing }); + return { showing: state.dialog.showing }; } export const DialogHostContainer = connect(mapStateToProps)(DialogHost); diff --git a/packages/app/client/src/ui/dialogs/openBotDialog/openBotDialog.spec.tsx b/packages/app/client/src/ui/dialogs/openBotDialog/openBotDialog.spec.tsx index 36173623b..88c4b0dc6 100644 --- a/packages/app/client/src/ui/dialogs/openBotDialog/openBotDialog.spec.tsx +++ b/packages/app/client/src/ui/dialogs/openBotDialog/openBotDialog.spec.tsx @@ -36,11 +36,13 @@ import { mount } from 'enzyme'; import * as React from 'react'; import { Provider } from 'react-redux'; import { combineReducers, createStore } from 'redux'; + import * as BotActions from '../../../data/action/botActions'; import { bot } from '../../../data/reducer/bot'; import { CommandServiceImpl } from '../../../platform/commands/commandServiceImpl'; import { ActiveBotHelper } from '../../helpers/activeBotHelper'; import { DialogService } from '../service'; + import { OpenBotDialog } from './openBotDialog'; import { OpenBotDialogContainer } from './openBotDialogContainer'; @@ -49,13 +51,13 @@ jest.mock('./openBotDialog.scss', () => ({})); jest.mock('../../../data/store', () => ({ get store() { return mockStore; - } + }, })); jest.mock('../service', () => ({ DialogService: { showDialog: () => Promise.resolve(true), hideDialog: () => Promise.resolve(false), - } + }, })); jest.mock('../dialogStyles.scss', () => ({})); jest.mock('../../editor/recentBotsList/recentBotsList.scss', () => ({})); @@ -63,11 +65,11 @@ jest.mock('../', () => ({})); const bots = [ { - 'path': '/some/path', - 'displayName': 'mockMock', - 'transcriptsPath': '/Users/microsoft/Documents/testbot/transcripts', - 'chatsPath': '/Users/microsoft/Documents/testbot/dialogs' - } + path: '/some/path', + displayName: 'mockMock', + transcriptsPath: '/Users/microsoft/Documents/testbot/transcripts', + chatsPath: '/Users/microsoft/Documents/testbot/dialogs', + }, ]; describe('The OpenBotDialog', () => { @@ -76,9 +78,11 @@ describe('The OpenBotDialog', () => { let instance; beforeEach(() => { mockStore.dispatch(BotActions.load(bots)); - parent = mount( - - ); + parent = mount( + + + + ); node = parent.find(OpenBotDialog); instance = node.instance(); }); @@ -93,8 +97,8 @@ describe('The OpenBotDialog', () => { instance.onInputChange({ target: { type: 'text', - value: 'http://localhost:6500/api/messages' - } + value: 'http://localhost:6500/api/messages', + }, } as any); expect(instance.state.botUrl).toBe('http://localhost:6500/api/messages'); @@ -102,8 +106,8 @@ describe('The OpenBotDialog', () => { instance.onInputChange({ target: { type: 'file', - files: { item: () => ({ path: 'some/path/to/myBot.bot' }) } - } + files: { item: () => ({ path: 'some/path/to/myBot.bot' }) }, + }, } as any); expect(instance.state.botUrl).toBe('some/path/to/myBot.bot'); @@ -113,7 +117,7 @@ describe('The OpenBotDialog', () => { const spy = jest.fn(); const mockInput = { value: 'this is some text', - setSelectionRange: spy + setSelectionRange: spy, }; instance.onFocus({ target: mockInput } as any); @@ -125,11 +129,13 @@ describe('The OpenBotDialog', () => { instance.onInputChange({ target: { type: 'file', - files: { item: () => ({ path: 'some/path/to/myBot.bot' }) } - } + files: { item: () => ({ path: 'some/path/to/myBot.bot' }) }, + }, } as any); - const botHelperSpy = jest.spyOn(ActiveBotHelper, 'confirmAndOpenBotFromFile').mockResolvedValue(true); + const botHelperSpy = jest + .spyOn(ActiveBotHelper, 'confirmAndOpenBotFromFile') + .mockResolvedValue(true); await instance.onSubmit(); expect(botHelperSpy).toHaveBeenCalledWith('some/path/to/myBot.bot'); @@ -139,14 +145,16 @@ describe('The OpenBotDialog', () => { instance.onInputChange({ target: { type: 'text', - value: 'http://localhost:6500/api/messages' - } + value: 'http://localhost:6500/api/messages', + }, } as any); const commandServiceSpy = jest.spyOn(CommandServiceImpl, 'call'); await instance.onSubmit(); - expect(commandServiceSpy).toHaveBeenCalledWith(SharedConstants.Commands.Emulator.NewLiveChat, - { endpoint: 'http://localhost:6500/api/messages' }); + expect(commandServiceSpy).toHaveBeenCalledWith( + SharedConstants.Commands.Emulator.NewLiveChat, + { endpoint: 'http://localhost:6500/api/messages' } + ); }); }); diff --git a/packages/app/client/src/ui/dialogs/openBotDialog/openBotDialog.tsx b/packages/app/client/src/ui/dialogs/openBotDialog/openBotDialog.tsx index 9d674df78..2716d60b2 100644 --- a/packages/app/client/src/ui/dialogs/openBotDialog/openBotDialog.tsx +++ b/packages/app/client/src/ui/dialogs/openBotDialog/openBotDialog.tsx @@ -31,9 +31,16 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { DefaultButton, Dialog, DialogFooter, PrimaryButton, TextField } from '@bfemulator/ui-react'; +import { + DefaultButton, + Dialog, + DialogFooter, + PrimaryButton, + TextField, +} from '@bfemulator/ui-react'; import * as React from 'react'; import { ChangeEvent, FocusEvent, ReactNode } from 'react'; + import * as openBotStyles from './openBotDialog.scss'; export interface OpenBotDialogProps { @@ -47,7 +54,10 @@ export interface OpenBotDialogState { botUrl?: string; } -export class OpenBotDialog extends React.Component { +export class OpenBotDialog extends React.Component< + OpenBotDialogProps, + OpenBotDialogState +> { public state = { botUrl: '' }; private static validateEndpoint(endpoint: string): string { @@ -55,9 +65,13 @@ export class OpenBotDialog extends React.Component -
+ cancel={this.props.onDialogCancel} + className={openBotStyles.themeOverrides} + title="Open a bot" + > + - - + onChange={this.onInputChange} + onFocus={this.onFocus} + autoFocus={true} + value={botUrl} + > + Browse + type="file" + /> - - + @@ -108,13 +119,13 @@ export class OpenBotDialog extends React.Component) => { const input = event.target as HTMLInputElement; input.setSelectionRange(0, (input.value || '').length); - } + }; private onInputChange = (event: ChangeEvent) => { const { type, files, value } = event.target; const botUrl = type === 'file' ? files.item(0).path : value; this.setState({ botUrl }); - } + }; private onSubmit = async () => { try { @@ -122,5 +133,5 @@ export class OpenBotDialog extends React.Component void): OpenBotDialogProps => { - const { Commands: { Emulator: { NewLiveChat } } } = SharedConstants; +const mapDispatchToProps = ( + dispatch: (action: Action) => void +): OpenBotDialogProps => { + const { + Commands: { + Emulator: { NewLiveChat }, + }, + } = SharedConstants; return { onDialogCancel: () => DialogService.hideDialog(), openBot: (urlOrPath: string) => { DialogService.hideDialog(); if (urlOrPath.startsWith('http')) { - return CommandServiceImpl.call(NewLiveChat, { endpoint: urlOrPath } as IEndpointService); + return CommandServiceImpl.call(NewLiveChat, { + endpoint: urlOrPath, + } as IEndpointService); } return ActiveBotHelper.confirmAndOpenBotFromFile(urlOrPath); }, sendNotification: (error: Error) => - dispatch(beginAdd(newNotification(`An Error occurred on the Open Bot Dialog: ${ error }`))), + dispatch( + beginAdd( + newNotification(`An Error occurred on the Open Bot Dialog: ${error}`) + ) + ), }; }; diff --git a/packages/app/client/src/ui/dialogs/postMigrationDialog/postMigrationDialog.spec.tsx b/packages/app/client/src/ui/dialogs/postMigrationDialog/postMigrationDialog.spec.tsx index 5baf43ff9..73f45c4e0 100644 --- a/packages/app/client/src/ui/dialogs/postMigrationDialog/postMigrationDialog.spec.tsx +++ b/packages/app/client/src/ui/dialogs/postMigrationDialog/postMigrationDialog.spec.tsx @@ -34,31 +34,34 @@ import * as React from 'react'; import { Provider } from 'react-redux'; import { createStore } from 'redux'; -import { navBar } from '../../../data/reducer/navBar'; import { mount } from 'enzyme'; + +import { navBar } from '../../../data/reducer/navBar'; + import { PostMigrationDialogContainer } from './postMigrationDialogContainer'; import { PostMigrationDialog } from './postMigrationDialog'; jest.mock('../../dialogs', () => ({})); jest.mock('./postMigrationDialog.scss', () => ({})); describe('The PostMigrationDialogContainer component', () => { - let wrapper; - let node; + let wrapper; + let node; - beforeEach(() => { - wrapper = mount( - - - ); - node = wrapper.find(PostMigrationDialog); - }); + beforeEach(() => { + wrapper = mount( + + + + ); + node = wrapper.find(PostMigrationDialog); + }); - it('should render deeply', () => { - expect(wrapper.find(PostMigrationDialogContainer)).not.toBe(null); - expect(node.find(PostMigrationDialog)).not.toBe(null); - }); + it('should render deeply', () => { + expect(wrapper.find(PostMigrationDialogContainer)).not.toBe(null); + expect(node.find(PostMigrationDialog)).not.toBe(null); + }); - it('should contain a close function in the props', () => { - expect(typeof (node.props() as any).close).toBe('function'); - }); + it('should contain a close function in the props', () => { + expect(typeof (node.props() as any).close).toBe('function'); + }); }); diff --git a/packages/app/client/src/ui/dialogs/postMigrationDialog/postMigrationDialog.tsx b/packages/app/client/src/ui/dialogs/postMigrationDialog/postMigrationDialog.tsx index 7df4e55bd..6fae5bd87 100644 --- a/packages/app/client/src/ui/dialogs/postMigrationDialog/postMigrationDialog.tsx +++ b/packages/app/client/src/ui/dialogs/postMigrationDialog/postMigrationDialog.tsx @@ -31,39 +31,48 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import * as React from 'react'; import { Dialog, DialogFooter, PrimaryButton } from '@bfemulator/ui-react'; +import * as React from 'react'; + import * as styles from './postMigrationDialog.scss'; export interface PostMigrationDialogProps { close: () => void; } -export class PostMigrationDialog extends React.Component { - constructor(props: PostMigrationDialogProps) { +export class PostMigrationDialog extends React.Component< + PostMigrationDialogProps +> { + public constructor(props: PostMigrationDialogProps) { super(props); } public render(): JSX.Element { return ( - -

+

+

We’ve copied your bot endpoints from Emulator v3 and saved them as - .bot files. - A - .bot file - stores metadata about different services your bot consumes and enables you - to edit these services directly from the Emulator v4. - Learn more about bot configuration files. + .bot files. A + .bot file + stores metadata about different services your bot consumes and enables + you to edit these services directly from the Emulator v4. + + {' '} + Learn more about bot configuration files. + +

+

+ {/* eslint-disable-next-line react/no-unescaped-entities */} + You can move a bot to any location by right-clicking the bot's name + under My Bots.

-

You can move a bot to any location by right-clicking the bot's name under My Bots.

- Learn more about new features in Bot Framework Emulator v4 + Learn more about new features in Bot Framework Emulator v4

- +
); @@ -71,5 +80,5 @@ export class PostMigrationDialog extends React.Component { this.props.close(); - } + }; } diff --git a/packages/app/client/src/ui/dialogs/postMigrationDialog/postMigrationDialogContainer.ts b/packages/app/client/src/ui/dialogs/postMigrationDialog/postMigrationDialogContainer.ts index e78cbc550..0d92354b5 100644 --- a/packages/app/client/src/ui/dialogs/postMigrationDialog/postMigrationDialogContainer.ts +++ b/packages/app/client/src/ui/dialogs/postMigrationDialog/postMigrationDialogContainer.ts @@ -1,6 +1,43 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { connect } from 'react-redux'; + import { DialogService } from '../service'; -import { PostMigrationDialog, PostMigrationDialogProps } from './postMigrationDialog'; + +import { + PostMigrationDialog, + PostMigrationDialogProps, +} from './postMigrationDialog'; const mapStateToProps = (ownProps: PostMigrationDialogProps) => ownProps; @@ -8,8 +45,11 @@ function mapDispatchToProps(): PostMigrationDialogProps { return { close: () => { DialogService.hideDialog(); - } + }, }; } -export const PostMigrationDialogContainer = connect(mapStateToProps, mapDispatchToProps)(PostMigrationDialog); +export const PostMigrationDialogContainer = connect( + mapStateToProps, + mapDispatchToProps +)(PostMigrationDialog); diff --git a/packages/app/client/src/ui/dialogs/progressIndicator/progressIndicator.spec.tsx b/packages/app/client/src/ui/dialogs/progressIndicator/progressIndicator.spec.tsx index fc9d89187..c2c505bbb 100644 --- a/packages/app/client/src/ui/dialogs/progressIndicator/progressIndicator.spec.tsx +++ b/packages/app/client/src/ui/dialogs/progressIndicator/progressIndicator.spec.tsx @@ -1,9 +1,43 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import * as React from 'react'; import { Provider } from 'react-redux'; import { mount } from 'enzyme'; -import { ProgressIndicatorContainer } from './progressIndicatorContainer'; import { combineReducers, createStore } from 'redux'; + import { progressIndicator } from '../../../data/reducer/progressIndicator'; + +import { ProgressIndicatorContainer } from './progressIndicatorContainer'; import { ProgressIndicator } from './progressIndicator'; jest.mock('./progressIndicator.scss', () => ({})); @@ -11,7 +45,7 @@ jest.mock('../service', () => ({ DialogService: { showDialog: () => Promise.resolve(true), hideDialog: () => Promise.resolve(false), - } + }, })); jest.mock('../dialogStyles.scss', () => ({})); @@ -20,9 +54,11 @@ describe('The ProgressIndicatorContainer component should', () => { let parent; let node; beforeEach(() => { - parent = mount( - - ); + parent = mount( + + + + ); node = parent.find(ProgressIndicator); }); diff --git a/packages/app/client/src/ui/dialogs/progressIndicator/progressIndicator.tsx b/packages/app/client/src/ui/dialogs/progressIndicator/progressIndicator.tsx index 43e475847..6173744e9 100644 --- a/packages/app/client/src/ui/dialogs/progressIndicator/progressIndicator.tsx +++ b/packages/app/client/src/ui/dialogs/progressIndicator/progressIndicator.tsx @@ -1,9 +1,43 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +import { Dialog, DialogFooter, PrimaryButton } from '@bfemulator/ui-react'; import * as React from 'react'; import { Component } from 'react'; -import { Dialog, DialogFooter, PrimaryButton } from '@bfemulator/ui-react'; -import * as styles from './progressIndicator.scss'; + import * as dialogStyles from '../dialogStyles.scss'; +import * as styles from './progressIndicator.scss'; + export interface ProgressIndicatorProps extends ProgressIndicatorState { cancel: () => void; close: () => void; @@ -14,24 +48,25 @@ export interface ProgressIndicatorState { progress: number; } -export class ProgressIndicator extends Component { +export class ProgressIndicator extends Component< + ProgressIndicatorProps, + ProgressIndicatorState +> { private hr: HTMLElement; public render() { if (this.hr) { - this.hr.style.setProperty('--progress-percentage', `${this.props.progress}%`); + this.hr.style.setProperty( + '--progress-percentage', + `${this.props.progress}%` + ); } return ( - -

- { this.props.label } -

-
+ +

{this.props.label}

+
- +
); @@ -39,5 +74,5 @@ export class ProgressIndicator extends Component { this.hr = hr; - } + }; } diff --git a/packages/app/client/src/ui/dialogs/progressIndicator/progressIndicatorContainer.ts b/packages/app/client/src/ui/dialogs/progressIndicator/progressIndicatorContainer.ts index 9542e795b..098ac7a80 100644 --- a/packages/app/client/src/ui/dialogs/progressIndicator/progressIndicatorContainer.ts +++ b/packages/app/client/src/ui/dialogs/progressIndicator/progressIndicatorContainer.ts @@ -33,23 +33,28 @@ import { connect } from 'react-redux'; import { Action } from 'redux'; + +import { cancelCurrentProcess } from '../../../data/action/progressIndicatorActions'; import { RootState } from '../../../data/store'; import { DialogService } from '../service'; + import { ProgressIndicator } from './progressIndicator'; -import { cancelCurrentProcess } from '../../../data/action/progressIndicatorActions'; -const mapStateToProps = (state: RootState, ownProps: { [propName: string]: any }) => { +const mapStateToProps = ( + state: RootState, + ownProps: { [propName: string]: any } +) => { const { progressIndicator } = state; return { ...ownProps, - ...progressIndicator + ...progressIndicator, }; }; const mapDispatchToProps = (dispatch: (action: Action) => void) => { return { cancel: () => DialogService.hideDialog(dispatch(cancelCurrentProcess())), - close: () => DialogService.hideDialog() + close: () => DialogService.hideDialog(), }; }; diff --git a/packages/app/client/src/ui/dialogs/resourcesSettings/resourcesSettings.spec.tsx b/packages/app/client/src/ui/dialogs/resourcesSettings/resourcesSettings.spec.tsx index 47a8ec2e6..eea507ad2 100644 --- a/packages/app/client/src/ui/dialogs/resourcesSettings/resourcesSettings.spec.tsx +++ b/packages/app/client/src/ui/dialogs/resourcesSettings/resourcesSettings.spec.tsx @@ -1,14 +1,48 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import * as React from 'react'; import { Provider } from 'react-redux'; import { mount } from 'enzyme'; -import { ResourcesSettingsContainer } from './resourcesSettingsContainer'; import { combineReducers, createStore } from 'redux'; + import { bot } from '../../../data/reducer/bot'; import { resources } from '../../../data/reducer/resourcesReducer'; -import { ResourcesSettings } from './resourcesSettings'; import { load, setActive } from '../../../data/action/botActions'; import { CommandServiceImpl } from '../../../platform/commands/commandServiceImpl'; +import { ResourcesSettings } from './resourcesSettings'; +import { ResourcesSettingsContainer } from './resourcesSettingsContainer'; + const mockStore = createStore(combineReducers({ resources, bot })); jest.mock('./resourcesSettings.scss', () => ({})); jest.mock('../dialogStyles.scss', () => ({})); @@ -17,7 +51,7 @@ jest.mock('../service', () => ({ DialogService: { showDialog: () => Promise.resolve(true), hideDialog: () => Promise.resolve(false), - } + }, })); jest.mock('../../../platform/commands/commandServiceImpl', () => ({ @@ -27,19 +61,19 @@ jest.mock('../../../platform/commands/commandServiceImpl', () => ({ }, call: async (commandName: string, ...args: any[]) => { // - } - } + }, + }, })); jest.mock('../../../data/store', () => ({ RootState: () => ({}), get store() { return mockStore; - } + }, })); jest.mock('../../../data/botHelpers', () => ({ - getBotInfoByPath: () => ({}) + getBotInfoByPath: () => ({}), })); describe('The ResourcesSettings component should', () => { @@ -62,9 +96,11 @@ describe('The ResourcesSettings component should', () => { mockStore.dispatch(load([mockBot])); mockStore.dispatch(setActive(mockBot)); - parent = mount( - - ); + parent = mount( + + + + ); node = parent.find(ResourcesSettings); }); @@ -82,7 +118,9 @@ describe('The ResourcesSettings component should', () => { it('should update the state when the chat input is changed', () => { const instance = node.instance(); expect(instance.state.chatsPath).toBeUndefined(); - const mockEvent = { target: { value: 'hello', dataset: { prop: 'chatsPath' } } }; + const mockEvent = { + target: { value: 'hello', dataset: { prop: 'chatsPath' } }, + }; instance.onInputChange(mockEvent as any); expect(instance.state.chatsPath).toBe('hello'); }); @@ -90,7 +128,9 @@ describe('The ResourcesSettings component should', () => { it('should update the state when the transcript input is changed', () => { const instance = node.instance(); expect(instance.state.transcriptsPath).toBeUndefined(); - const mockEvent = { target: { value: 'hello', dataset: { prop: 'transcriptsPath' } } }; + const mockEvent = { + target: { value: 'hello', dataset: { prop: 'transcriptsPath' } }, + }; instance.onInputChange(mockEvent as any); expect(instance.state.transcriptsPath).toBe('hello'); }); @@ -98,7 +138,9 @@ describe('The ResourcesSettings component should', () => { it('should open the browse dialog when the browse anchor is clicked', async () => { const instance = node.instance(); const spy = jest.spyOn(CommandServiceImpl, 'remoteCall'); - await instance.onBrowseClick({ currentTarget: { getAttribute: () => 'attr' } }); + await instance.onBrowseClick({ + currentTarget: { getAttribute: () => 'attr' }, + }); expect(spy).toHaveBeenCalled(); }); }); diff --git a/packages/app/client/src/ui/dialogs/resourcesSettings/resourcesSettings.tsx b/packages/app/client/src/ui/dialogs/resourcesSettings/resourcesSettings.tsx index 19d817d81..abbe168fc 100644 --- a/packages/app/client/src/ui/dialogs/resourcesSettings/resourcesSettings.tsx +++ b/packages/app/client/src/ui/dialogs/resourcesSettings/resourcesSettings.tsx @@ -1,10 +1,50 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +import { BotInfo } from '@bfemulator/app-shared'; +import { + DefaultButton, + Dialog, + DialogFooter, + PrimaryButton, + TextField, +} from '@bfemulator/ui-react'; import * as React from 'react'; import { ChangeEvent, Component, MouseEvent } from 'react'; -import { BotInfo } from '@bfemulator/app-shared'; -import { DefaultButton, Dialog, DialogFooter, PrimaryButton, TextField } from '@bfemulator/ui-react'; -import * as styles from './resourcesSettings.scss'; + import * as dialogStyles from '../dialogStyles.scss'; +import * as styles from './resourcesSettings.scss'; + export interface ResourcesSettingsState { transcriptsPath?: string; chatsPath?: string; @@ -20,7 +60,10 @@ export interface ResourcesSettingsProps extends ResourcesSettingsState { showOpenDialog: () => Promise; } -export class ResourcesSettings extends Component { +export class ResourcesSettings extends Component< + ResourcesSettingsProps, + ResourcesSettingsState +> { constructor(props: ResourcesSettingsProps, context: ResourcesSettingsState) { super(props, context); const { transcriptsPath, chatsPath } = props; @@ -28,49 +71,61 @@ export class ResourcesSettings extends Component -
+ cancel={this.props.cancel} + className={dialogStyles.dialogLarge} + > + -
+
+ required={true} + onChange={this.onInputChange} + errorMessage={transcriptsInputError} + /> + className={styles.browseAnchor} + onClick={this.onBrowseClick} + > Browse
- Cancel - Save Changes + Cancel + + Save Changes +
); @@ -80,13 +135,13 @@ export class ResourcesSettings extends Component { const { chatsPath, transcriptsPath } = this.state; const { path } = this.props; this.props.save({ chatsPath, transcriptsPath, path }); - } + }; private onBrowseClick = async (event: MouseEvent) => { const prop = event.currentTarget.getAttribute('data-prop'); @@ -94,5 +149,5 @@ export class ResourcesSettings extends Component { +const mapStateToProps = ( + state: RootState, + ownProps: ResourcesSettingsProps +) => { const { path } = state.bot.activeBot; const botInfo: BotInfo = getBotInfoByPath(path); const { transcriptsPath, chatsPath } = botInfo; @@ -16,9 +52,12 @@ const mapStateToProps = (state: RootState, ownProps: ResourcesSettingsProps) => const mapDispatchToProps = _dispatch => ({ save: (settings: Partial) => DialogService.hideDialog(settings), - showOpenDialog: () => CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.ShowOpenDialog, - { properties: ['openDirectory'] }), - cancel: () => DialogService.hideDialog(0) + showOpenDialog: () => + CommandServiceImpl.remoteCall( + SharedConstants.Commands.Electron.ShowOpenDialog, + { properties: ['openDirectory'] } + ), + cancel: () => DialogService.hideDialog(0), }); export const ResourcesSettingsContainer = connect( mapStateToProps, diff --git a/packages/app/client/src/ui/dialogs/secretPromptDialog/secretPromptDialog.spec.tsx b/packages/app/client/src/ui/dialogs/secretPromptDialog/secretPromptDialog.spec.tsx index 85fc7e127..96a59c347 100644 --- a/packages/app/client/src/ui/dialogs/secretPromptDialog/secretPromptDialog.spec.tsx +++ b/packages/app/client/src/ui/dialogs/secretPromptDialog/secretPromptDialog.spec.tsx @@ -1,21 +1,55 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import * as React from 'react'; import { Provider } from 'react-redux'; import { mount } from 'enzyme'; import { combineReducers, createStore } from 'redux'; -import { bot } from '../../../data/reducer/bot'; import { BotConfigWithPathImpl } from '@bfemulator/sdk-shared'; + +import { bot } from '../../../data/reducer/bot'; import { setActive } from '../../../data/action/botActions'; +import { DialogService } from '../service'; + import { SecretPromptDialogContainer } from './secretPromptDialogContainer'; import { SecretPromptDialog } from './secretPromptDialog'; -import { DialogService } from '../service'; const mockStore = createStore(combineReducers({ bot })); const mockBot = BotConfigWithPathImpl.fromJSON({}); jest.mock('../../../platform/commands/commandServiceImpl', () => ({ CommandServiceImpl: { - remoteCall: async () => true - } + remoteCall: async () => true, + }, })); jest.mock('../dialogStyles.scss', () => ({})); @@ -24,20 +58,20 @@ jest.mock('./secretPromptDialog.scss', () => ({})); jest.mock('../../../data/store', () => ({ get store() { return mockStore; - } + }, })); jest.mock('../service', () => ({ DialogService: { showDialog: () => Promise.resolve(true), hideDialog: () => Promise.resolve(false), - } + }, })); jest.mock('../../../utils', () => ({ generateBotSecret: () => { return Math.random() + ''; - } + }, })); describe('The Secret prompt dialog', () => { @@ -45,9 +79,11 @@ describe('The Secret prompt dialog', () => { let node; beforeEach(() => { mockStore.dispatch(setActive(mockBot)); - parent = mount( - - ); + parent = mount( + + + + ); node = parent.find(SecretPromptDialog); }); @@ -70,7 +106,9 @@ describe('The Secret prompt dialog', () => { it('should update the state when a secret is input by the user', () => { const instance = node.instance(); - const mockEvent = { target: { value: 'shhh!', dataset: { prop: 'secret' } } }; + const mockEvent = { + target: { value: 'shhh!', dataset: { prop: 'secret' } }, + }; instance.onChangeSecret(mockEvent as any); expect(instance.state.secret).toBe('shhh!'); }); @@ -78,7 +116,9 @@ describe('The Secret prompt dialog', () => { it('should call DialogService.hideDialog with the new secrete when the save button is clicked', () => { const spy = jest.spyOn(DialogService, 'hideDialog'); const instance = node.instance(); - const mockEvent = { target: { value: 'shhh!', dataset: { prop: 'secret' } } }; + const mockEvent = { + target: { value: 'shhh!', dataset: { prop: 'secret' } }, + }; instance.onChangeSecret(mockEvent as any); instance.onSaveClick(null); @@ -88,7 +128,9 @@ describe('The Secret prompt dialog', () => { it('should call DialogService.hideDialog with nothing when the cancel button is clicked', () => { const spy = jest.spyOn(DialogService, 'hideDialog'); const instance = node.instance(); - const mockEvent = { target: { value: 'shhh!', dataset: { prop: 'secret' } } }; + const mockEvent = { + target: { value: 'shhh!', dataset: { prop: 'secret' } }, + }; instance.onChangeSecret(mockEvent as any); instance.onDismissClick(); diff --git a/packages/app/client/src/ui/dialogs/secretPromptDialog/secretPromptDialog.tsx b/packages/app/client/src/ui/dialogs/secretPromptDialog/secretPromptDialog.tsx index 2b4e9df0f..635fb522c 100644 --- a/packages/app/client/src/ui/dialogs/secretPromptDialog/secretPromptDialog.tsx +++ b/packages/app/client/src/ui/dialogs/secretPromptDialog/secretPromptDialog.tsx @@ -31,12 +31,18 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +import { + DefaultButton, + Dialog, + PrimaryButton, + TextField, +} from '@bfemulator/ui-react'; import { ChangeEvent } from 'react'; import * as React from 'react'; -import * as styles from './secretPromptDialog.scss'; + import * as dialogStyles from '../dialogStyles.scss'; -import { DefaultButton, Dialog, PrimaryButton, TextField, } from '@bfemulator/ui-react'; +import * as styles from './secretPromptDialog.scss'; interface SecretPromptDialogState { secret: string; @@ -48,52 +54,66 @@ export interface SecretPromptDialogProps { onSaveClick: (newSecret: string) => void; } -export class SecretPromptDialog extends React.Component { - constructor(props: SecretPromptDialogProps, context: SecretPromptDialogState) { +export class SecretPromptDialog extends React.Component< + SecretPromptDialogProps, + SecretPromptDialogState +> { + constructor( + props: SecretPromptDialogProps, + context: SecretPromptDialogState + ) { super(props, context); this.state = { secret: '', revealSecret: false }; } - render(): JSX.Element { + public render(): JSX.Element { return ( + className={dialogStyles.dialogMedium} + modalStyle={styles.secretPromptDialogModal} + cancel={this.onDismissClick} + >

- { ' If you created your bot through the Azure Bot Service, you can find your bot file secret in the Azure ' + - 'portal under Application settings.' } + {' If you created your bot through the Azure Bot Service, you can find your bot file secret in the Azure ' + + 'portal under Application settings.'}

- { 'If you encrypted your bot file with the MsBot command-line tool, your bot file secret was displayed ' + - 'when you ran MsBot. ' } - Learn more about MsBot. + {'If you encrypted your bot file with the MsBot command-line tool, your bot file secret was displayed ' + + 'when you ran MsBot. '} + + Learn more about MsBot. +

-
); @@ -101,18 +121,18 @@ export class SecretPromptDialog extends React.Component { this.setState({ revealSecret: !this.state.revealSecret }); - } + }; private onDismissClick = () => { this.props.onCancelClick(); - } + }; private onSaveClick = () => { this.props.onSaveClick(this.state.secret); - } + }; private onChangeSecret = (event: ChangeEvent) => { const { value: secret } = event.target; this.setState({ secret }); - } + }; } diff --git a/packages/app/client/src/ui/dialogs/secretPromptDialog/secretPromptDialogContainer.ts b/packages/app/client/src/ui/dialogs/secretPromptDialog/secretPromptDialogContainer.ts index f9fef80ac..d50e26235 100644 --- a/packages/app/client/src/ui/dialogs/secretPromptDialog/secretPromptDialogContainer.ts +++ b/packages/app/client/src/ui/dialogs/secretPromptDialog/secretPromptDialogContainer.ts @@ -1,10 +1,48 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { connect } from 'react-redux'; -import { SecretPromptDialog, SecretPromptDialogProps } from './secretPromptDialog'; import { DialogService } from '../service'; +import { + SecretPromptDialog, + SecretPromptDialogProps, +} from './secretPromptDialog'; + const mapStateToProps = (): SecretPromptDialogProps => ({ onCancelClick: () => DialogService.hideDialog(null), - onSaveClick: (newSecret: string) => DialogService.hideDialog(newSecret) + onSaveClick: (newSecret: string) => DialogService.hideDialog(newSecret), }); -export const SecretPromptDialogContainer = connect(mapStateToProps)(SecretPromptDialog); +export const SecretPromptDialogContainer = connect(mapStateToProps)( + SecretPromptDialog +); diff --git a/packages/app/client/src/ui/dialogs/service/dialogService.spec.tsx b/packages/app/client/src/ui/dialogs/service/dialogService.spec.tsx index 7bd89d980..4674aa44a 100644 --- a/packages/app/client/src/ui/dialogs/service/dialogService.spec.tsx +++ b/packages/app/client/src/ui/dialogs/service/dialogService.spec.tsx @@ -1,14 +1,49 @@ -import { DialogService } from './dialogService'; +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + import * as React from 'react'; import { Component } from 'react'; -import { resources } from '../../../data/reducer/resourcesReducer'; -import { bot } from '../../../data/reducer/bot'; import { combineReducers, createStore } from 'redux'; + +import { bot } from '../../../data/reducer/bot'; +import { resources } from '../../../data/reducer/resourcesReducer'; import * as DialogActions from '../../../data/action/dialogActions'; +import { DialogService } from './dialogService'; + const mockComponent = class extends Component<{}, {}> { public render() { - return
; + return
; } componentDidMount() { @@ -19,7 +54,7 @@ const mockStore = createStore(combineReducers({ resources, bot })); jest.mock('../../../data/store', () => ({ get store() { return mockStore; - } + }, })); describe('The DialogService', () => { it('should resolve to null if no dialogHost element is set', async () => { diff --git a/packages/app/client/src/ui/dialogs/service/dialogService.ts b/packages/app/client/src/ui/dialogs/service/dialogService.ts index 13f17215a..c2780a36c 100644 --- a/packages/app/client/src/ui/dialogs/service/dialogService.ts +++ b/packages/app/client/src/ui/dialogs/service/dialogService.ts @@ -35,19 +35,22 @@ import * as React from 'react'; import { ComponentClass, StatelessComponent } from 'react'; import * as ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; -import * as DialogActions from '../../../data/action/dialogActions'; +import * as DialogActions from '../../../data/action/dialogActions'; import { store } from '../../../data/store'; export interface DialogService { - showDialog(dialog: ComponentClass | StatelessComponent, props: { [propName: string]: any }): any; + showDialog( + dialog: ComponentClass | StatelessComponent, + props: { [propName: string]: any } + ): any; hideDialog(): any; setHost(hostElement: HTMLElement): void; } -export const DialogService = new class implements DialogService { +class DialogServiceImpl implements DialogService { private _hostElement: HTMLElement; private _resolve: (value?: any) => void; @@ -55,16 +58,23 @@ export const DialogService = new class implements DialogService { * * Ex. DialogService.showDialog(PasswordPromptDialog).then(pw => // do something with password from dialog) */ - showDialog(dialog: T, props: {} = {}): Promise { + showDialog( + dialog: T, + props: {} = {} + ): Promise { if (!this._hostElement) { - return new Promise((resolve) => resolve(null)); + return new Promise(resolve => resolve(null)); } - const reactElement = React.createElement(Provider, { store }, React.createElement(dialog, props)); + const reactElement = React.createElement( + Provider, + { store }, + React.createElement(dialog, props) + ); ReactDOM.render(reactElement, this._hostElement, this.notifyHostOfRender); store.dispatch(DialogActions.setShowing(true)); // set up the dialog to return a value from the dialog - return new Promise((resolve) => { + return new Promise(resolve => { this._resolve = resolve; }); } @@ -90,5 +100,7 @@ export const DialogService = new class implements DialogService { if (this._hostElement) { this._hostElement.dispatchEvent(new Event('dialogRendered')); } - } -}; + }; +} + +export const DialogService = new DialogServiceImpl(); diff --git a/packages/app/client/src/ui/dialogs/tabManager/tabManager.spec.tsx b/packages/app/client/src/ui/dialogs/tabManager/tabManager.spec.tsx index fb20c73ad..31fca9a2d 100644 --- a/packages/app/client/src/ui/dialogs/tabManager/tabManager.spec.tsx +++ b/packages/app/client/src/ui/dialogs/tabManager/tabManager.spec.tsx @@ -1,68 +1,102 @@ -import { editor } from '../../../data/reducer/editor'; +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + import { combineReducers, createStore } from 'redux'; import { mount } from 'enzyme'; import * as React from 'react'; -import { TabManager } from './tabManager'; -import { TabManagerContainer } from './tabManagerContainer'; import { Provider } from 'react-redux'; +import { editor } from '../../../data/reducer/editor'; + +import { TabManagerContainer } from './tabManagerContainer'; +import { TabManager } from './tabManager'; + const mockState = { - 'editor': { - 'activeEditor': 'primary', - 'draggingTab': true, - 'editors': { - 'primary': { - 'activeDocumentId': '1234', - 'documents': { + editor: { + activeEditor: 'primary', + draggingTab: true, + editors: { + primary: { + activeDocumentId: '1234', + documents: { 'e7985c20-b059-11e8-8bf1-69211e6350d9': { - 'contentType': 'application/vnd.microsoft.bfemulator.document.livechat', - 'documentId': 'e7985c20-b059-11e8-8bf1-69211e6350d9', - 'isGlobal': false + contentType: + 'application/vnd.microsoft.bfemulator.document.livechat', + documentId: 'e7985c20-b059-11e8-8bf1-69211e6350d9', + isGlobal: false, }, '12345678': { - 'contentType': 'application/vnd.microsoft.bfemulator.document.transcript', - 'documentId': '12345678', - 'isGlobal': false, - 'fileName': 'CardExamples.transcript' + contentType: + 'application/vnd.microsoft.bfemulator.document.transcript', + documentId: '12345678', + isGlobal: false, + fileName: 'CardExamples.transcript', }, '1234': { - 'contentType': 'application/vnd.microsoft.bfemulator.document.transcript', - 'documentId': '1234', - 'isGlobal': false - } + contentType: + 'application/vnd.microsoft.bfemulator.document.transcript', + documentId: '1234', + isGlobal: false, + }, }, - 'recentTabs': [ + recentTabs: [ '1234', '12345678', - 'e7985c20-b059-11e8-8bf1-69211e6350d9' - ], - 'tabOrder': [ 'e7985c20-b059-11e8-8bf1-69211e6350d9', - '12345678', - '1234' - ] + ], + tabOrder: ['e7985c20-b059-11e8-8bf1-69211e6350d9', '12345678', '1234'], + }, + secondary: { + activeDocumentId: null, + documents: {}, + recentTabs: [], + tabOrder: [], }, - 'secondary': { - 'activeDocumentId': null, - 'documents': {}, - 'recentTabs': [], - 'tabOrder': [] - } }, - 'docsWithPendingChanges': [] - } + docsWithPendingChanges: [], + }, }; const mockStore = createStore(combineReducers({ editor }), mockState); const windowEvents = []; const mockWindow = { addEventListener: event => { windowEvents.push(event); - } + }, }; jest.mock('../../../data/store', () => ({ get store() { return mockStore; - } + }, })); jest.mock('./tabManager.scss', () => ({})); @@ -72,18 +106,17 @@ describe('The TabManager component', () => { let instance; beforeEach(() => { windowEvents.length = 0; - parent = mount( - - ); + parent = mount( + + + + ); node = parent.find(TabManager); instance = node.instance(); }); it('should set event listeners on the window object when the component mounts', () => { - expect(windowEvents).toEqual([ - 'keydown', - 'keyup' - ]); + expect(windowEvents).toEqual(['keydown', 'keyup']); }); describe('should set the state as expected when the', () => { diff --git a/packages/app/client/src/ui/dialogs/tabManager/tabManager.tsx b/packages/app/client/src/ui/dialogs/tabManager/tabManager.tsx index 9eac388a4..5b6766798 100644 --- a/packages/app/client/src/ui/dialogs/tabManager/tabManager.tsx +++ b/packages/app/client/src/ui/dialogs/tabManager/tabManager.tsx @@ -32,6 +32,7 @@ // import * as React from 'react'; + import * as styles from './tabManager.scss'; export interface TabManagerProps { @@ -48,7 +49,10 @@ export interface TabManagerState { showing: boolean; } -export class TabManager extends React.Component { +export class TabManager extends React.Component< + TabManagerProps, + TabManagerState +> { private tabRefs: HTMLLIElement[] = []; constructor(props: TabManagerProps) { @@ -58,51 +62,50 @@ export class TabManager extends React.Component -
    - { - this.props.recentTabs.map((tabId, index) => { - // TODO: Come up with a simple way to retrieve document - // name from store using documentId - const tabClassName = index === this.state.selectedIndex ? styles.selectedTab : ''; - return ( -
  • this.saveTabRef(x, index) } - key={ tabId } - tabIndex={ 0 }>{ tabId } -
  • - ); - }) - } -
-
- ) : null; + public render() { + return this.state.showing && !this.props.disabled ? ( +
+
    + {this.props.recentTabs.map((tabId, index) => { + // TODO: Come up with a simple way to retrieve document + // name from store using documentId + const tabClassName = + index === this.state.selectedIndex ? styles.selectedTab : ''; + return ( +
  • this.saveTabRef(x, index)} + key={tabId} + tabIndex={0} + > + {tabId} +
  • + ); + })} +
+
+ ) : null; } private saveTabRef = (element: HTMLLIElement, index: number) => { this.tabRefs[index] = element; - } + }; private onKeyDown = (e: KeyboardEvent) => { if (!this.props.recentTabs.length) { @@ -112,34 +115,34 @@ export class TabManager extends React.Component { switch (e.key) { case 'Control': if (this.state.showing) { - this.setState(({ controlIsPressed: false, showing: false })); - this.props.setActiveTab(this.props.recentTabs[this.state.selectedIndex]); + this.setState({ controlIsPressed: false, showing: false }); + this.props.setActiveTab( + this.props.recentTabs[this.state.selectedIndex] + ); } else { - this.setState(({ controlIsPressed: false })); + this.setState({ controlIsPressed: false }); } break; case 'Shift': - this.setState(({ shiftIsPressed: false })); + this.setState({ shiftIsPressed: false }); break; default: break; } - } + }; private moveIndexDown() { - return this.state.selectedIndex === this.props.recentTabs.length - 1 ? 0 : this.state.selectedIndex + 1; + return this.state.selectedIndex === this.props.recentTabs.length - 1 + ? 0 + : this.state.selectedIndex + 1; } private moveIndexUp() { - return this.state.selectedIndex === 0 ? this.props.recentTabs.length - 1 : this.state.selectedIndex - 1; + return this.state.selectedIndex === 0 + ? this.props.recentTabs.length - 1 + : this.state.selectedIndex - 1; } } diff --git a/packages/app/client/src/ui/dialogs/tabManager/tabManagerContainer.ts b/packages/app/client/src/ui/dialogs/tabManager/tabManagerContainer.ts index 23868831a..60639ad66 100644 --- a/packages/app/client/src/ui/dialogs/tabManager/tabManagerContainer.ts +++ b/packages/app/client/src/ui/dialogs/tabManager/tabManagerContainer.ts @@ -1,17 +1,57 @@ -import { RootState } from '../../../data/store'; +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { connect } from 'react-redux'; + import * as EditorActions from '../../../data/action/editorActions'; +import { RootState } from '../../../data/store'; + import { TabManager, TabManagerProps } from './tabManager'; -const mapStateToProps = (state: RootState, ownProps: { [propName: string]: any }): TabManagerProps => ({ +const mapStateToProps = ( + state: RootState, + ownProps: { [propName: string]: any } +): TabManagerProps => ({ recentTabs: state.editor.editors[state.editor.activeEditor].recentTabs, window, - ...ownProps + ...ownProps, }); const mapDispatchToProps = (dispatch): TabManagerProps => ({ setActiveTab: (tab: string) => { dispatch(EditorActions.setActiveTab(tab)); - } + }, }); -export const TabManagerContainer = connect(mapStateToProps, mapDispatchToProps)(TabManager) as any; +export const TabManagerContainer = connect( + mapStateToProps, + mapDispatchToProps +)(TabManager) as any; diff --git a/packages/app/client/src/ui/dialogs/updateAvailableDialog/updateAvailableDialog.spec.tsx b/packages/app/client/src/ui/dialogs/updateAvailableDialog/updateAvailableDialog.spec.tsx index a48033ce3..f3c00725e 100644 --- a/packages/app/client/src/ui/dialogs/updateAvailableDialog/updateAvailableDialog.spec.tsx +++ b/packages/app/client/src/ui/dialogs/updateAvailableDialog/updateAvailableDialog.spec.tsx @@ -31,19 +31,23 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { UpdateAvailableDialog } from './updateAvailableDialog'; -import { UpdateAvailableDialogContainer } from './updateAvailableDialogContainer'; import { createStore } from 'redux'; import { Provider } from 'react-redux'; -import { navBar } from '../../../data/reducer/navBar'; import * as React from 'react'; import { mount } from 'enzyme'; +import { navBar } from '../../../data/reducer/navBar'; + +import { UpdateAvailableDialogContainer } from './updateAvailableDialogContainer'; +import { UpdateAvailableDialog } from './updateAvailableDialog'; + let mockHideDialog; jest.mock('../service', () => ({ DialogService: { - get hideDialog() { return mockHideDialog; } - } + get hideDialog() { + return mockHideDialog; + }, + }, })); jest.mock('../../dialogs', () => ({})); @@ -55,8 +59,8 @@ describe('UpdateAvailableDialog', () => { beforeEach(() => { wrapper = mount( - - + + ); diff --git a/packages/app/client/src/ui/dialogs/updateAvailableDialog/updateAvailableDialog.tsx b/packages/app/client/src/ui/dialogs/updateAvailableDialog/updateAvailableDialog.tsx index 1f97afbcb..45bd6d15f 100644 --- a/packages/app/client/src/ui/dialogs/updateAvailableDialog/updateAvailableDialog.tsx +++ b/packages/app/client/src/ui/dialogs/updateAvailableDialog/updateAvailableDialog.tsx @@ -31,8 +31,14 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +import { + Checkbox, + DefaultButton, + Dialog, + DialogFooter, + PrimaryButton, +} from '@bfemulator/ui-react'; import * as React from 'react'; -import { Dialog, DialogFooter, PrimaryButton, DefaultButton, Checkbox } from '@bfemulator/ui-react'; export interface UpdateAvailableDialogProps { onCloseClick?: () => any; @@ -44,7 +50,10 @@ export interface UpdateAvailableDialogState { installAfterDownload: boolean; } -export class UpdateAvailableDialog extends React.Component { +export class UpdateAvailableDialog extends React.Component< + UpdateAvailableDialogProps, + UpdateAvailableDialogState +> { constructor(props: UpdateAvailableDialogProps) { super(props); @@ -57,14 +66,22 @@ export class UpdateAvailableDialog extends React.Component -

Bot Framework Emulator { version } is available. Would you like to download the new version?

- + +

+ Bot Framework Emulator {version} is available. Would you like to + download the new version? +

+ - - onDownloadClick(this.state.installAfterDownload) }/> + + onDownloadClick(this.state.installAfterDownload)} + />
); @@ -72,5 +89,5 @@ export class UpdateAvailableDialog extends React.Component { this.setState({ installAfterDownload: !this.state.installAfterDownload }); - } + }; } diff --git a/packages/app/client/src/ui/dialogs/updateAvailableDialog/updateAvailableDialogContainer.ts b/packages/app/client/src/ui/dialogs/updateAvailableDialog/updateAvailableDialogContainer.ts index ea725072d..4e8a63385 100644 --- a/packages/app/client/src/ui/dialogs/updateAvailableDialog/updateAvailableDialogContainer.ts +++ b/packages/app/client/src/ui/dialogs/updateAvailableDialog/updateAvailableDialogContainer.ts @@ -31,17 +31,25 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { UpdateAvailableDialog, UpdateAvailableDialogProps } from './updateAvailableDialog'; import { connect } from 'react-redux'; + import { DialogService } from '../service'; +import { + UpdateAvailableDialog, + UpdateAvailableDialogProps, +} from './updateAvailableDialog'; + function mapDispatchToProps(_dispatch: any): UpdateAvailableDialogProps { return { onCloseClick: () => DialogService.hideDialog(null), onDownloadClick: (installAfterDownload: boolean) => { DialogService.hideDialog({ installAfterDownload }); - } + }, }; } -export const UpdateAvailableDialogContainer = connect(null, mapDispatchToProps)(UpdateAvailableDialog); +export const UpdateAvailableDialogContainer = connect( + null, + mapDispatchToProps +)(UpdateAvailableDialog); diff --git a/packages/app/client/src/ui/dialogs/updateUnavailableDialog/updateUnavailableDialog.spec.tsx b/packages/app/client/src/ui/dialogs/updateUnavailableDialog/updateUnavailableDialog.spec.tsx index a29c22817..3e254acac 100644 --- a/packages/app/client/src/ui/dialogs/updateUnavailableDialog/updateUnavailableDialog.spec.tsx +++ b/packages/app/client/src/ui/dialogs/updateUnavailableDialog/updateUnavailableDialog.spec.tsx @@ -31,19 +31,23 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { UpdateUnavailableDialog } from './updateUnavailableDialog'; -import { UpdateUnavailableDialogContainer } from './updateUnavailableDialogContainer'; import { createStore } from 'redux'; import { Provider } from 'react-redux'; -import { navBar } from '../../../data/reducer/navBar'; import * as React from 'react'; import { mount } from 'enzyme'; +import { navBar } from '../../../data/reducer/navBar'; + +import { UpdateUnavailableDialogContainer } from './updateUnavailableDialogContainer'; +import { UpdateUnavailableDialog } from './updateUnavailableDialog'; + let mockHideDialog; jest.mock('../service', () => ({ DialogService: { - get hideDialog() { return mockHideDialog; } - } + get hideDialog() { + return mockHideDialog; + }, + }, })); jest.mock('../../dialogs', () => ({})); @@ -55,8 +59,8 @@ describe('UpdateUnavailableDialog', () => { beforeEach(() => { wrapper = mount( - - + + ); diff --git a/packages/app/client/src/ui/dialogs/updateUnavailableDialog/updateUnavailableDialog.tsx b/packages/app/client/src/ui/dialogs/updateUnavailableDialog/updateUnavailableDialog.tsx index c2f61ca18..6a0521959 100644 --- a/packages/app/client/src/ui/dialogs/updateUnavailableDialog/updateUnavailableDialog.tsx +++ b/packages/app/client/src/ui/dialogs/updateUnavailableDialog/updateUnavailableDialog.tsx @@ -31,14 +31,17 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import * as React from 'react'; import { Dialog, DialogFooter, PrimaryButton } from '@bfemulator/ui-react'; +import * as React from 'react'; export interface UpdateUnavailableDialogProps { onCloseClick?: () => any; } -export class UpdateUnavailableDialog extends React.Component { +export class UpdateUnavailableDialog extends React.Component< + UpdateUnavailableDialogProps, + {} +> { constructor(props: UpdateUnavailableDialogProps) { super(props); } @@ -47,10 +50,10 @@ export class UpdateUnavailableDialog extends React.Component +

There are no updates available for download.

- +
); diff --git a/packages/app/client/src/ui/dialogs/updateUnavailableDialog/updateUnavailableDialogContainer.ts b/packages/app/client/src/ui/dialogs/updateUnavailableDialog/updateUnavailableDialogContainer.ts index 1cb47fbe7..1f430b851 100644 --- a/packages/app/client/src/ui/dialogs/updateUnavailableDialog/updateUnavailableDialogContainer.ts +++ b/packages/app/client/src/ui/dialogs/updateUnavailableDialog/updateUnavailableDialogContainer.ts @@ -32,13 +32,21 @@ // import { connect } from 'react-redux'; + import { DialogService } from '../service'; -import { UpdateUnavailableDialog, UpdateUnavailableDialogProps } from './updateUnavailableDialog'; + +import { + UpdateUnavailableDialog, + UpdateUnavailableDialogProps, +} from './updateUnavailableDialog'; function mapDispatchToProps(_dispatch: any): UpdateUnavailableDialogProps { return { - onCloseClick: () => DialogService.hideDialog(null) + onCloseClick: () => DialogService.hideDialog(null), }; } -export const UpdateUnavailableDialogContainer = connect(null, mapDispatchToProps)(UpdateUnavailableDialog); +export const UpdateUnavailableDialogContainer = connect( + null, + mapDispatchToProps +)(UpdateUnavailableDialog); diff --git a/packages/app/client/src/ui/editor/appSettingsEditor/appSettingsEditor.tsx b/packages/app/client/src/ui/editor/appSettingsEditor/appSettingsEditor.tsx index 488765735..f399ebf8e 100644 --- a/packages/app/client/src/ui/editor/appSettingsEditor/appSettingsEditor.tsx +++ b/packages/app/client/src/ui/editor/appSettingsEditor/appSettingsEditor.tsx @@ -31,7 +31,11 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -import { FrameworkSettings, newNotification, SharedConstants } from '@bfemulator/app-shared'; +import { + FrameworkSettings, + newNotification, + SharedConstants, +} from '@bfemulator/app-shared'; import { Checkbox, Column, @@ -40,10 +44,11 @@ import { RowAlignment, RowJustification, SmallHeader, - TextField + TextField, } from '@bfemulator/ui-react'; import * as React from 'react'; import { ChangeEvent } from 'react'; + import * as Constants from '../../../constants'; import * as EditorActions from '../../../data/action/editorActions'; import { beginAdd } from '../../../data/action/notificationActions'; @@ -52,6 +57,7 @@ import { store } from '../../../data/store'; import { CommandServiceImpl } from '../../../platform/commands/commandServiceImpl'; import { debounce } from '../../../utils'; import { GenericDocument } from '../../layout'; + import * as styles from './appSettingsEditor.scss'; interface AppSettingsEditorProps { @@ -73,25 +79,32 @@ const defaultAppSettings: FrameworkSettings = { stateSizeLimit: 64, use10Tokens: false, useCodeValidation: false, - usePrereleases: false + usePrereleases: false, }; function shallowEqual(x: any, y: any) { return ( - Object.keys(x).length === Object.keys(y).length - && Object.keys(x).every(key => key in y && x[key] === y[key]) + Object.keys(x).length === Object.keys(y).length && + Object.keys(x).every(key => key in y && x[key] === y[key]) ); } -export class AppSettingsEditor extends React.Component { - setDirtyFlag = debounce((dirty) => store.dispatch(EditorActions.setDirtyFlag(this.props.documentId, dirty)), 300); +export class AppSettingsEditor extends React.Component< + AppSettingsEditorProps, + AppSettingsEditorState +> { + public setDirtyFlag = debounce( + dirty => + store.dispatch(EditorActions.setDirtyFlag(this.props.documentId, dirty)), + 300 + ); - constructor(props: AppSettingsEditorProps, context: any) { + public constructor(props: AppSettingsEditorProps, context: any) { super(props, context); this.state = { committed: { ...defaultAppSettings }, - uncommitted: { ...defaultAppSettings } + uncommitted: { ...defaultAppSettings }, }; } @@ -102,11 +115,11 @@ export class AppSettingsEditor extends React.Component { this.setState(() => ({ committed: settings, - uncommitted: settings + uncommitted: settings, })); }) .catch(err => { - const errMsg = `Error while loading emulator settings: ${ err }`; + const errMsg = `Error while loading emulator settings: ${err}`; const notification = newNotification(errMsg); store.dispatch(beginAdd(notification)); }); @@ -118,7 +131,7 @@ export class AppSettingsEditor extends React.Component + - + Service -

ngrok is network tunneling software. The Bot Framework - Emulator works with ngrok to communicate with bots hosted remotely. Read the wiki - page to learn more about using ngrok and to download it.

- +

+ + ngrok + {' '} + is network tunneling software. The Bot Framework Emulator works + with ngrok to communicate with bots hosted remotely. Read the{' '} + + wiki page + {' '} + to learn more about using ngrok and to download it. +

+ - + label={'Path to ngrok'} + /> + - + label="Bypass ngrok for local addresses" + /> + + label="localhost override" + /> - + + onChange={this.onInputChange} + label="Locale" + />
- + Auth + label="Use version 1.0 authentication tokens" + /> Sign-in + label="Use a sign-in verification code for OAuthCards" + /> Application Updates - - + +
- - - + + +
); } private onChangeAutoInstallUpdates = (): void => { - this.setUncommittedState({ autoUpdate: !this.state.uncommitted.autoUpdate }); - } + this.setUncommittedState({ + autoUpdate: !this.state.uncommitted.autoUpdate, + }); + }; private onChangeUsePrereleases = (): void => { - this.setUncommittedState({ usePrereleases: !this.state.uncommitted.usePrereleases }); - } + this.setUncommittedState({ + usePrereleases: !this.state.uncommitted.usePrereleases, + }); + }; private setUncommittedState(patch: any) { this.setState(state => { const nextUncommitted = { ...state.uncommitted, - ...patch + ...patch, }; const clean = shallowEqual(state.uncommitted, state.committed); @@ -238,17 +294,20 @@ export class AppSettingsEditor extends React.Component this.setUncommittedState({ ngrokPath })) .catch(err => { - const errMsg = `Error while browsing for ngrok: ${ err }`; + const errMsg = `Error while browsing for ngrok: ${err}`; const notification = newNotification(errMsg); store.dispatch(beginAdd(notification)); }); - } + }; private onClickSave = (): void => { const { Commands } = SharedConstants; @@ -262,38 +321,49 @@ export class AppSettingsEditor extends React.Component this.commit(settings)) .catch(err => { - const errMsg = `Error while saving emulator settings: ${ err }`; + const errMsg = `Error while saving emulator settings: ${err}`; const notification = newNotification(errMsg); store.dispatch(beginAdd(notification)); }); - } + }; private onChangeAuthTokenVersion = (): void => { - this.setUncommittedState({ use10Tokens: !this.state.uncommitted.use10Tokens }); - } + this.setUncommittedState({ + use10Tokens: !this.state.uncommitted.use10Tokens, + }); + }; private onChangeUseValidationToken = (): void => { - this.setUncommittedState({ useCodeValidation: !this.state.uncommitted.useCodeValidation }); - } + this.setUncommittedState({ + useCodeValidation: !this.state.uncommitted.useCodeValidation, + }); + }; private onChangeNgrokBypass = (): void => { - this.setUncommittedState({ bypassNgrokLocalhost: !this.state.uncommitted.bypassNgrokLocalhost }); - } + this.setUncommittedState({ + bypassNgrokLocalhost: !this.state.uncommitted.bypassNgrokLocalhost, + }); + }; private onInputChange = (event: ChangeEvent): void => { const { value } = event.target; const { prop } = event.target.dataset; this.setUncommittedState({ [prop]: value }); - } + }; private onClickDiscard = (): void => { const { DOCUMENT_ID_APP_SETTINGS } = Constants; - store.dispatch(EditorActions.close(getTabGroupForDocument(this.props.documentId), DOCUMENT_ID_APP_SETTINGS)); - } + store.dispatch( + EditorActions.close( + getTabGroupForDocument(this.props.documentId), + DOCUMENT_ID_APP_SETTINGS + ) + ); + }; } diff --git a/packages/app/client/src/ui/editor/editor.tsx b/packages/app/client/src/ui/editor/editor.tsx index 0c2e65603..32ef9776b 100644 --- a/packages/app/client/src/ui/editor/editor.tsx +++ b/packages/app/client/src/ui/editor/editor.tsx @@ -34,9 +34,14 @@ import * as React from 'react'; import * as Constants from '../../constants'; -import { AppSettingsEditor, EmulatorContainer, WelcomePageContainer } from './index'; import { Document } from '../../data/reducer/editor'; +import { + AppSettingsEditor, + EmulatorContainer, + WelcomePageContainer, +} from './index'; + interface EditorFactoryProps { document?: Document; } @@ -46,28 +51,39 @@ export class EditorFactory extends React.Component { super(props); } - render() { + public render() { const { document } = this.props; const { contentType } = document; switch (contentType) { case Constants.CONTENT_TYPE_LIVE_CHAT: - return (); + return ( + + ); case Constants.CONTENT_TYPE_TRANSCRIPT: - return (); + return ( + + ); case Constants.CONTENT_TYPE_APP_SETTINGS: - return (); + return ( + + ); case Constants.CONTENT_TYPE_WELCOME_PAGE: - return (); + return ; default: return false; diff --git a/packages/app/client/src/ui/editor/emulator/chatPanel/chatPanel.spec.tsx b/packages/app/client/src/ui/editor/emulator/chatPanel/chatPanel.spec.tsx index 4c6601f04..b204f5944 100644 --- a/packages/app/client/src/ui/editor/emulator/chatPanel/chatPanel.spec.tsx +++ b/packages/app/client/src/ui/editor/emulator/chatPanel/chatPanel.spec.tsx @@ -41,11 +41,11 @@ import ChatPanel from './chatPanel'; jest.mock('./chatPanel.scss', () => ({})); jest.mock('../parts/chat/chat.scss', () => ({})); jest.mock('../../../dialogs', () => ({ - AzureLoginPromptDialogContainer: () => ({ }), - AzureLoginSuccessDialogContainer: () => ({ }), - BotCreationDialog: () => ({ }), + AzureLoginPromptDialogContainer: () => ({}), + AzureLoginSuccessDialogContainer: () => ({}), + BotCreationDialog: () => ({}), DialogService: { showDialog: () => Promise.resolve(true) }, - SecretPromptDialog: () => ({ }) + SecretPromptDialog: () => ({}), })); const document = { @@ -61,15 +61,16 @@ const document = { this._listeners.push(cb); return { - unsubscribe: () => this._listeners = this._listeners.filter(l => l !== cb) + unsubscribe: () => + (this._listeners = this._listeners.filter(l => l !== cb)), }; - } - } + }, + }, }; function render() { const props = { - document + document, }; // @ts-ignore - mocking out BehaviorSubject @@ -121,8 +122,8 @@ describe('', () => { expect(component.state('selectedActivity')).toEqual({ new: 'activity', - showInInspector: true - }); + showInInspector: true, + }); }); }); }); diff --git a/packages/app/client/src/ui/editor/emulator/chatPanel/chatPanel.tsx b/packages/app/client/src/ui/editor/emulator/chatPanel/chatPanel.tsx index 2840f3054..9ffcc1e0e 100644 --- a/packages/app/client/src/ui/editor/emulator/chatPanel/chatPanel.tsx +++ b/packages/app/client/src/ui/editor/emulator/chatPanel/chatPanel.tsx @@ -36,6 +36,7 @@ import { Activity } from '@bfemulator/sdk-shared'; import { ChatContainer } from '../parts/chat/chatContainer'; import { EmulatorMode } from '../emulator'; + import * as styles from './chatPanel.scss'; interface ChatPanelProps { @@ -52,9 +53,12 @@ interface ChatPanelState { selectedActivity: any | null; } -export default class ChatPanel extends React.Component { +export default class ChatPanel extends React.Component< + ChatPanelProps, + ChatPanelState +> { state = { - selectedActivity: null + selectedActivity: null, }; selectedActivitySub: Subscription; @@ -75,30 +79,34 @@ export default class ChatPanel extends React.Component) => { + updateSelectedActivityObservable = ( + observable: BehaviorSubject + ) => { return (activity: Activity) => { if (observable) { observable.next({ ...activity, - showInInspector: true + showInInspector: true, }); } }; - } + }; render() { const { document, mode, onStartConversation } = this.props; const { endpointUrl } = document || { endpointUrl: '' }; return ( -
-
{ endpointUrl }
+
+
{endpointUrl}
); diff --git a/packages/app/client/src/ui/editor/emulator/emulator.tsx b/packages/app/client/src/ui/editor/emulator/emulator.tsx index 25800d9c5..d26639439 100644 --- a/packages/app/client/src/ui/editor/emulator/emulator.tsx +++ b/packages/app/client/src/ui/editor/emulator/emulator.tsx @@ -39,19 +39,25 @@ import { IEndpointService } from 'botframework-config/lib/schema'; import * as React from 'react'; import { connect } from 'react-redux'; import { BehaviorSubject } from 'rxjs'; +import { + newNotification, + Notification, + SharedConstants, +} from '@bfemulator/app-shared'; + import * as ChatActions from '../../../data/action/chatActions'; import { updateDocument } from '../../../data/action/editorActions'; import * as PresentationActions from '../../../data/action/presentationActions'; import { Document } from '../../../data/reducer/editor'; import { RootState } from '../../../data/store'; import { CommandServiceImpl } from '../../../platform/commands/commandServiceImpl'; +import { debounce } from '../../../utils'; +import { beginAdd } from '../../../data/action/notificationActions'; + import ChatPanel from './chatPanel/chatPanel'; import LogPanel from './logPanel/logPanel'; import PlaybackBar from './playbackBar/playbackBar'; -import { debounce } from '../../../utils'; -import { newNotification, Notification, SharedConstants } from '@bfemulator/app-shared'; import * as styles from './emulator.scss'; -import { beginAdd } from '../../../data/action/notificationActions'; import { InspectorContainer } from './parts'; import { ToolBar } from './toolbar/toolbar'; @@ -59,7 +65,7 @@ const { encode } = base64Url; const RestartConversationOptions = { NewUserId: 'Restart with new user ID', - SameUserId: 'Restart with same user ID' + SameUserId: 'Restart with same user ID', }; export type EmulatorMode = 'transcript' | 'livechat'; @@ -80,22 +86,25 @@ interface EmulatorProps { presentationModeEnabled?: boolean; setInspectorObjects?: (documentId: string, objects: any) => void; updateChat?: (documentId: string, updatedValues: any) => void; - updateDocument?: (documentId: string, updatedValues: Partial) => void; + updateDocument?: ( + documentId: string, + updatedValues: Partial + ) => void; url?: string; } class EmulatorComponent extends React.Component { - private readonly onVerticalSizeChange = debounce((sizes) => { + private readonly onVerticalSizeChange = debounce(sizes => { this.props.document.ui = { ...this.props.document.ui, - verticalSplitter: sizes + verticalSplitter: sizes, }; }, 500); - private readonly onHorizontalSizeChange = debounce((sizes) => { + private readonly onHorizontalSizeChange = debounce(sizes => { this.props.document.ui = { ...this.props.document.ui, - horizontalSplitter: sizes + horizontalSplitter: sizes, }; }, 500); @@ -104,8 +113,10 @@ class EmulatorComponent extends React.Component { } shouldStartNewConversation(props: EmulatorProps = this.props): boolean { - return !props.document.directLine || - (props.document.conversationId !== props.document.directLine.conversationId); + return ( + !props.document.directLine || + props.document.conversationId !== props.document.directLine.conversationId + ); } componentWillMount() { @@ -124,15 +135,19 @@ class EmulatorComponent extends React.Component { const { document = {} } = props; const { document: nextDocument = {} } = nextProps; - const documentOrUserIdChanged = (!nextDocument.directLine && document.documentId !== nextDocument.documentId) - || (document.userId !== nextDocument.userId); + const documentOrUserIdChanged = + (!nextDocument.directLine && + document.documentId !== nextDocument.documentId) || + document.userId !== nextDocument.userId; if (documentOrUserIdChanged) { startNewConversation(nextProps).catch(); } - const switchedDocuments = props.activeDocumentId !== nextProps.activeDocumentId; - const switchedToThisDocument = (nextProps.activeDocumentId === props.documentId); + const switchedDocuments = + props.activeDocumentId !== nextProps.activeDocumentId; + const switchedToThisDocument = + nextProps.activeDocumentId === props.documentId; if (switchedDocuments) { if (switchedToThisDocument) { @@ -144,24 +159,25 @@ class EmulatorComponent extends React.Component { } } - startNewConversation = async (props: EmulatorProps = this.props): Promise => { + startNewConversation = async ( + props: EmulatorProps = this.props + ): Promise => { if (props.document.subscription) { props.document.subscription.unsubscribe(); } const selectedActivity$ = new BehaviorSubject({}); - const subscription = selectedActivity$.subscribe((activity) => { - + const subscription = selectedActivity$.subscribe(activity => { if (activity && activity.showInInspector) { this.props.setInspectorObjects(props.document.documentId, activity); } }); // TODO: Don't append mode - const conversationId = `${ uniqueId() }|${ props.mode }`; + const conversationId = `${uniqueId()}|${props.mode}`; const options = { conversationId, conversationMode: props.mode, - endpointId: props.endpointId + endpointId: props.endpointId, }; if (props.document.directLine) { @@ -177,7 +193,11 @@ class EmulatorComponent extends React.Component { conversationId ); - if (props.document && props.document.inMemory && props.document.activities) { + if ( + props.document && + props.document.inMemory && + props.document.activities + ) { try { // transcript was deep linked via protocol or is generated in-memory via chatdown, // and should just be fed its own activities attached to the document @@ -189,12 +209,17 @@ class EmulatorComponent extends React.Component { props.document.activities ); } catch (err) { - throw new Error(`Error while feeding deep-linked transcript to conversation: ${err}`); + throw new Error( + `Error while feeding deep-linked transcript to conversation: ${err}` + ); } } else { try { // the transcript is on disk, so its activities need to be read on the main side and fed in - const fileInfo: { fileName: string, filePath: string } = await CommandServiceImpl.remoteCall( + const fileInfo: { + fileName: string; + filePath: string; + } = await CommandServiceImpl.remoteCall( SharedConstants.Commands.Emulator.FeedTranscriptFromDisk, conversation.conversationId, props.document.botId, @@ -204,7 +229,9 @@ class EmulatorComponent extends React.Component { this.props.updateDocument(this.props.documentId, fileInfo); } catch (err) { - throw new Error(`Error while feeding transcript on disk to conversation: ${err}`); + throw new Error( + `Error while feeding transcript on disk to conversation: ${err}` + ); } } } catch (err) { @@ -213,17 +240,22 @@ class EmulatorComponent extends React.Component { this.props.createErrorNotification(notification); } } - } - - initConversation(props: EmulatorProps, options: any, selectedActivity$: any, subscription: any): void { + }; + + initConversation( + props: EmulatorProps, + options: any, + selectedActivity$: any, + subscription: any + ): void { const encodedOptions = encode(JSON.stringify(options)); // TODO: We need to use encoded token because we need to pass both endpoint ID and conversation ID // We should think about a better model to pass conversation ID from Web Chat to emulator core const directLine = createDirectLine({ secret: encodedOptions, - domain: `${ this.props.url }/v3/directline`, - webSocket: false + domain: `${this.props.url}/v3/directline`, + webSocket: false, }); this.props.newConversation(props.documentId, { @@ -231,30 +263,36 @@ class EmulatorComponent extends React.Component { // webChatStore, directLine, selectedActivity$, - subscription + subscription, }); } render(): JSX.Element { - return this.props.presentationModeEnabled ? this.renderPresentationView() : this.renderDefaultView(); + return this.props.presentationModeEnabled + ? this.renderPresentationView() + : this.renderDefaultView(); } renderPresentationView(): JSX.Element { const transcriptMode = this.props.mode === 'transcript'; const chatPanelChild = transcriptMode ? ( -
- -
) : null; +
+ +
+ ) : null; return ( -
-
- - { chatPanelChild } +
+
+ + {chatPanelChild}
this.onPresentationClick(false) } + className={styles.closePresentationIcon} + onClick={() => this.onPresentationClick(false)} />
); @@ -264,43 +302,52 @@ class EmulatorComponent extends React.Component { const { NewUserId, SameUserId } = RestartConversationOptions; return ( -
- { - this.props.mode === 'livechat' && -
+
+ {this.props.mode === 'livechat' && ( +
+ buttonClass={styles.restartIcon} + options={[NewUserId, SameUserId]} + onClick={this.onStartOverClick} + />
- } -
- -
- + )} +
+ +
+
-
- - - +
+ + +
@@ -311,32 +358,38 @@ class EmulatorComponent extends React.Component { private getVerticalSplitterSizes = (): { [0]: string } => { return { - 0: `${this.props.document.ui.verticalSplitter[0].percentage}` + 0: `${this.props.document.ui.verticalSplitter[0].percentage}`, }; - } + }; private getHorizontalSplitterSizes = (): { [0]: string } => { return { - 0: `${this.props.document.ui.horizontalSplitter[0].percentage}` + 0: `${this.props.document.ui.horizontalSplitter[0].percentage}`, }; - } + }; private onPresentationClick = (enabled: boolean): void => { this.props.enablePresentationMode(enabled); - } + }; - private onStartOverClick = async (option: string = RestartConversationOptions.NewUserId): Promise => { + private onStartOverClick = async ( + option: string = RestartConversationOptions.NewUserId + ): Promise => { const { NewUserId, SameUserId } = RestartConversationOptions; this.props.clearLog(this.props.document.documentId); this.props.setInspectorObjects(this.props.document.documentId, []); switch (option) { - case NewUserId: + case NewUserId: { const newUserId = uniqueIdv4(); // set new user as current on emulator facilities side - await CommandServiceImpl.remoteCall(SharedConstants.Commands.Emulator.SetCurrentUser, newUserId); + await CommandServiceImpl.remoteCall( + SharedConstants.Commands.Emulator.SetCurrentUser, + newUserId + ); this.props.updateChat(this.props.documentId, { userId: newUserId }); break; + } case SameUserId: this.startNewConversation(); @@ -345,7 +398,7 @@ class EmulatorComponent extends React.Component { default: break; } - } + }; private onExportClick = (): void => { if (this.props.document.directLine) { @@ -354,36 +407,53 @@ class EmulatorComponent extends React.Component { this.props.document.directLine.conversationId ); } - } + }; - private readonly keyboardEventListener: EventListener = (event: KeyboardEvent): void => { + private readonly keyboardEventListener: EventListener = ( + event: KeyboardEvent + ): void => { // Meta corresponds to 'Command' on Mac - const ctrlOrCmdPressed = event.getModifierState('Control') || event.getModifierState('Meta'); + const ctrlOrCmdPressed = + event.getModifierState('Control') || event.getModifierState('Meta'); const shiftPressed = ctrlOrCmdPressed && event.getModifierState('Shift'); const key = event.key.toLowerCase(); if (ctrlOrCmdPressed && shiftPressed && key === 'r') { this.onStartOverClick(); } - } + }; } -const mapStateToProps = (state: RootState, { documentId }: { documentId: string }): EmulatorProps => ({ - activeDocumentId: state.editor.editors[state.editor.activeEditor].activeDocumentId, +const mapStateToProps = ( + state: RootState, + { documentId }: { documentId: string } +): EmulatorProps => ({ + activeDocumentId: + state.editor.editors[state.editor.activeEditor].activeDocumentId, conversationId: state.chat.chats[documentId].conversationId, document: state.chat.chats[documentId], endpointId: state.chat.chats[documentId].endpointId, - presentationModeEnabled: state.presentation.enabled + presentationModeEnabled: state.presentation.enabled, }); const mapDispatchToProps = (dispatch): EmulatorProps => ({ - enablePresentationMode: enable => enable ? dispatch(PresentationActions.enable()) - : dispatch(PresentationActions.disable()), - setInspectorObjects: (documentId, objects) => dispatch(ChatActions.setInspectorObjects(documentId, objects)), + enablePresentationMode: enable => + enable + ? dispatch(PresentationActions.enable()) + : dispatch(PresentationActions.disable()), + setInspectorObjects: (documentId, objects) => + dispatch(ChatActions.setInspectorObjects(documentId, objects)), clearLog: documentId => dispatch(ChatActions.clearLog(documentId)), - newConversation: (documentId, options) => dispatch(ChatActions.newConversation(documentId, options)), - updateChat: (documentId: string, updatedValues: any) => dispatch(ChatActions.updateChat(documentId, updatedValues)), - updateDocument: (documentId, updatedValues: Partial) => dispatch(updateDocument(documentId, updatedValues)), - createErrorNotification: (notification: Notification) => dispatch(beginAdd(notification)) + newConversation: (documentId, options) => + dispatch(ChatActions.newConversation(documentId, options)), + updateChat: (documentId: string, updatedValues: any) => + dispatch(ChatActions.updateChat(documentId, updatedValues)), + updateDocument: (documentId, updatedValues: Partial) => + dispatch(updateDocument(documentId, updatedValues)), + createErrorNotification: (notification: Notification) => + dispatch(beginAdd(notification)), }); -export const Emulator = connect(mapStateToProps, mapDispatchToProps)(EmulatorComponent as any) as any; +export const Emulator = connect( + mapStateToProps, + mapDispatchToProps +)(EmulatorComponent as any) as any; diff --git a/packages/app/client/src/ui/editor/emulator/emulatorContainer.ts b/packages/app/client/src/ui/editor/emulator/emulatorContainer.ts index efa9b26a5..8cfb48262 100644 --- a/packages/app/client/src/ui/editor/emulator/emulatorContainer.ts +++ b/packages/app/client/src/ui/editor/emulator/emulatorContainer.ts @@ -1,10 +1,44 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import { connect } from 'react-redux'; + import { RootState } from '../../../data/store'; + import { Emulator } from './emulator'; const mapStateToProps = (state: RootState, ownProps: any[]) => ({ url: state.clientAwareSettings.serverUrl, - ...ownProps + ...ownProps, }); export const EmulatorContainer = connect(mapStateToProps)(Emulator); diff --git a/packages/app/client/src/ui/editor/emulator/logPanel/logPanel.tsx b/packages/app/client/src/ui/editor/emulator/logPanel/logPanel.tsx index c5d40ec29..524676b56 100644 --- a/packages/app/client/src/ui/editor/emulator/logPanel/logPanel.tsx +++ b/packages/app/client/src/ui/editor/emulator/logPanel/logPanel.tsx @@ -33,8 +33,9 @@ import * as React from 'react'; -import { Log } from '../parts/log'; import Panel, { PanelContent } from '../../panel/panel'; +import { Log } from '../parts/log'; + import * as styles from './logPanel.scss'; interface LogPanelProps { @@ -42,12 +43,12 @@ interface LogPanelProps { } export default class LogPanel extends React.Component { - render() { + public render() { return ( -
+
- +
diff --git a/packages/app/client/src/ui/editor/emulator/parts/chat/activityWrapper.spec.tsx b/packages/app/client/src/ui/editor/emulator/parts/chat/activityWrapper.spec.tsx index 3837298e8..e69b52ba4 100644 --- a/packages/app/client/src/ui/editor/emulator/parts/chat/activityWrapper.spec.tsx +++ b/packages/app/client/src/ui/editor/emulator/parts/chat/activityWrapper.spec.tsx @@ -39,7 +39,7 @@ import ActivityWrapper from './activityWrapper'; jest.mock('./chat.scss', () => ({ chatActivity: 'chat-activity', - selectedActivity: 'selected-activity' + selectedActivity: 'selected-activity', })); const defaultProps = { @@ -57,11 +57,7 @@ function render( ...overrides, }; - return mount( - - { children } - - ); + return mount({children}); } describe('', () => { diff --git a/packages/app/client/src/ui/editor/emulator/parts/chat/activityWrapper.tsx b/packages/app/client/src/ui/editor/emulator/parts/chat/activityWrapper.tsx index ac8892567..3a09090d5 100644 --- a/packages/app/client/src/ui/editor/emulator/parts/chat/activityWrapper.tsx +++ b/packages/app/client/src/ui/editor/emulator/parts/chat/activityWrapper.tsx @@ -67,33 +67,35 @@ class ActivityWrapper extends Component { let classes = styles.chatActivity; if (isSelected) { - classes = `${ classes } ${ styles.selectedActivity }`; + classes = `${classes} ${styles.selectedActivity}`; } return (
- { children } + {children}
); } - private setSelectedActivity = (activity: Activity) => (e: React.SyntheticEvent) => { + private setSelectedActivity = (activity: Activity) => ( + e: React.SyntheticEvent + ) => { if (shouldSelectActivity(e)) { this.props.onClick(activity); } - } + }; private onKeyDown = (activity: Activity) => (e: React.KeyboardEvent) => { if (shouldSelectActivity(e) && [' ', 'Enter'].includes(e.key)) { this.props.onClick(activity); } - } + }; } export default ActivityWrapper; diff --git a/packages/app/client/src/ui/editor/emulator/parts/chat/chat.spec.ts b/packages/app/client/src/ui/editor/emulator/parts/chat/chat.spec.ts new file mode 100644 index 000000000..d1b0ad1fb --- /dev/null +++ b/packages/app/client/src/ui/editor/emulator/parts/chat/chat.spec.ts @@ -0,0 +1,66 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +import { CommandServiceImpl } from '../../../../../platform/commands/commandServiceImpl'; + +import { getSpeechToken } from './chat'; + +jest.mock('../../../../dialogs', () => ({ + AzureLoginPromptDialogContainer: () => ({}), + AzureLoginSuccessDialogContainer: () => ({}), + BotCreationDialog: () => ({}), + DialogService: { showDialog: () => Promise.resolve(true) }, + SecretPromptDialog: () => ({}), +})); + +jest.mock('./chat.scss', () => ({})); + +describe('ChatPanel tests', () => { + it('should get speech token by calling remotely', async () => { + const mockRemoteCall = jest.fn().mockResolvedValue('1A2B3C4'); + (CommandServiceImpl as any).remoteCall = mockRemoteCall; + + const speechToken = getSpeechToken( + { + appId: 'APP_ID', + appPassword: 'APP_PASSWORD', + endpoint: 'http://example.com/', + id: '123', + name: 'bot endpoint', + }, + true + ); + + expect(speechToken).resolves.toBe('1A2B3C4'); + expect(mockRemoteCall).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/app/client/src/ui/editor/emulator/parts/chat/chat.spec.tsx b/packages/app/client/src/ui/editor/emulator/parts/chat/chat.spec.tsx index 4b860cf54..fd5036458 100644 --- a/packages/app/client/src/ui/editor/emulator/parts/chat/chat.spec.tsx +++ b/packages/app/client/src/ui/editor/emulator/parts/chat/chat.spec.tsx @@ -34,26 +34,27 @@ import * as React from 'react'; import { mount, shallow, ShallowWrapper } from 'enzyme'; import ReactWebChat from 'botframework-webchat'; + import { CommandServiceImpl } from '../../../../../platform/commands/commandServiceImpl'; -import { Chat, getSpeechToken } from './chat'; +import { Chat, getSpeechToken } from './chat'; import webChatStyleOptions from './webChatTheme'; jest.mock('../../../../dialogs', () => ({ - AzureLoginPromptDialogContainer: () => ({ }), - AzureLoginSuccessDialogContainer: () => ({ }), - BotCreationDialog: () => ({ }), + AzureLoginPromptDialogContainer: () => ({}), + AzureLoginSuccessDialogContainer: () => ({}), + BotCreationDialog: () => ({}), DialogService: { showDialog: () => Promise.resolve(true) }, - SecretPromptDialog: () => ({ }) + SecretPromptDialog: () => ({}), })); jest.mock('./chat.scss', () => ({})); const defaultDocument = { directLine: { - token: 'direct line token' + token: 'direct line token', }, - botId: '456' + botId: '456', }; function render(overrides: any = {}): ShallowWrapper { @@ -66,7 +67,7 @@ function render(overrides: any = {}): ShallowWrapper { locale: 'en-US', selectedActivity: null, updateSelectedActivity: jest.fn(), - ...overrides + ...overrides, }; return shallow(); @@ -91,7 +92,7 @@ describe('', () => { directLine: defaultDocument.directLine, locale: 'en-US', styleOptions: webChatStyleOptions, - userId: '123' + userId: '123', }); }); }); @@ -110,7 +111,7 @@ describe('', () => { expect(activityWrapper.props()).toMatchObject({ activity: card.activity, onClick: updateSelectedActivity, - isSelected: false + isSelected: false, }); expect(activityWrapper.text()).toEqual('a child node'); }); @@ -122,8 +123,8 @@ describe('', () => { const component = render({ endpoint: { appId: 'some-app-id', - appPassword: 'some-password' - } + appPassword: 'some-password', + }, }); expect(component.text()).toEqual('Connecting...'); @@ -135,12 +136,14 @@ describe('', () => { const component = render({ endpoint: { appId: 'some-app-id', - appPassword: 'some-password' - } + appPassword: 'some-password', + }, }); - await new Promise((resolve) => setTimeout(resolve, 250)); - expect(component.find(ReactWebChat).prop('webSpeechPonyfillFactory')).toBeTruthy(); + await new Promise(resolve => setTimeout(resolve, 250)); + expect( + component.find(ReactWebChat).prop('webSpeechPonyfillFactory') + ).toBeTruthy(); }); }); }); @@ -151,13 +154,16 @@ describe('getSpeechToken', () => { (CommandServiceImpl as any).remoteCall = mockRemoteCall; - const speechToken = getSpeechToken({ - appId: 'APP_ID', - appPassword: 'APP_PASSWORD', - endpoint: 'http://example.com/', - id: '123', - name: 'bot endpoint' - }, true); + const speechToken = getSpeechToken( + { + appId: 'APP_ID', + appPassword: 'APP_PASSWORD', + endpoint: 'http://example.com/', + id: '123', + name: 'bot endpoint', + }, + true + ); expect(speechToken).resolves.toBe('1A2B3C4'); expect(mockRemoteCall).toHaveBeenCalledTimes(1); diff --git a/packages/app/client/src/ui/editor/emulator/parts/chat/chat.tsx b/packages/app/client/src/ui/editor/emulator/parts/chat/chat.tsx index c15bdd5c8..c7dc345c0 100644 --- a/packages/app/client/src/ui/editor/emulator/parts/chat/chat.tsx +++ b/packages/app/client/src/ui/editor/emulator/parts/chat/chat.tsx @@ -34,10 +34,14 @@ import * as React from 'react'; import { Component } from 'react'; import { Activity } from '@bfemulator/sdk-shared'; import { IEndpointService } from 'botframework-config/lib/schema'; -import ReactWebChat, { createCognitiveServicesBingSpeechPonyfillFactory } from 'botframework-webchat'; +import ReactWebChat, { + createCognitiveServicesBingSpeechPonyfillFactory, +} from 'botframework-webchat'; + import { CommandServiceImpl } from '../../../../../platform/commands/commandServiceImpl'; -import * as styles from './chat.scss'; import { EmulatorMode } from '../../emulator'; + +import * as styles from './chat.scss'; import ActivityWrapper from './activityWrapper'; import webChatStyleOptions from './webChatTheme'; @@ -57,8 +61,13 @@ interface ChatState { webSpeechPonyfillFactory: any; } -function isCardSelected(selectedActivity: Activity | null, activity: Activity): boolean { - return Boolean(selectedActivity && activity.id && selectedActivity.id === activity.id); +function isCardSelected( + selectedActivity: Activity | null, + activity: Activity +): boolean { + return Boolean( + selectedActivity && activity.id && selectedActivity.id === activity.id + ); } function isSpeechEnabled(endpoint: IEndpointService | null): boolean { @@ -70,15 +79,17 @@ export async function getSpeechToken( refresh: boolean = false ): Promise { if (!endpoint) { + // eslint-disable-next-line no-console console.warn('No endpoint for this chat, cannot fetch speech token.'); return; } - let command = refresh ? 'speech-token:refresh' : 'speech-token:get'; + const command = refresh ? 'speech-token:refresh' : 'speech-token:get'; try { return await CommandServiceImpl.remoteCall(command, endpoint.id); } catch (err) { + // eslint-disable-next-line no-console console.error(err); } } @@ -89,7 +100,7 @@ export class Chat extends Component { this.state = { waitForSpeechToken: isSpeechEnabled(props.endpoint), - webSpeechPonyfillFactory: null + webSpeechPonyfillFactory: null, }; } @@ -98,9 +109,11 @@ export class Chat extends Component { const speechToken = await getSpeechToken(this.props.endpoint); if (speechToken) { - const webSpeechPonyfillFactory = await createCognitiveServicesBingSpeechPonyfillFactory({ - authorizationToken: speechToken - }); + const webSpeechPonyfillFactory = await createCognitiveServicesBingSpeechPonyfillFactory( + { + authorizationToken: speechToken, + } + ); this.setState({ webSpeechPonyfillFactory, waitForSpeechToken: false }); } else { @@ -113,51 +126,43 @@ export class Chat extends Component { const { currentUserId, document, locale, mode } = this.props; if (this.state.waitForSpeechToken) { - return ( -
- Connecting... -
- ); + return
Connecting...
; } if (document.directLine) { const bot = { id: document.botId || 'bot', - name: 'Bot' + name: 'Bot', }; const isDisabled = mode === 'transcript'; return ( -
+
); } - return ( -
- Not Connected -
- ); + return
Not Connected
; } private createActivityMiddleware = () => next => card => children => ( - { next(card)(children) } + {next(card)(children)} - ) + ); } diff --git a/packages/app/client/src/ui/editor/emulator/parts/chat/chatContainer.ts b/packages/app/client/src/ui/editor/emulator/parts/chat/chatContainer.ts index de57891ad..c00254767 100644 --- a/packages/app/client/src/ui/editor/emulator/parts/chat/chatContainer.ts +++ b/packages/app/client/src/ui/editor/emulator/parts/chat/chatContainer.ts @@ -1,13 +1,51 @@ -import { Chat } from './chat'; +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +import { IEndpointService } from 'botframework-config/lib/schema'; import { connect } from 'react-redux'; + import { RootState } from '../../../../../data/store'; -import { IEndpointService } from 'botframework-config/lib/schema'; + +import { Chat } from './chat'; const mapStateToProps = (state: RootState, { document }) => ({ currentUserId: state.clientAwareSettings.users.currentUserId, locale: state.clientAwareSettings.locale || 'en-us', - endpoint: ((state.bot.activeBot && state.bot.activeBot.services) || []) - .find(s => s.id === document.endpointId) as IEndpointService + endpoint: ((state.bot.activeBot && state.bot.activeBot.services) || []).find( + s => s.id === document.endpointId + ) as IEndpointService, }); -export const ChatContainer = connect(mapStateToProps, null)(Chat); +export const ChatContainer = connect( + mapStateToProps, + null +)(Chat); diff --git a/packages/app/client/src/ui/editor/emulator/parts/chat/webChatTheme.ts b/packages/app/client/src/ui/editor/emulator/parts/chat/webChatTheme.ts index efb7d4c40..341749ea5 100644 --- a/packages/app/client/src/ui/editor/emulator/parts/chat/webChatTheme.ts +++ b/packages/app/client/src/ui/editor/emulator/parts/chat/webChatTheme.ts @@ -1,3 +1,35 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// export default { backgroundColor: 'var(--webchat-bg)', @@ -21,9 +53,13 @@ export default { suggestedActionTextColor: 'var(--webchat-sa-text)', transcriptOverlayButtonBackground: 'var(--webchat-transcript-overlay-bg)', - transcriptOverlayButtonBackgroundOnFocus: 'var(--webchat-transcript-overlay-bg-focus)', - transcriptOverlayButtonBackgroundOnHover: 'var(--webchat-transcript-overlay-bg-focus)', + transcriptOverlayButtonBackgroundOnFocus: + 'var(--webchat-transcript-overlay-bg-focus)', + transcriptOverlayButtonBackgroundOnHover: + 'var(--webchat-transcript-overlay-bg-focus)', transcriptOverlayButtonColor: 'var(--webchat-transcript-overlay-text)', - transcriptOverlayButtonColorOnFocus: 'var(--webchat-transcript-overlay-text-focus)', - transcriptOverlayButtonColorOnHover: 'var(--webchat-transcript-overlay-text-focus)', + transcriptOverlayButtonColorOnFocus: + 'var(--webchat-transcript-overlay-text-focus)', + transcriptOverlayButtonColorOnHover: + 'var(--webchat-transcript-overlay-text-focus)', }; diff --git a/packages/app/client/src/ui/editor/emulator/parts/inspector/inspector.spec.tsx b/packages/app/client/src/ui/editor/emulator/parts/inspector/inspector.spec.tsx index b34861a91..facf72674 100644 --- a/packages/app/client/src/ui/editor/emulator/parts/inspector/inspector.spec.tsx +++ b/packages/app/client/src/ui/editor/emulator/parts/inspector/inspector.spec.tsx @@ -1,213 +1,253 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// +// Microsoft Bot Framework: http://botframework.com +// +// Bot Framework Emulator Github: +// https://github.com/Microsoft/BotFramwork-Emulator +// +// Copyright (c) Microsoft Corporation +// All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// import * as React from 'react'; import { Provider } from 'react-redux'; import { mount } from 'enzyme'; import { combineReducers, createStore } from 'redux'; +import { + logEntry, + textItem, +} from '@bfemulator/emulator-core/lib/types/log/util'; +import LogLevel from '@bfemulator/emulator-core/lib/types/log/level'; + import { bot } from '../../../../../data/reducer/bot'; import { clientAwareSettings } from '../../../../../data/reducer/clientAwareSettingsReducer'; import { load, setActive } from '../../../../../data/action/botActions'; import { theme } from '../../../../../data/reducer/themeReducer'; -import { Inspector } from './inspector'; -import { InspectorContainer } from './inspectorContainer'; import { switchTheme } from '../../../../../data/action/themeActions'; import { ExtensionManager } from '../../../../../extensions'; import { LogService } from '../../../../../platform/log/logService'; -import { logEntry, textItem } from '@bfemulator/emulator-core/lib/types/log/util'; -import LogLevel from '@bfemulator/emulator-core/lib/types/log/level'; -const mockStore = createStore(combineReducers({ theme, bot, clientAwareSettings }), {}); +import { InspectorContainer } from './inspectorContainer'; +import { Inspector } from './inspector'; + +const mockStore = createStore( + combineReducers({ theme, bot, clientAwareSettings }), + {} +); jest.mock('../../../panel/panel.scss', () => ({})); jest.mock('../../../../../data/store', () => ({ get store() { return mockStore; - } + }, })); const mockState = { - 'bot': { - 'description': '', - 'internal': { - 'location': 'C:\\Users\\blerg\\Documents\\dev\\BotBuilder-Samples\\javascript_nodejs\\' + - '50.contoso-cafe-bot\\contoso-cafe-bot.bot' + bot: { + description: '', + internal: { + location: + 'C:\\Users\\blerg\\Documents\\dev\\BotBuilder-Samples\\javascript_nodejs\\' + + '50.contoso-cafe-bot\\contoso-cafe-bot.bot', }, - 'name': 'contoso-cafe-bot', - 'overrides': null, - 'path': 'C:\\Users\\blerg\\Documents\\dev\\BotBuilder-Samples\\javascript_nodejs\\' + + name: 'contoso-cafe-bot', + overrides: null, + path: + 'C:\\Users\\blerg\\Documents\\dev\\BotBuilder-Samples\\javascript_nodejs\\' + '50.contoso-cafe-bot\\contoso-cafe-bot.bot', - 'padlock': '', - 'services': [ + padlock: '', + services: [ { - 'appId': '', - 'appPassword': '', - 'endpoint': 'http://localhost:3978/api/messages', - 'id': 'http://localhost:3978/api/messages', - 'name': 'contoso-cafe-bot', - 'type': 'endpoint' + appId: '', + appPassword: '', + endpoint: 'http://localhost:3978/api/messages', + id: 'http://localhost:3978/api/messages', + name: 'contoso-cafe-bot', + type: 'endpoint', }, { - 'appId': 'cb904573-3d6f-46b0-80b9-b23a24e49152', - 'authoringKey': 'mathmatical!', - 'id': '60', - 'name': 'cafeDispatchModel', - 'region': 'westus', - 'subscriptionKey': 'mathmatical!', - 'type': 'luis', - 'version': '0.1' + appId: 'cb904573-3d6f-46b0-80b9-b23a24e49152', + authoringKey: 'mathmatical!', + id: '60', + name: 'cafeDispatchModel', + region: 'westus', + subscriptionKey: 'mathmatical!', + type: 'luis', + version: '0.1', }, { - 'appId': 'e01dc6a8-e2cb-4a96-8a41-f10730c41c43', - 'authoringKey': 'mathmatical!', - 'id': '235', - 'name': 'cafeBotBookTableTurnN', - 'region': 'westus', - 'subscriptionKey': 'mathmatical!', - 'type': 'luis', - 'version': '0.1' + appId: 'e01dc6a8-e2cb-4a96-8a41-f10730c41c43', + authoringKey: 'mathmatical!', + id: '235', + name: 'cafeBotBookTableTurnN', + region: 'westus', + subscriptionKey: 'mathmatical!', + type: 'luis', + version: '0.1', }, { - 'endpointKey': 'mathmatical!', - 'hostname': 'https://contosocafeqnab8.azurewebsites.net/qnamaker', - 'id': '163', - 'kbId': '1234', - 'name': 'cafeFaqChitChat', - 'subscriptionKey': 'd30ebbcc44ef4f07bae1a0e31b69f709', - 'type': 'qna' + endpointKey: 'mathmatical!', + hostname: 'https://contosocafeqnab8.azurewebsites.net/qnamaker', + id: '163', + kbId: '1234', + name: 'cafeFaqChitChat', + subscriptionKey: 'd30ebbcc44ef4f07bae1a0e31b69f709', + type: 'qna', }, { - 'appId': 'd59fa0b8-2398-4a7c-8043-224a4153494d', - 'authoringKey': 'mathmatical!', - 'id': '111', - 'name': 'getUserProfile', - 'region': 'westus', - 'subscriptionKey': 'mathmatical!', - 'type': 'luis', - 'version': '0.1' - } + appId: 'd59fa0b8-2398-4a7c-8043-224a4153494d', + authoringKey: 'mathmatical!', + id: '111', + name: 'getUserProfile', + region: 'westus', + subscriptionKey: 'mathmatical!', + type: 'luis', + version: '0.1', + }, ], - 'version': '2.0' + version: '2.0', }, - 'document': { - 'documentId': 'a00c2150-b6dc-11e8-9139-bbce58b6f97c', - 'inspectorObjects': [ + document: { + documentId: 'a00c2150-b6dc-11e8-9139-bbce58b6f97c', + inspectorObjects: [ { - 'accessories': [], - 'channelId': 'emulator', - 'conversation': { - 'id': 'd298cdb0-bad5-11e8-bffe-a55cd19d7f71|livechat' + accessories: [], + channelId: 'emulator', + conversation: { + id: 'd298cdb0-bad5-11e8-bffe-a55cd19d7f71|livechat', }, - 'from': { - 'id': 'http://localhost:3978/api/messages', - 'name': 'Bot', - 'role': 'bot' + from: { + id: 'http://localhost:3978/api/messages', + name: 'Bot', + role: 'bot', }, - 'id': 'ed50b550-bad5-11e8-b74d-8fc778b06796', - 'label': 'Luis Trace', - 'localTimestamp': '2018-09-17T17:01:00-07:00', - 'name': 'LuisRecognizer', - 'recipient': { - 'id': 'default-user', - 'role': 'user' + id: 'ed50b550-bad5-11e8-b74d-8fc778b06796', + label: 'Luis Trace', + localTimestamp: '2018-09-17T17:01:00-07:00', + name: 'LuisRecognizer', + recipient: { + id: 'default-user', + role: 'user', }, - 'replyToId': 'ecba8fd0-bad5-11e8-b74d-8fc778b06796', - 'serviceUrl': 'http://localhost:54725', - 'timestamp': '2018-09-18T00:01:00.709Z', - 'type': 'trace', - 'value': { - 'luisModel': { - 'ModelID': 'cb904573-3d6f-46b0-80b9-b23a24e49152' + replyToId: 'ecba8fd0-bad5-11e8-b74d-8fc778b06796', + serviceUrl: 'http://localhost:54725', + timestamp: '2018-09-18T00:01:00.709Z', + type: 'trace', + value: { + luisModel: { + ModelID: 'cb904573-3d6f-46b0-80b9-b23a24e49152', }, - 'luisOptions': { - 'Staging': false + luisOptions: { + Staging: false, }, - 'luisResult': { - 'entities': [], - 'query': 'HI', - 'topScoringIntent': { - 'intent': 'ChitChat', - 'score': 0.8652684 - } + luisResult: { + entities: [], + query: 'HI', + topScoringIntent: { + intent: 'ChitChat', + score: 0.8652684, + }, }, - 'recognizerResult': { - 'entities': { - '$instance': {} + recognizerResult: { + entities: { + $instance: {}, }, - 'intents': { - 'ChitChat': { - 'score': 0.8652684 - } + intents: { + ChitChat: { + score: 0.8652684, + }, }, - 'luisResult': null, - 'text': 'HI' - } + luisResult: null, + text: 'HI', + }, }, - 'valueType': 'https://www.luis.ai/schemas/trace' - } - ] + valueType: 'https://www.luis.ai/schemas/trace', + }, + ], }, - 'themeInfo': { - 'themeName': 'Dark', - 'themeHref': null, - 'themeComponents': [ + themeInfo: { + themeName: 'Dark', + themeHref: null, + themeComponents: [ 'http://localhost:3000/css/neutral.css', 'http://localhost:3000/themes/dark.css', 'http://localhost:3000/css/fonts.css', - 'http://localhost:3000/css/redline.css' - ] - } + 'http://localhost:3000/css/redline.css', + ], + }, }; const mockExtensions = [ { - 'client': { - 'basePath': '', - 'inspectors': [ + client: { + basePath: '', + inspectors: [ { - 'accessories': [ + accessories: [ { - 'id': 'train', - 'states': { - 'default': { - 'icon': 'Refresh', - 'label': 'Train' + id: 'train', + states: { + default: { + icon: 'Refresh', + label: 'Train', }, - 'working': { - 'icon': 'Spinner', - 'label': 'Training' - } - } + working: { + icon: 'Spinner', + label: 'Training', + }, + }, }, { - 'id': 'publish', - 'states': { - 'default': { - 'icon': 'Share', - 'label': 'Publish' + id: 'publish', + states: { + default: { + icon: 'Share', + label: 'Publish', + }, + working: { + icon: 'Spinner', + label: 'Publishing', }, - 'working': { - 'icon': 'Spinner', - 'label': 'Publishing' - } - } - } + }, + }, ], - 'criteria': { - 'path': '$.type', - 'value': 'message' + criteria: { + path: '$.type', + value: 'message', }, - 'name': 'JSON', - 'src': 'file:///C:/Users/juwilaby/Documents/dev/BotFramework-Emulator/packages/app/' + + name: 'JSON', + src: + 'file:///C:/Users/juwilaby/Documents/dev/BotFramework-Emulator/packages/app/' + 'main/node_modules/@bfemulator/extension-json/index.html', - 'summaryText': [ - 'attachments.0.contentType', - 'text' - ] - } - ] + summaryText: ['attachments.0.contentType', 'text'], + }, + ], }, - 'name': 'JSON', - 'node': {} - } + name: 'JSON', + node: {}, + }, ]; ExtensionManager.addExtension(mockExtensions[0], '1234'); jest.mock('./inspector.scss', () => ({})); @@ -217,9 +257,9 @@ describe('The Inspector component', () => { let parent; let node; const documentCreateElement = document.createElement.bind(document); - document.createElement = function (...args: any[]): any { + document.createElement = function(...args: any[]): any { const el = documentCreateElement(...args); - el.send = function () { + el.send = function() { return true; }; return el; @@ -229,9 +269,11 @@ describe('The Inspector component', () => { mockStore.dispatch(load([mockState.bot])); mockStore.dispatch(setActive(mockState.bot as any)); - parent = mount( - - ); + parent = mount( + + + + ); node = parent.find(Inspector); }); @@ -271,7 +313,10 @@ describe('The Inspector component', () => { it('should send the initialization stack to the inspector when the dom is ready', () => { const instance = node.instance(); - const instanceSpy = jest.spyOn(instance, 'sendInitializationStackToInspector'); + const instanceSpy = jest.spyOn( + instance, + 'sendInitializationStackToInspector' + ); const event = { currentTarget: { removeEventListener: () => true } }; const eventSpy = jest.spyOn(event.currentTarget, 'removeEventListener'); @@ -334,7 +379,10 @@ describe('The Inspector component', () => { const text = `[${inspectorName}] ${event.args[0]}`; instance.ipcMessageEventHandler(event); - expect(logSpy).toHaveBeenCalledWith(mockState.document.documentId, logEntry(textItem(LogLevel.Info, text))); + expect(logSpy).toHaveBeenCalledWith( + mockState.document.documentId, + logEntry(textItem(LogLevel.Info, text)) + ); }); }); }); diff --git a/packages/app/client/src/ui/editor/emulator/parts/inspector/inspector.tsx b/packages/app/client/src/ui/editor/emulator/parts/inspector/inspector.tsx index 05bd6dc2b..729bb0c0a 100644 --- a/packages/app/client/src/ui/editor/emulator/parts/inspector/inspector.tsx +++ b/packages/app/client/src/ui/editor/emulator/parts/inspector/inspector.tsx @@ -32,14 +32,27 @@ import LogLevel from '@bfemulator/emulator-core/lib/types/log/level'; // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // Cheating here and pulling in a module from node. Can be easily replaced if we ever move the emulator to the web. -import { logEntry, textItem } from '@bfemulator/emulator-core/lib/types/log/util'; -import { ExtensionInspector, InspectorAccessory, InspectorAccessoryState } from '@bfemulator/sdk-shared'; +import { + logEntry, + textItem, +} from '@bfemulator/emulator-core/lib/types/log/util'; +import { + ExtensionInspector, + InspectorAccessory, + InspectorAccessoryState, +} from '@bfemulator/sdk-shared'; import { Spinner } from '@bfemulator/ui-react'; import { IBotConfiguration } from 'botframework-config/lib/schema'; import * as React from 'react'; -import { ExtensionManager, GetInspectorResult, InspectorAPI } from '../../../../../extensions'; + +import { + ExtensionManager, + GetInspectorResult, + InspectorAPI, +} from '../../../../../extensions'; import { LogService } from '../../../../../platform/log/logService'; import Panel, { PanelContent, PanelControls } from '../../../panel/panel'; + import * as styles from './inspector.scss'; interface GetInspectorResultInternal { @@ -61,7 +74,7 @@ interface IpcMessageEvent extends Event { interface InspectorProps { document: any; cwdAsBase: string; - themeInfo: { themeName: string, themeComponents: string[] }; + themeInfo: { themeName: string; themeComponents: string[] }; activeBot?: IBotConfiguration; botHash?: string; } @@ -72,16 +85,17 @@ interface InspectorState { botHash?: string; inspectorSrc?: string; inspectObj: { [propName: string]: any }; - themeInfo: { themeName: string, themeComponents: string[] }; + themeInfo: { themeName: string; themeComponents: string[] }; inspector: ExtensionInspector; buttons: AccessoryButton[]; title: string; } -declare type ElectronHTMLWebViewElement = HTMLWebViewElement & { send: (...args: any[]) => void }; +declare type ElectronHTMLWebViewElement = HTMLWebViewElement & { + send: (...args: any[]) => void; +}; export class Inspector extends React.Component { - public get state(): InspectorState { return this._state; } @@ -94,17 +108,25 @@ export class Inspector extends React.Component { private _state = {} as InspectorState; private containerRef: HTMLDivElement; - private webViewByLocation: { [location: string]: ElectronHTMLWebViewElement } = {}; - - public static getDerivedStateFromProps(newProps: InspectorProps, prevState: InspectorState): InspectorState { + private webViewByLocation: { + [location: string]: ElectronHTMLWebViewElement; + } = {}; + + public static getDerivedStateFromProps( + newProps: InspectorProps, + prevState: InspectorState + ): InspectorState { const { document = {} } = newProps; const inspectorResult = Inspector.getInspector(document.inspectorObjects); const { inspector = { name: '' } } = inspectorResult.response; - if (newProps.botHash !== prevState.botHash || + if ( + newProps.botHash !== prevState.botHash || inspector.src !== prevState.inspectorSrc || newProps.themeInfo.themeName !== prevState.themeInfo.themeName || - JSON.stringify(inspectorResult.inspectObj) !== JSON.stringify(prevState.inspectObj)) { + JSON.stringify(inspectorResult.inspectObj) !== + JSON.stringify(prevState.inspectObj) + ) { return { ...prevState, activeBot: newProps.activeBot, @@ -114,35 +136,43 @@ export class Inspector extends React.Component { inspectObj: inspectorResult.inspectObj, themeInfo: newProps.themeInfo, title: inspector.name, - buttons: Inspector.getButtons(inspector.accessories) + buttons: Inspector.getButtons(inspector.accessories), }; } return null; } - private static getInspector(inspectorObjects: any[] = []): GetInspectorResultInternal { + private static getInspector( + inspectorObjects: any[] = [] + ): GetInspectorResultInternal { const obj = inspectorObjects[0]; return { inspectObj: obj, // Find an inspector for this object. - response: obj ? ExtensionManager.inspectorForObject(obj, true) || {} : {} as any + response: obj + ? ExtensionManager.inspectorForObject(obj, true) || {} + : ({} as any), }; } - private static getButtons(accessories: InspectorAccessory[] = []): AccessoryButton[] { - return accessories.map(config => { - // Accessory must have a "default" state to be added - if (config && config.states.default) { - return { - config, - state: 'default', - enabled: true - }; - } else { - return null; - } - }).filter(accessoryState => !!accessoryState); + private static getButtons( + accessories: InspectorAccessory[] = [] + ): AccessoryButton[] { + return accessories + .map(config => { + // Accessory must have a "default" state to be added + if (config && config.states.default) { + return { + config, + state: 'default', + enabled: true, + }; + } else { + return null; + } + }) + .filter(accessoryState => !!accessoryState); } public componentDidMount() { @@ -151,18 +181,30 @@ export class Inspector extends React.Component { } public componentWillUnmount() { - window.removeEventListener('toggle-inspector-devtools', this.toggleDevTools); + window.removeEventListener( + 'toggle-inspector-devtools', + this.toggleDevTools + ); } public render() { if (this.state.inspector) { return ( -
- s && s.length).join(' - ') }> - { this.renderAccessoryButtons(this.state.inspector) } +
+ s && s.length) + .join(' - ')} + > + {this.renderAccessoryButtons(this.state.inspector)} -
-
 
+
+
+   +
@@ -171,9 +213,8 @@ export class Inspector extends React.Component { } else { return ( // No inspector was found. -
- - +
+
); } @@ -181,27 +222,36 @@ export class Inspector extends React.Component { private renderAccessoryIcon(config: InspectorAccessoryState) { if (config.icon === 'Spinner') { - return ; + return ; } else if (config.icon) { return ( -