mirror of
https://github.com/mitmproxy/mitmproxy.git
synced 2024-11-24 05:40:05 +00:00
web: test coverage++, adjust commandbar
This commit is contained in:
parent
2945ba925b
commit
46cd40f493
@ -529,7 +529,13 @@ class ExecuteCommand(RequestHandler):
|
|||||||
args = self.json['arguments']
|
args = self.json['arguments']
|
||||||
except APIError:
|
except APIError:
|
||||||
args = []
|
args = []
|
||||||
|
try:
|
||||||
result = self.master.commands.call_strings(cmd, args)
|
result = self.master.commands.call_strings(cmd, args)
|
||||||
|
except Exception as e:
|
||||||
|
self.write({
|
||||||
|
"error": str(e)
|
||||||
|
})
|
||||||
|
else:
|
||||||
self.write({
|
self.write({
|
||||||
"value": result,
|
"value": result,
|
||||||
# "type": command.typename(type(result)) if result is not None else "none"
|
# "type": command.typename(type(result)) if result is not None else "none"
|
||||||
|
1095
mitmproxy/tools/web/static/app.css
vendored
1095
mitmproxy/tools/web/static/app.css
vendored
File diff suppressed because one or more lines are too long
50338
mitmproxy/tools/web/static/app.js
vendored
50338
mitmproxy/tools/web/static/app.js
vendored
File diff suppressed because one or more lines are too long
9134
mitmproxy/tools/web/static/vendor.css
vendored
9134
mitmproxy/tools/web/static/vendor.css
vendored
File diff suppressed because one or more lines are too long
@ -13,8 +13,9 @@ module.exports = async () => {
|
|||||||
],
|
],
|
||||||
"coverageDirectory": "./coverage",
|
"coverageDirectory": "./coverage",
|
||||||
"coveragePathIgnorePatterns": [
|
"coveragePathIgnorePatterns": [
|
||||||
"<rootDir>/src/js/filt/filt.js",
|
"<rootDir>/src/js/contrib/",
|
||||||
"<rootDir>/src/js/filt/command.js"
|
"<rootDir>/src/js/filt/",
|
||||||
|
"<rootDir>/src/js/components/editors/"
|
||||||
],
|
],
|
||||||
"collectCoverageFrom": [
|
"collectCoverageFrom": [
|
||||||
"src/js/**/*.{js,jsx,ts,tsx}"
|
"src/js/**/*.{js,jsx,ts,tsx}"
|
||||||
|
152
web/package-lock.json
generated
152
web/package-lock.json
generated
@ -662,12 +662,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/runtime-corejs3": {
|
"@babel/runtime-corejs3": {
|
||||||
"version": "7.14.6",
|
"version": "7.15.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.14.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.15.3.tgz",
|
||||||
"integrity": "sha512-Xl8SPYtdjcMoCsIM4teyVRg7jIcgl8F2kRtoCcXuHzXswt9UxZCS6BzRo8fcnCuP6u2XtPgvyonmEPF57Kxo9Q==",
|
"integrity": "sha512-30A3lP+sRL6ml8uhoJSs+8jwpKzbw8CqBvDc1laeptxPm5FahumJxirigcbD2qTs71Sonvj1cyZB0OKGAmxQ+A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"core-js-pure": "^3.14.0",
|
"core-js-pure": "^3.16.0",
|
||||||
"regenerator-runtime": "^0.13.4"
|
"regenerator-runtime": "^0.13.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1025,9 +1025,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@testing-library/dom": {
|
"@testing-library/dom": {
|
||||||
"version": "7.31.2",
|
"version": "8.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.2.tgz",
|
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.1.0.tgz",
|
||||||
"integrity": "sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==",
|
"integrity": "sha512-kmW9alndr19qd6DABzQ978zKQ+J65gU2Rzkl8hriIetPnwpesRaK4//jEQyYh8fEALmGhomD/LBQqt+o+DL95Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.10.4",
|
"@babel/code-frame": "^7.10.4",
|
||||||
@ -1037,7 +1037,71 @@
|
|||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
"dom-accessibility-api": "^0.5.6",
|
"dom-accessibility-api": "^0.5.6",
|
||||||
"lz-string": "^1.4.4",
|
"lz-string": "^1.4.4",
|
||||||
"pretty-format": "^26.6.2"
|
"pretty-format": "^27.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@testing-library/jest-dom": {
|
||||||
|
"version": "5.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.14.1.tgz",
|
||||||
|
"integrity": "sha512-dfB7HVIgTNCxH22M1+KU6viG5of2ldoA5ly8Ar8xkezKHKXjRvznCdbMbqjYGgO2xjRbwnR+rR8MLUIqF3kKbQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.9.2",
|
||||||
|
"@types/testing-library__jest-dom": "^5.9.1",
|
||||||
|
"aria-query": "^4.2.2",
|
||||||
|
"chalk": "^3.0.0",
|
||||||
|
"css": "^3.0.0",
|
||||||
|
"css.escape": "^1.5.1",
|
||||||
|
"dom-accessibility-api": "^0.5.6",
|
||||||
|
"lodash": "^4.17.15",
|
||||||
|
"redent": "^3.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indent-string": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"redent": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"indent-string": "^4.0.0",
|
||||||
|
"strip-indent": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"strip-indent": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"min-indent": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@testing-library/react": {
|
||||||
|
"version": "11.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.7.tgz",
|
||||||
|
"integrity": "sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.12.5",
|
||||||
|
"@testing-library/dom": "^7.28.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jest/types": {
|
"@jest/types": {
|
||||||
@ -1053,10 +1117,26 @@
|
|||||||
"chalk": "^4.0.0"
|
"chalk": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@testing-library/dom": {
|
||||||
|
"version": "7.31.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.2.tgz",
|
||||||
|
"integrity": "sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/code-frame": "^7.10.4",
|
||||||
|
"@babel/runtime": "^7.12.5",
|
||||||
|
"@types/aria-query": "^4.2.0",
|
||||||
|
"aria-query": "^4.2.2",
|
||||||
|
"chalk": "^4.1.0",
|
||||||
|
"dom-accessibility-api": "^0.5.6",
|
||||||
|
"lz-string": "^1.4.4",
|
||||||
|
"pretty-format": "^26.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/yargs": {
|
"@types/yargs": {
|
||||||
"version": "15.0.13",
|
"version": "15.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz",
|
||||||
"integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
|
"integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/yargs-parser": "*"
|
"@types/yargs-parser": "*"
|
||||||
@ -1076,14 +1156,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@testing-library/react": {
|
"@testing-library/user-event": {
|
||||||
"version": "11.2.7",
|
"version": "13.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.2.1.tgz",
|
||||||
"integrity": "sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA==",
|
"integrity": "sha512-cczlgVl+krjOb3j1625usarNEibI0IFRJrSWX9UsJ1HKYFgCQv9Nb7QAipUDXl3Xdz8NDTsiS78eAkPSxlzTlw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5"
|
||||||
"@testing-library/dom": "^7.28.1"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@tootallnate/once": {
|
"@tootallnate/once": {
|
||||||
@ -1093,9 +1172,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/aria-query": {
|
"@types/aria-query": {
|
||||||
"version": "4.2.1",
|
"version": "4.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz",
|
||||||
"integrity": "sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==",
|
"integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/babel__core": {
|
"@types/babel__core": {
|
||||||
@ -1336,6 +1415,15 @@
|
|||||||
"integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==",
|
"integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/testing-library__jest-dom": {
|
||||||
|
"version": "5.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.1.tgz",
|
||||||
|
"integrity": "sha512-Gk9vaXfbzc5zCXI9eYE9BI5BNHEp4D3FWjgqBE/ePGYElLAP+KvxBcsdkwfIVvezs605oiyd/VrpiHe3Oeg+Aw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/jest": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/vinyl": {
|
"@types/vinyl": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.4.tgz",
|
||||||
@ -2652,9 +2740,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"core-js-pure": {
|
"core-js-pure": {
|
||||||
"version": "3.15.0",
|
"version": "3.16.2",
|
||||||
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.16.2.tgz",
|
||||||
"integrity": "sha512-RO+LFAso8DB6OeBX9BAcEGvyth36QtxYon1OyVsITNVtSKr/Hos0BXZwnsOJ7o+O6KHtK+O+cJIEj9NGg6VwFA==",
|
"integrity": "sha512-oxKe64UH049mJqrKkynWp6Vu0Rlm/BTXO/bJZuN2mmR3RtOFNepLlSWDd1eo16PzHpQAoNG97rLU1V/YxesJjw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
@ -2729,6 +2817,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"css.escape": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
|
||||||
|
"integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"cssom": {
|
"cssom": {
|
||||||
"version": "0.4.4",
|
"version": "0.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
|
||||||
@ -2954,9 +3048,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"dom-accessibility-api": {
|
"dom-accessibility-api": {
|
||||||
"version": "0.5.6",
|
"version": "0.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.7.tgz",
|
||||||
"integrity": "sha512-DplGLZd8L1lN64jlT27N9TVSESFR5STaEJvX+thCby7fuCHonfPpAlodYc3vuUYbDuDec5w8AMP7oCM5TWFsqw==",
|
"integrity": "sha512-ml3lJIq9YjUfM9TUnEPvEYWFSwivwIGBPKpewX7tii7fwCazA8yCioGdqQcNsItPpfFvSJ3VIdMQPj60LJhcQA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"domexception": {
|
"domexception": {
|
||||||
@ -6666,6 +6760,12 @@
|
|||||||
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"min-indent": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||||
|
@ -23,7 +23,10 @@
|
|||||||
"stable": "^0.1.8"
|
"stable": "^0.1.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@testing-library/dom": "^8.1.0",
|
||||||
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^11.2.7",
|
"@testing-library/react": "^11.2.7",
|
||||||
|
"@testing-library/user-event": "^13.2.1",
|
||||||
"@types/jest": "^26.0.23",
|
"@types/jest": "^26.0.23",
|
||||||
"@types/redux-mock-store": "^1.0.2",
|
"@types/redux-mock-store": "^1.0.2",
|
||||||
"esbuild": "^0.12.9",
|
"esbuild": "^0.12.9",
|
||||||
|
@ -1,26 +1,67 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import {render, waitFor, screen} from "../test-utils";
|
import {render, screen, userEvent, waitFor} from "../test-utils";
|
||||||
import CommandBar from "../../components/CommandBar";
|
import CommandBar from "../../components/CommandBar";
|
||||||
import fetchMock, {enableFetchMocks} from "jest-fetch-mock";
|
import fetchMock, {enableFetchMocks} from "jest-fetch-mock";
|
||||||
|
|
||||||
enableFetchMocks();
|
enableFetchMocks();
|
||||||
|
|
||||||
test("CommandBar", async () => {
|
test("CommandBar", async () => {
|
||||||
fetchMock.mockResponseOnce(JSON.stringify({
|
fetchMock.mockOnceIf("./commands", JSON.stringify({
|
||||||
"flow.decode": {"help": "Decode flows.",
|
"flow.decode": {
|
||||||
"parameters": [{"name": "flows", "type": "flow[]", "kind": "POSITIONAL_OR_KEYWORD"}, {
|
"help": "Decode flows.",
|
||||||
"name": "part",
|
"parameters": [
|
||||||
"type": "str",
|
{"name": "flows", "type": "flow[]", "kind": "POSITIONAL_OR_KEYWORD"},
|
||||||
"kind": "POSITIONAL_OR_KEYWORD"
|
{"name": "part", "type": "str", "kind": "POSITIONAL_OR_KEYWORD"}
|
||||||
}],
|
],
|
||||||
"return_type": null,
|
"return_type": null,
|
||||||
"signature_help": "flow.decode flows part"
|
"signature_help": "flow.decode flows part"
|
||||||
|
},
|
||||||
|
"flow.encode": {
|
||||||
|
"help": "Encode flows with a specified encoding.",
|
||||||
|
"parameters": [
|
||||||
|
{"name": "flows", "type": "flow[]", "kind": "POSITIONAL_OR_KEYWORD"},
|
||||||
|
{"name": "part", "type": "str", "kind": "POSITIONAL_OR_KEYWORD"},
|
||||||
|
{"name": "encoding", "type": "choice", "kind": "POSITIONAL_OR_KEYWORD"}
|
||||||
|
],
|
||||||
|
"return_type": null,
|
||||||
|
"signature_help": "flow.encode flows part encoding"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
fetchMock.mockOnceIf("./commands/commands.history.get", JSON.stringify({value: ["foo"]}));
|
||||||
|
fetchMock.mockOnceIf("./commands/commands.history.add", JSON.stringify({value: null}));
|
||||||
|
fetchMock.mockOnceIf("./commands/flow.encode", JSON.stringify({value: null}));
|
||||||
|
|
||||||
const {asFragment} = render(<CommandBar/>);
|
const {asFragment} = render(<CommandBar/>);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
await waitFor(() => screen.getByText('["flow.decode"]'))
|
await waitFor(() => screen.getByText('["flow.decode","flow.encode"]'))
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
|
||||||
|
const input = screen.getByPlaceholderText("Enter command");
|
||||||
|
|
||||||
|
userEvent.type(input, 'x');
|
||||||
|
expect(screen.getByText("[]")).toBeInTheDocument();
|
||||||
|
userEvent.type(input, "{backspace}");
|
||||||
|
|
||||||
|
userEvent.type(input, 'fl');
|
||||||
|
userEvent.tab();
|
||||||
|
expect(input).toHaveValue('flow.decode');
|
||||||
|
userEvent.tab();
|
||||||
|
expect(input).toHaveValue('flow.encode');
|
||||||
|
|
||||||
|
fetchMock.mockOnce(JSON.stringify({value: null}));
|
||||||
|
userEvent.type(input, "{enter}");
|
||||||
|
await waitFor(() => screen.getByText("Command Result"));
|
||||||
|
|
||||||
|
userEvent.type(input, "{arrowdown}");
|
||||||
|
expect(input).toHaveValue("");
|
||||||
|
|
||||||
|
userEvent.type(input, "{arrowup}");
|
||||||
|
expect(input).toHaveValue("flow.encode");
|
||||||
|
userEvent.type(input, "{arrowup}");
|
||||||
|
expect(input).toHaveValue("foo");
|
||||||
|
userEvent.type(input, "{arrowdown}");
|
||||||
|
expect(input).toHaveValue("flow.encode");
|
||||||
|
userEvent.type(input, "{arrowdown}");
|
||||||
|
expect(input).toHaveValue("");
|
||||||
});
|
});
|
||||||
|
@ -109,7 +109,7 @@ exports[`OptionMenu Component should render correctly 1`] = `
|
|||||||
>
|
>
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
checked={true}
|
checked={false}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
/>
|
/>
|
||||||
|
22
web/src/js/__tests__/components/HeaderSpec.tsx
Normal file
22
web/src/js/__tests__/components/HeaderSpec.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import {render, screen} from "../test-utils";
|
||||||
|
import Header from "../../components/Header";
|
||||||
|
import {fireEvent} from "@testing-library/react";
|
||||||
|
|
||||||
|
|
||||||
|
test("Header", async () => {
|
||||||
|
|
||||||
|
const {asFragment} = render(<Header/>);
|
||||||
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText("Options"));
|
||||||
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
expect(screen.getByText("Edit Options")).toBeTruthy();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText("File"));
|
||||||
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
expect(screen.getByText("Open...")).toBeTruthy();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText("File"));
|
||||||
|
expect(screen.queryByText("Open...")).toBeNull()
|
||||||
|
});
|
15
web/src/js/__tests__/components/ProxyAppSpec.tsx
Normal file
15
web/src/js/__tests__/components/ProxyAppSpec.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import {render, screen, waitFor} from "../test-utils";
|
||||||
|
import ProxyApp from "../../components/ProxyApp";
|
||||||
|
import {enableFetchMocks} from "jest-fetch-mock";
|
||||||
|
import {ContentViewData} from "../../components/contentviews/useContent";
|
||||||
|
|
||||||
|
enableFetchMocks();
|
||||||
|
|
||||||
|
test("ProxyApp", async () => {
|
||||||
|
const cv: ContentViewData = {lines: [[["text", "my data"]]], description: ""}
|
||||||
|
fetchMock.doMockOnceIf("./flows/flow2/request/content/Auto.json?lines=81", JSON.stringify(cv));
|
||||||
|
render(<ProxyApp/>);
|
||||||
|
expect(screen.getByTitle("Mitmproxy Version")).toBeDefined();
|
||||||
|
await waitFor(() => screen.getByText("my data"));
|
||||||
|
});
|
@ -84,7 +84,7 @@ exports[`CommandBar 2`] = `
|
|||||||
<p
|
<p
|
||||||
class="available-commands"
|
class="available-commands"
|
||||||
>
|
>
|
||||||
["flow.decode"]
|
["flow.decode","flow.encode"]
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,533 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Header 1`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<header>
|
||||||
|
<nav
|
||||||
|
class="nav-tabs nav-tabs-lg"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="pull-left special"
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
File
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
Start
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
Options
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="active"
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
Flow
|
||||||
|
</a>
|
||||||
|
<span
|
||||||
|
class="connection-indicator established"
|
||||||
|
>
|
||||||
|
connected
|
||||||
|
</span>
|
||||||
|
</nav>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="flow-menu"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-content"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn btn-default"
|
||||||
|
disabled=""
|
||||||
|
title="[r]eplay flow"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-repeat text-primary"
|
||||||
|
/>
|
||||||
|
Replay
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-default"
|
||||||
|
title="[D]uplicate flow"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-copy text-info"
|
||||||
|
/>
|
||||||
|
Duplicate
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-default"
|
||||||
|
disabled=""
|
||||||
|
title="revert changes to flow [V]"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-history text-warning"
|
||||||
|
/>
|
||||||
|
Revert
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-default"
|
||||||
|
title="[d]elete flow"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-trash text-danger"
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-legend"
|
||||||
|
>
|
||||||
|
Flow Modification
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-content"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn btn-default"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-download"
|
||||||
|
/>
|
||||||
|
Download▾
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn btn-default"
|
||||||
|
title="Export flow."
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-clone"
|
||||||
|
/>
|
||||||
|
Export▾
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-legend"
|
||||||
|
>
|
||||||
|
Export
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-content"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn btn-default"
|
||||||
|
disabled=""
|
||||||
|
title="[a]ccept intercepted flow"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-play text-success"
|
||||||
|
/>
|
||||||
|
Resume
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-default"
|
||||||
|
disabled=""
|
||||||
|
title="kill intercepted flow [x]"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-times text-danger"
|
||||||
|
/>
|
||||||
|
Abort
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-legend"
|
||||||
|
>
|
||||||
|
Interception
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Header 2`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<header>
|
||||||
|
<nav
|
||||||
|
class="nav-tabs nav-tabs-lg"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="pull-left special"
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
File
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
Start
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="active"
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
Options
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
Flow
|
||||||
|
</a>
|
||||||
|
<span
|
||||||
|
class="connection-indicator established"
|
||||||
|
>
|
||||||
|
connected
|
||||||
|
</span>
|
||||||
|
</nav>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="menu-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-content"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn btn-default"
|
||||||
|
title="Open Options"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-cogs text-primary"
|
||||||
|
/>
|
||||||
|
Edit Options
|
||||||
|
<sup>
|
||||||
|
alpha
|
||||||
|
</sup>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-legend"
|
||||||
|
>
|
||||||
|
Options Editor
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-entry"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Strip cache headers
|
||||||
|
<a
|
||||||
|
href="https://docs.mitmproxy.org/stable/overview-features/#anticache"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-question-circle"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-entry"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Use host header for display
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-entry"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Don't verify server certificates
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-legend"
|
||||||
|
>
|
||||||
|
Quick Options
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-entry"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
checked=""
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Display Event Log
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-entry"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Display Command Bar
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-legend"
|
||||||
|
>
|
||||||
|
View Options
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Header 3`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<header>
|
||||||
|
<nav
|
||||||
|
class="nav-tabs nav-tabs-lg"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="pull-left special open"
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
File
|
||||||
|
</a>
|
||||||
|
<ul
|
||||||
|
class="dropdown-menu show"
|
||||||
|
style="position: absolute; left: 0px; top: 0px;"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-fw fa-folder-open"
|
||||||
|
/>
|
||||||
|
Open...
|
||||||
|
<input
|
||||||
|
class="hidden"
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-fw fa-floppy-o"
|
||||||
|
/>
|
||||||
|
Save...
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-fw fa-trash"
|
||||||
|
/>
|
||||||
|
Clear All
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="divider"
|
||||||
|
role="separator"
|
||||||
|
/>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="http://mitm.it/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-fw fa-external-link"
|
||||||
|
/>
|
||||||
|
Install Certificates...
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
Start
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="active"
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
Options
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
Flow
|
||||||
|
</a>
|
||||||
|
<span
|
||||||
|
class="connection-indicator established"
|
||||||
|
>
|
||||||
|
connected
|
||||||
|
</span>
|
||||||
|
</nav>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="menu-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-content"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn btn-default"
|
||||||
|
title="Open Options"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-cogs text-primary"
|
||||||
|
/>
|
||||||
|
Edit Options
|
||||||
|
<sup>
|
||||||
|
alpha
|
||||||
|
</sup>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-legend"
|
||||||
|
>
|
||||||
|
Options Editor
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-entry"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Strip cache headers
|
||||||
|
<a
|
||||||
|
href="https://docs.mitmproxy.org/stable/overview-features/#anticache"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-question-circle"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-entry"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Use host header for display
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-entry"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Don't verify server certificates
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-legend"
|
||||||
|
>
|
||||||
|
Quick Options
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="menu-entry"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
checked=""
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Display Event Log
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-entry"
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Display Command Bar
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="menu-legend"
|
||||||
|
>
|
||||||
|
View Options
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
@ -1,6 +1,6 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import ValidateEditor from '../../../components/editors/ValidateEditor'
|
import ValidateEditor from '../../../components/editors/ValidateEditor'
|
||||||
import {fireEvent, render, screen, waitFor} from "../../test-utils";
|
import {fireEvent, render, screen, userEvent, waitFor} from "../../test-utils";
|
||||||
|
|
||||||
test("ValidateEditor", async () => {
|
test("ValidateEditor", async () => {
|
||||||
const onEditDone = jest.fn();
|
const onEditDone = jest.fn();
|
||||||
@ -9,8 +9,7 @@ test("ValidateEditor", async () => {
|
|||||||
);
|
);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
|
||||||
fireEvent.mouseDown(screen.getByText("ok"));
|
userEvent.click(screen.getByText("ok"));
|
||||||
fireEvent.mouseUp(screen.getByText("ok"));
|
|
||||||
|
|
||||||
screen.getByText("ok").innerHTML = "this is ok";
|
screen.getByText("ok").innerHTML = "this is ok";
|
||||||
|
|
||||||
@ -19,8 +18,7 @@ test("ValidateEditor", async () => {
|
|||||||
await waitFor(() => expect(onEditDone).toBeCalledWith("this is ok"));
|
await waitFor(() => expect(onEditDone).toBeCalledWith("this is ok"));
|
||||||
onEditDone.mockClear();
|
onEditDone.mockClear();
|
||||||
|
|
||||||
fireEvent.mouseDown(screen.getByText("this is ok"));
|
userEvent.click(screen.getByText("this is ok"));
|
||||||
fireEvent.mouseUp(screen.getByText("this is ok"));
|
|
||||||
screen.getByText("this is ok").innerHTML = "wat";
|
screen.getByText("this is ok").innerHTML = "wat";
|
||||||
fireEvent.blur(screen.getByText("wat"));
|
fireEvent.blur(screen.getByText("wat"));
|
||||||
expect(screen.getByText("ok")).toBeDefined();
|
expect(screen.getByText("ok")).toBeDefined();
|
||||||
|
10
web/src/js/__tests__/ducks/commandBarSpec.tsx
Normal file
10
web/src/js/__tests__/ducks/commandBarSpec.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import reduceCommandBar, * as commandBarActions from '../../ducks/commandBar'
|
||||||
|
|
||||||
|
test("CommandBar", async () => {
|
||||||
|
expect(reduceCommandBar(undefined, {type: "other"})).toEqual({
|
||||||
|
visible: false
|
||||||
|
})
|
||||||
|
expect(reduceCommandBar(undefined, commandBarActions.toggleVisibility())).toEqual({
|
||||||
|
visible: true
|
||||||
|
});
|
||||||
|
});
|
@ -187,10 +187,15 @@ describe('flows actions', () => {
|
|||||||
|
|
||||||
test("makeSort", () => {
|
test("makeSort", () => {
|
||||||
const a = TFlow(), b = TFlow();
|
const a = TFlow(), b = TFlow();
|
||||||
|
a.request.scheme = "https";
|
||||||
|
a.request.method = "POST";
|
||||||
|
a.request.path = "/foo";
|
||||||
|
a.response.contentLength = 42;
|
||||||
|
a.response.status_code = 418;
|
||||||
|
|
||||||
Object.keys(FlowColumns).forEach((column) => {
|
Object.keys(FlowColumns).forEach((column, i) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const sort = flowActions.makeSort({column, desc: true});
|
const sort = flowActions.makeSort({column, desc: i % 2 == 0});
|
||||||
expect(sort(a, b)).toBeDefined();
|
expect(sort(a, b)).toBeDefined();
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -38,3 +38,29 @@ test("sendUpdate", async () => {
|
|||||||
])
|
])
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("save", async () => {
|
||||||
|
enableFetchMocks();
|
||||||
|
fetchMock.mockResponseOnce("");
|
||||||
|
let store = TStore();
|
||||||
|
await store.dispatch(OptionsActions.save());
|
||||||
|
expect(fetchMock).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("addInterceptFilter", async () => {
|
||||||
|
enableFetchMocks();
|
||||||
|
fetchMock.mockClear();
|
||||||
|
fetchMock.mockResponses("", "");
|
||||||
|
let store = TStore();
|
||||||
|
await store.dispatch(OptionsActions.addInterceptFilter("~u foo"));
|
||||||
|
expect(fetchMock.mock.calls[0][1]?.body).toEqual('{"intercept":"~u foo"}');
|
||||||
|
store.getState().options.intercept = "~u foo";
|
||||||
|
|
||||||
|
await store.dispatch(OptionsActions.addInterceptFilter("~u foo"));
|
||||||
|
expect(fetchMock.mock.calls).toHaveLength(1);
|
||||||
|
|
||||||
|
await store.dispatch(OptionsActions.addInterceptFilter("~u bar"));
|
||||||
|
expect(fetchMock.mock.calls[1][1]?.body).toEqual('{"intercept":"~u foo | ~u bar"}');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
16
web/src/js/__tests__/ducks/options_metaSpec.tsx
Normal file
16
web/src/js/__tests__/ducks/options_metaSpec.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import reduceOptionsMeta, * as OptionsMetaActions from "../../ducks/options_meta";
|
||||||
|
import * as OptionsActions from "../../ducks/options";
|
||||||
|
|
||||||
|
test("options_meta", async () => {
|
||||||
|
expect(reduceOptionsMeta(undefined, {type: "other"})).toEqual(OptionsMetaActions.defaultState);
|
||||||
|
|
||||||
|
expect(reduceOptionsMeta(undefined, {
|
||||||
|
type: OptionsActions.RECEIVE,
|
||||||
|
data: {id: {value: 'foo'}}
|
||||||
|
})).toEqual({id: {value: 'foo'}})
|
||||||
|
|
||||||
|
expect(reduceOptionsMeta(undefined, {
|
||||||
|
type: OptionsActions.UPDATE,
|
||||||
|
data: {id: {value: 1}}
|
||||||
|
})).toEqual({...OptionsMetaActions.defaultState, id: {value: 1}})
|
||||||
|
});
|
@ -105,7 +105,7 @@ export const testState: RootState = {
|
|||||||
viewIndex: {}, // TODO: incomplete
|
viewIndex: {}, // TODO: incomplete
|
||||||
},
|
},
|
||||||
commandBar: {
|
commandBar: {
|
||||||
visible: true,
|
visible: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import {render as rtlRender} from '@testing-library/react'
|
import {render as rtlRender} from '@testing-library/react'
|
||||||
|
import userEvent from '@testing-library/user-event'
|
||||||
|
import "@testing-library/jest-dom"
|
||||||
import {Provider} from 'react-redux'
|
import {Provider} from 'react-redux'
|
||||||
// Import your own reducer
|
// Import your own reducer
|
||||||
import {createAppStore} from '../ducks'
|
import {createAppStore} from '../ducks'
|
||||||
@ -9,6 +11,9 @@ import {testState} from "./ducks/tutils";
|
|||||||
export {
|
export {
|
||||||
waitFor, fireEvent, act, screen
|
waitFor, fireEvent, act, screen
|
||||||
} from '@testing-library/react'
|
} from '@testing-library/react'
|
||||||
|
export {
|
||||||
|
userEvent
|
||||||
|
}
|
||||||
|
|
||||||
export function render(
|
export function render(
|
||||||
ui,
|
ui,
|
||||||
|
@ -1,8 +1,25 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react'
|
import React, {useEffect, useRef, useState} from 'react'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import { Key, fetchApi } from '../utils'
|
import {fetchApi, Key, runCommand} from '../utils'
|
||||||
import Filt from '../filt/command'
|
import Filt from '../filt/command'
|
||||||
|
|
||||||
|
type CommandParameter = {
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
kind: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Command = {
|
||||||
|
help?: string
|
||||||
|
parameters: CommandParameter[]
|
||||||
|
return_type: string | undefined
|
||||||
|
signature_help: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AllCommands = {
|
||||||
|
[name: string]: Command
|
||||||
|
}
|
||||||
|
|
||||||
type CommandHelpProps = {
|
type CommandHelpProps = {
|
||||||
nextArgs: string[],
|
nextArgs: string[],
|
||||||
currentArg: number,
|
currentArg: number,
|
||||||
@ -12,16 +29,15 @@ type CommandHelpProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CommandResult = {
|
type CommandResult = {
|
||||||
"id": number,
|
command: string,
|
||||||
"command": string,
|
result: string,
|
||||||
"result": string,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResultProps = {
|
type ResultProps = {
|
||||||
results: CommandResult[],
|
results: CommandResult[],
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAvailableCommands(commands: object, input: string = "") {
|
function getAvailableCommands(commands: AllCommands, input: string = ""): string[] {
|
||||||
if (!commands) return []
|
if (!commands) return []
|
||||||
let availableCommands: string[] = []
|
let availableCommands: string[] = []
|
||||||
for (const [command, args] of Object.entries(commands)) {
|
for (const [command, args] of Object.entries(commands)) {
|
||||||
@ -33,21 +49,21 @@ function getAvailableCommands(commands: object, input: string = "") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Results({results}: ResultProps) {
|
export function Results({results}: ResultProps) {
|
||||||
const resultElement= useRef<HTMLDivElement>(null!);
|
const resultElement = useRef<HTMLDivElement>(null!);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (resultElement) {
|
if (resultElement) {
|
||||||
resultElement.current.addEventListener('DOMNodeInserted', (event) => {
|
resultElement.current.addEventListener('DOMNodeInserted', (event) => {
|
||||||
const target = event.currentTarget as Element;
|
const target = event.currentTarget as Element;
|
||||||
target.scroll({ top: target.scrollHeight, behavior: 'auto' });
|
target.scroll({top: target.scrollHeight, behavior: 'auto'});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="command-result" ref={resultElement}>
|
<div className="command-result" ref={resultElement}>
|
||||||
{results.map(result => (
|
{results.map((result, i) => (
|
||||||
<div key={result.id}>
|
<div key={i}>
|
||||||
<div><strong>$ {result.command}</strong></div>
|
<div><strong>$ {result.command}</strong></div>
|
||||||
{result.result}
|
{result.result}
|
||||||
</div>
|
</div>
|
||||||
@ -56,22 +72,23 @@ export function Results({results}: ResultProps) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CommandHelp({nextArgs, currentArg, help, description, availableCommands}: CommandHelpProps){
|
export function CommandHelp({nextArgs, currentArg, help, description, availableCommands}: CommandHelpProps) {
|
||||||
let argumentSuggestion: JSX.Element[] = []
|
let argumentSuggestion: JSX.Element[] = []
|
||||||
for (let i: number = 0; i < nextArgs.length; i++) {
|
for (let i: number = 0; i < nextArgs.length; i++) {
|
||||||
if (i==currentArg) {
|
if (i == currentArg) {
|
||||||
argumentSuggestion.push(<mark>{nextArgs[i]}</mark>)
|
argumentSuggestion.push(<mark key={i}>{nextArgs[i]}</mark>)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
argumentSuggestion.push(<span>{nextArgs[i]} </span>)
|
argumentSuggestion.push(<span key={i}>{nextArgs[i]} </span>)
|
||||||
}
|
}
|
||||||
return (<div className="argument-suggestion popover top">
|
return (<div className="argument-suggestion popover top">
|
||||||
<div className="arrow"/>
|
<div className="arrow"/>
|
||||||
<div className="popover-content">
|
<div className="popover-content">
|
||||||
{ argumentSuggestion.length > 0 && <div><strong>Argument suggestion:</strong> {argumentSuggestion}</div> }
|
{argumentSuggestion.length > 0 && <div><strong>Argument suggestion:</strong> {argumentSuggestion}</div>}
|
||||||
{ help?.includes("->") && <div><strong>Signature help: </strong>{help}</div>}
|
{help?.includes("->") && <div><strong>Signature help: </strong>{help}</div>}
|
||||||
{ description && <div># {description}</div>}
|
{description && <div># {description}</div>}
|
||||||
<div><strong>Available Commands: </strong><p className="available-commands">{JSON.stringify(availableCommands)}</p></div>
|
<div><strong>Available Commands: </strong><p
|
||||||
|
className="available-commands">{JSON.stringify(availableCommands)}</p></div>
|
||||||
</div>
|
</div>
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
||||||
@ -83,7 +100,7 @@ export default function CommandBar() {
|
|||||||
const [completionCandidate, setCompletionCandidate] = useState<string[]>([])
|
const [completionCandidate, setCompletionCandidate] = useState<string[]>([])
|
||||||
|
|
||||||
const [availableCommands, setAvailableCommands] = useState<string[]>([])
|
const [availableCommands, setAvailableCommands] = useState<string[]>([])
|
||||||
const [allCommands, setAllCommands] = useState<object>({})
|
const [allCommands, setAllCommands] = useState<AllCommands>({})
|
||||||
const [nextArgs, setNextArgs] = useState<string[]>([])
|
const [nextArgs, setNextArgs] = useState<string[]>([])
|
||||||
const [currentArg, setCurrentArg] = useState<number>(0)
|
const [currentArg, setCurrentArg] = useState<number>(0)
|
||||||
const [signatureHelp, setSignatureHelp] = useState<string>("")
|
const [signatureHelp, setSignatureHelp] = useState<string>("")
|
||||||
@ -91,33 +108,39 @@ export default function CommandBar() {
|
|||||||
|
|
||||||
const [results, setResults] = useState<CommandResult[]>([])
|
const [results, setResults] = useState<CommandResult[]>([])
|
||||||
const [history, setHistory] = useState<string[]>([])
|
const [history, setHistory] = useState<string[]>([])
|
||||||
const [currentPos, setCurrentPos] = useState<number>(0)
|
const [currentPos, setCurrentPos] = useState<number | undefined>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchApi('/commands', { method: 'GET' })
|
fetchApi('/commands', {method: 'GET'})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then((data: AllCommands) => {
|
||||||
setAllCommands(data["commands"])
|
setAllCommands(data)
|
||||||
setCompletionCandidate(getAvailableCommands(data["commands"]))
|
setCompletionCandidate(getAvailableCommands(data))
|
||||||
setAvailableCommands(Object.keys(data))
|
setAvailableCommands(Object.keys(data))
|
||||||
}).catch(e => console.error(e))
|
}).catch(e => console.error(e))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
runCommand("commands.history.get").then((ret) => {
|
||||||
|
setHistory(ret.value);
|
||||||
|
}).catch(e => console.error(e))
|
||||||
|
}, [])
|
||||||
|
|
||||||
const parseCommand = (originalInput: string, input: string) => {
|
const parseCommand = (originalInput: string, input: string) => {
|
||||||
const parts: string[] = Filt.parse(input)
|
const parts: string[] = Filt.parse(input)
|
||||||
const originalParts: string[] = Filt.parse(originalInput)
|
const originalParts: string[] = Filt.parse(originalInput)
|
||||||
|
|
||||||
setSignatureHelp(allCommands[parts[0]]?.signature_help)
|
setSignatureHelp(allCommands[parts[0]]?.signature_help)
|
||||||
setDescription(allCommands[parts[0]]?.description)
|
setDescription(allCommands[parts[0]]?.help || "")
|
||||||
|
|
||||||
setCompletionCandidate(getAvailableCommands(allCommands, originalParts[0]))
|
setCompletionCandidate(getAvailableCommands(allCommands, originalParts[0]))
|
||||||
setAvailableCommands(getAvailableCommands(allCommands, parts[0]))
|
setAvailableCommands(getAvailableCommands(allCommands, parts[0]))
|
||||||
|
|
||||||
const nextArgs: string[] = allCommands[parts[0]]?.args
|
const nextArgs: string[] = allCommands[parts[0]]?.parameters.map(p => p.name)
|
||||||
|
|
||||||
if (nextArgs) {
|
if (nextArgs) {
|
||||||
setNextArgs([parts[0], ...nextArgs])
|
setNextArgs([parts[0], ...nextArgs])
|
||||||
setCurrentArg(parts.length-1)
|
setCurrentArg(parts.length - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,25 +152,27 @@ export default function CommandBar() {
|
|||||||
|
|
||||||
const onKeyDown = (e) => {
|
const onKeyDown = (e) => {
|
||||||
if (e.keyCode === Key.ENTER) {
|
if (e.keyCode === Key.ENTER) {
|
||||||
const body = {"command": input}
|
const [cmd, ...args] = Filt.parse(input);
|
||||||
|
|
||||||
fetchApi(`/commands`, {
|
setHistory([...history, input]);
|
||||||
method: 'POST',
|
runCommand("commands.history.add", input).catch(() => 0);
|
||||||
body: JSON.stringify(body),
|
|
||||||
headers: {
|
fetchApi.post(`/commands/${cmd}`, {arguments: args})
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
setHistory(data.history)
|
setCurrentPos(undefined)
|
||||||
setCurrentPos(currentPos + 1)
|
|
||||||
setNextArgs([])
|
setNextArgs([])
|
||||||
setResults([...results, {
|
setResults([...results, {
|
||||||
"id": results.length,
|
command: input,
|
||||||
"command": input,
|
result: JSON.stringify(data.value || data.error)
|
||||||
"result": JSON.stringify(data.result)
|
|
||||||
}])
|
}])
|
||||||
|
}).catch(e => {
|
||||||
|
setCurrentPos(undefined)
|
||||||
|
setNextArgs([])
|
||||||
|
setResults([...results, {
|
||||||
|
command: input,
|
||||||
|
result: e.toString()
|
||||||
|
}]);
|
||||||
})
|
})
|
||||||
|
|
||||||
setSignatureHelp("")
|
setSignatureHelp("")
|
||||||
@ -160,17 +185,28 @@ export default function CommandBar() {
|
|||||||
setCompletionCandidate(availableCommands)
|
setCompletionCandidate(availableCommands)
|
||||||
}
|
}
|
||||||
if (e.keyCode === Key.UP) {
|
if (e.keyCode === Key.UP) {
|
||||||
if (currentPos > 0) {
|
let nextPos;
|
||||||
setInput(history[currentPos - 1])
|
if (currentPos === undefined) {
|
||||||
setOriginalInput(history[currentPos -1])
|
nextPos = history.length - 1;
|
||||||
setCurrentPos(currentPos - 1)
|
} else {
|
||||||
|
nextPos = Math.max(0, currentPos - 1);
|
||||||
}
|
}
|
||||||
|
setInput(history[nextPos])
|
||||||
|
setOriginalInput(history[nextPos])
|
||||||
|
setCurrentPos(nextPos)
|
||||||
}
|
}
|
||||||
if (e.keyCode === Key.DOWN) {
|
if (e.keyCode === Key.DOWN) {
|
||||||
setInput(history[currentPos])
|
if (currentPos === undefined) {
|
||||||
setOriginalInput(history[currentPos])
|
return
|
||||||
if (currentPos < history.length -1) {
|
} else if (currentPos == history.length - 1) {
|
||||||
setCurrentPos(currentPos + 1)
|
setInput("");
|
||||||
|
setOriginalInput("");
|
||||||
|
setCurrentPos(undefined);
|
||||||
|
} else {
|
||||||
|
const nextPos = currentPos + 1;
|
||||||
|
setInput(history[nextPos])
|
||||||
|
setOriginalInput(history[nextPos])
|
||||||
|
setCurrentPos(nextPos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (e.keyCode === Key.TAB) {
|
if (e.keyCode === Key.TAB) {
|
||||||
@ -195,8 +231,9 @@ export default function CommandBar() {
|
|||||||
<div className="command-title">
|
<div className="command-title">
|
||||||
Command Result
|
Command Result
|
||||||
</div>
|
</div>
|
||||||
<Results results={results} />
|
<Results results={results}/>
|
||||||
<CommandHelp nextArgs={nextArgs} currentArg={currentArg} help={signatureHelp} description={description} availableCommands={availableCommands} />
|
<CommandHelp nextArgs={nextArgs} currentArg={currentArg} help={signatureHelp} description={description}
|
||||||
|
availableCommands={availableCommands}/>
|
||||||
<div className={classnames('command-input input-group')}>
|
<div className={classnames('command-input input-group')}>
|
||||||
<span className="input-group-addon">
|
<span className="input-group-addon">
|
||||||
<i className={'fa fa-fw fa-terminal'}/>
|
<i className={'fa fa-fw fa-terminal'}/>
|
||||||
@ -205,7 +242,7 @@ export default function CommandBar() {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="Enter command"
|
placeholder="Enter command"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
value={input}
|
value={input || ""}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
onKeyUp={onKeyUp}
|
onKeyUp={onKeyUp}
|
||||||
|
@ -6,7 +6,7 @@ interface CommandBarState {
|
|||||||
visible: boolean
|
visible: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultState: CommandBarState = {
|
export const defaultState: CommandBarState = {
|
||||||
visible: false,
|
visible: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -112,16 +112,11 @@ export function makeFilter(filter?: string): FlowFilterFn | undefined {
|
|||||||
return Filt.parse(filter)
|
return Filt.parse(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeSort({column, desc}: { column: keyof typeof FlowColumns, desc: boolean }): FlowSortFn;
|
export function makeSort({column, desc}: { column?: keyof typeof FlowColumns, desc: boolean }): FlowSortFn {
|
||||||
export function makeSort({column, desc}: { column?: keyof typeof FlowColumns, desc: boolean }): FlowSortFn | undefined;
|
|
||||||
export function makeSort({column, desc}: { column?: keyof typeof FlowColumns, desc: boolean }): FlowSortFn | undefined {
|
|
||||||
if (!column) {
|
if (!column) {
|
||||||
return
|
return (a,b) => 0;
|
||||||
}
|
}
|
||||||
const sortKeyFun = FlowColumns[column].sortKey
|
const sortKeyFun = FlowColumns[column].sortKey
|
||||||
if (!sortKeyFun) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return (a, b) => {
|
return (a, b) => {
|
||||||
const ka = sortKeyFun(a)
|
const ka = sortKeyFun(a)
|
||||||
const kb = sortKeyFun(b)
|
const kb = sortKeyFun(b)
|
||||||
|
@ -49,7 +49,7 @@ export async function pureSendUpdate(option: Option, value, dispatch) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let sendUpdate = _.throttle(pureSendUpdate, 500, {leading: true, trailing: true})
|
let sendUpdate = pureSendUpdate; // _.throttle(pureSendUpdate, 500, {leading: true, trailing: true})
|
||||||
|
|
||||||
export function update(name: Option, value: any): AppThunk {
|
export function update(name: Option, value: any): AppThunk {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
|
@ -14,7 +14,7 @@ type OptionsMetaState = Partial<{
|
|||||||
[name in keyof OptionsState]: OptionMeta<OptionsState[name]>
|
[name in keyof OptionsState]: OptionMeta<OptionsState[name]>
|
||||||
}>
|
}>
|
||||||
|
|
||||||
const defaultState: OptionsMetaState = {
|
export const defaultState: OptionsMetaState = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const reducer: Reducer<OptionsMetaState> = (state = defaultState, action) => {
|
const reducer: Reducer<OptionsMetaState> = (state = defaultState, action) => {
|
||||||
|
@ -116,6 +116,19 @@ fetchApi.put = (url: string, json: any, options: RequestInit = {}) => fetchApi(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
fetchApi.post = (url: string, json: any, options: RequestInit = {}) => fetchApi(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(json),
|
||||||
|
...options
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export async function runCommand(command: string, ...args): Promise<any> {
|
export async function runCommand(command: string, ...args): Promise<any> {
|
||||||
let response = await fetchApi(`/commands/${command}`, {
|
let response = await fetchApi(`/commands/${command}`, {
|
||||||
method: 'POST', headers: {
|
method: 'POST', headers: {
|
||||||
|
Loading…
Reference in New Issue
Block a user