mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-21 22:58:24 +00:00
web: test coverage++, adjust commandbar
This commit is contained in:
parent
2945ba925b
commit
46cd40f493
@ -529,11 +529,17 @@ class ExecuteCommand(RequestHandler):
|
||||
args = self.json['arguments']
|
||||
except APIError:
|
||||
args = []
|
||||
result = self.master.commands.call_strings(cmd, args)
|
||||
self.write({
|
||||
"value": result,
|
||||
# "type": command.typename(type(result)) if result is not None else "none"
|
||||
})
|
||||
try:
|
||||
result = self.master.commands.call_strings(cmd, args)
|
||||
except Exception as e:
|
||||
self.write({
|
||||
"error": str(e)
|
||||
})
|
||||
else:
|
||||
self.write({
|
||||
"value": result,
|
||||
# "type": command.typename(type(result)) if result is not None else "none"
|
||||
})
|
||||
|
||||
|
||||
class Events(RequestHandler):
|
||||
|
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",
|
||||
"coveragePathIgnorePatterns": [
|
||||
"<rootDir>/src/js/filt/filt.js",
|
||||
"<rootDir>/src/js/filt/command.js"
|
||||
"<rootDir>/src/js/contrib/",
|
||||
"<rootDir>/src/js/filt/",
|
||||
"<rootDir>/src/js/components/editors/"
|
||||
],
|
||||
"collectCoverageFrom": [
|
||||
"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": {
|
||||
"version": "7.14.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.14.6.tgz",
|
||||
"integrity": "sha512-Xl8SPYtdjcMoCsIM4teyVRg7jIcgl8F2kRtoCcXuHzXswt9UxZCS6BzRo8fcnCuP6u2XtPgvyonmEPF57Kxo9Q==",
|
||||
"version": "7.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.15.3.tgz",
|
||||
"integrity": "sha512-30A3lP+sRL6ml8uhoJSs+8jwpKzbw8CqBvDc1laeptxPm5FahumJxirigcbD2qTs71Sonvj1cyZB0OKGAmxQ+A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-js-pure": "^3.14.0",
|
||||
"core-js-pure": "^3.16.0",
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
@ -1025,9 +1025,9 @@
|
||||
}
|
||||
},
|
||||
"@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==",
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.1.0.tgz",
|
||||
"integrity": "sha512-kmW9alndr19qd6DABzQ978zKQ+J65gU2Rzkl8hriIetPnwpesRaK4//jEQyYh8fEALmGhomD/LBQqt+o+DL95Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
@ -1037,7 +1037,71 @@
|
||||
"chalk": "^4.1.0",
|
||||
"dom-accessibility-api": "^0.5.6",
|
||||
"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": {
|
||||
"@jest/types": {
|
||||
@ -1053,10 +1117,26 @@
|
||||
"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": {
|
||||
"version": "15.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
|
||||
"integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
|
||||
"version": "15.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz",
|
||||
"integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/yargs-parser": "*"
|
||||
@ -1076,14 +1156,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@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==",
|
||||
"@testing-library/user-event": {
|
||||
"version": "13.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.2.1.tgz",
|
||||
"integrity": "sha512-cczlgVl+krjOb3j1625usarNEibI0IFRJrSWX9UsJ1HKYFgCQv9Nb7QAipUDXl3Xdz8NDTsiS78eAkPSxlzTlw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@testing-library/dom": "^7.28.1"
|
||||
"@babel/runtime": "^7.12.5"
|
||||
}
|
||||
},
|
||||
"@tootallnate/once": {
|
||||
@ -1093,9 +1172,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/aria-query": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.1.tgz",
|
||||
"integrity": "sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz",
|
||||
"integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/babel__core": {
|
||||
@ -1336,6 +1415,15 @@
|
||||
"integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==",
|
||||
"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": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.4.tgz",
|
||||
@ -2652,9 +2740,9 @@
|
||||
}
|
||||
},
|
||||
"core-js-pure": {
|
||||
"version": "3.15.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.15.0.tgz",
|
||||
"integrity": "sha512-RO+LFAso8DB6OeBX9BAcEGvyth36QtxYon1OyVsITNVtSKr/Hos0BXZwnsOJ7o+O6KHtK+O+cJIEj9NGg6VwFA==",
|
||||
"version": "3.16.2",
|
||||
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.16.2.tgz",
|
||||
"integrity": "sha512-oxKe64UH049mJqrKkynWp6Vu0Rlm/BTXO/bJZuN2mmR3RtOFNepLlSWDd1eo16PzHpQAoNG97rLU1V/YxesJjw==",
|
||||
"dev": true
|
||||
},
|
||||
"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": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
|
||||
@ -2954,9 +3048,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"dom-accessibility-api": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.6.tgz",
|
||||
"integrity": "sha512-DplGLZd8L1lN64jlT27N9TVSESFR5STaEJvX+thCby7fuCHonfPpAlodYc3vuUYbDuDec5w8AMP7oCM5TWFsqw==",
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.7.tgz",
|
||||
"integrity": "sha512-ml3lJIq9YjUfM9TUnEPvEYWFSwivwIGBPKpewX7tii7fwCazA8yCioGdqQcNsItPpfFvSJ3VIdMQPj60LJhcQA==",
|
||||
"dev": true
|
||||
},
|
||||
"domexception": {
|
||||
@ -6666,6 +6760,12 @@
|
||||
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
||||
"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": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
|
@ -23,7 +23,10 @@
|
||||
"stable": "^0.1.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/dom": "^8.1.0",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^13.2.1",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/redux-mock-store": "^1.0.2",
|
||||
"esbuild": "^0.12.9",
|
||||
|
@ -1,26 +1,67 @@
|
||||
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 fetchMock, {enableFetchMocks} from "jest-fetch-mock";
|
||||
|
||||
enableFetchMocks();
|
||||
|
||||
test("CommandBar", async () => {
|
||||
fetchMock.mockResponseOnce(JSON.stringify({
|
||||
"flow.decode": {"help": "Decode flows.",
|
||||
"parameters": [{"name": "flows", "type": "flow[]", "kind": "POSITIONAL_OR_KEYWORD"}, {
|
||||
"name": "part",
|
||||
"type": "str",
|
||||
"kind": "POSITIONAL_OR_KEYWORD"
|
||||
}],
|
||||
fetchMock.mockOnceIf("./commands", JSON.stringify({
|
||||
"flow.decode": {
|
||||
"help": "Decode flows.",
|
||||
"parameters": [
|
||||
{"name": "flows", "type": "flow[]", "kind": "POSITIONAL_OR_KEYWORD"},
|
||||
{"name": "part", "type": "str", "kind": "POSITIONAL_OR_KEYWORD"}
|
||||
],
|
||||
"return_type": null,
|
||||
"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/>);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
await waitFor(() => screen.getByText('["flow.decode"]'))
|
||||
await waitFor(() => screen.getByText('["flow.decode","flow.encode"]'))
|
||||
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>
|
||||
<input
|
||||
checked={true}
|
||||
checked={false}
|
||||
onChange={[Function]}
|
||||
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
|
||||
class="available-commands"
|
||||
>
|
||||
["flow.decode"]
|
||||
["flow.decode","flow.encode"]
|
||||
</p>
|
||||
</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 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 () => {
|
||||
const onEditDone = jest.fn();
|
||||
@ -9,8 +9,7 @@ test("ValidateEditor", async () => {
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
fireEvent.mouseDown(screen.getByText("ok"));
|
||||
fireEvent.mouseUp(screen.getByText("ok"));
|
||||
userEvent.click(screen.getByText("ok"));
|
||||
|
||||
screen.getByText("ok").innerHTML = "this is ok";
|
||||
|
||||
@ -19,8 +18,7 @@ test("ValidateEditor", async () => {
|
||||
await waitFor(() => expect(onEditDone).toBeCalledWith("this is ok"));
|
||||
onEditDone.mockClear();
|
||||
|
||||
fireEvent.mouseDown(screen.getByText("this is ok"));
|
||||
fireEvent.mouseUp(screen.getByText("this is ok"));
|
||||
userEvent.click(screen.getByText("this is ok"));
|
||||
screen.getByText("this is ok").innerHTML = "wat";
|
||||
fireEvent.blur(screen.getByText("wat"));
|
||||
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", () => {
|
||||
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
|
||||
const sort = flowActions.makeSort({column, desc: true});
|
||||
const sort = flowActions.makeSort({column, desc: i % 2 == 0});
|
||||
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
|
||||
},
|
||||
commandBar: {
|
||||
visible: true,
|
||||
visible: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
import * as React from "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 your own reducer
|
||||
import {createAppStore} from '../ducks'
|
||||
@ -9,6 +11,9 @@ import {testState} from "./ducks/tutils";
|
||||
export {
|
||||
waitFor, fireEvent, act, screen
|
||||
} from '@testing-library/react'
|
||||
export {
|
||||
userEvent
|
||||
}
|
||||
|
||||
export function render(
|
||||
ui,
|
||||
|
@ -1,8 +1,25 @@
|
||||
import React, { useState, useEffect, useRef } from 'react'
|
||||
import React, {useEffect, useRef, useState} from 'react'
|
||||
import classnames from 'classnames'
|
||||
import { Key, fetchApi } from '../utils'
|
||||
import {fetchApi, Key, runCommand} from '../utils'
|
||||
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 = {
|
||||
nextArgs: string[],
|
||||
currentArg: number,
|
||||
@ -12,16 +29,15 @@ type CommandHelpProps = {
|
||||
}
|
||||
|
||||
type CommandResult = {
|
||||
"id": number,
|
||||
"command": string,
|
||||
"result": string,
|
||||
command: string,
|
||||
result: string,
|
||||
}
|
||||
|
||||
type ResultProps = {
|
||||
results: CommandResult[],
|
||||
}
|
||||
|
||||
function getAvailableCommands(commands: object, input: string = "") {
|
||||
function getAvailableCommands(commands: AllCommands, input: string = ""): string[] {
|
||||
if (!commands) return []
|
||||
let availableCommands: string[] = []
|
||||
for (const [command, args] of Object.entries(commands)) {
|
||||
@ -33,21 +49,21 @@ function getAvailableCommands(commands: object, input: string = "") {
|
||||
}
|
||||
|
||||
export function Results({results}: ResultProps) {
|
||||
const resultElement= useRef<HTMLDivElement>(null!);
|
||||
const resultElement = useRef<HTMLDivElement>(null!);
|
||||
|
||||
useEffect(() => {
|
||||
if (resultElement) {
|
||||
resultElement.current.addEventListener('DOMNodeInserted', (event) => {
|
||||
const target = event.currentTarget as Element;
|
||||
target.scroll({ top: target.scrollHeight, behavior: 'auto' });
|
||||
target.scroll({top: target.scrollHeight, behavior: 'auto'});
|
||||
});
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="command-result" ref={resultElement}>
|
||||
{results.map(result => (
|
||||
<div key={result.id}>
|
||||
{results.map((result, i) => (
|
||||
<div key={i}>
|
||||
<div><strong>$ {result.command}</strong></div>
|
||||
{result.result}
|
||||
</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[] = []
|
||||
for (let i: number = 0; i < nextArgs.length; i++) {
|
||||
if (i==currentArg) {
|
||||
argumentSuggestion.push(<mark>{nextArgs[i]}</mark>)
|
||||
if (i == currentArg) {
|
||||
argumentSuggestion.push(<mark key={i}>{nextArgs[i]}</mark>)
|
||||
continue
|
||||
}
|
||||
argumentSuggestion.push(<span>{nextArgs[i]} </span>)
|
||||
argumentSuggestion.push(<span key={i}>{nextArgs[i]} </span>)
|
||||
}
|
||||
return (<div className="argument-suggestion popover top">
|
||||
<div className="arrow"/>
|
||||
<div className="popover-content">
|
||||
{ argumentSuggestion.length > 0 && <div><strong>Argument suggestion:</strong> {argumentSuggestion}</div> }
|
||||
{ help?.includes("->") && <div><strong>Signature help: </strong>{help}</div>}
|
||||
{ description && <div># {description}</div>}
|
||||
<div><strong>Available Commands: </strong><p className="available-commands">{JSON.stringify(availableCommands)}</p></div>
|
||||
{argumentSuggestion.length > 0 && <div><strong>Argument suggestion:</strong> {argumentSuggestion}</div>}
|
||||
{help?.includes("->") && <div><strong>Signature help: </strong>{help}</div>}
|
||||
{description && <div># {description}</div>}
|
||||
<div><strong>Available Commands: </strong><p
|
||||
className="available-commands">{JSON.stringify(availableCommands)}</p></div>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
@ -83,7 +100,7 @@ export default function CommandBar() {
|
||||
const [completionCandidate, setCompletionCandidate] = useState<string[]>([])
|
||||
|
||||
const [availableCommands, setAvailableCommands] = useState<string[]>([])
|
||||
const [allCommands, setAllCommands] = useState<object>({})
|
||||
const [allCommands, setAllCommands] = useState<AllCommands>({})
|
||||
const [nextArgs, setNextArgs] = useState<string[]>([])
|
||||
const [currentArg, setCurrentArg] = useState<number>(0)
|
||||
const [signatureHelp, setSignatureHelp] = useState<string>("")
|
||||
@ -91,15 +108,21 @@ export default function CommandBar() {
|
||||
|
||||
const [results, setResults] = useState<CommandResult[]>([])
|
||||
const [history, setHistory] = useState<string[]>([])
|
||||
const [currentPos, setCurrentPos] = useState<number>(0)
|
||||
const [currentPos, setCurrentPos] = useState<number | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
fetchApi('/commands', { method: 'GET' })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
setAllCommands(data["commands"])
|
||||
setCompletionCandidate(getAvailableCommands(data["commands"]))
|
||||
setAvailableCommands(Object.keys(data))
|
||||
fetchApi('/commands', {method: 'GET'})
|
||||
.then(response => response.json())
|
||||
.then((data: AllCommands) => {
|
||||
setAllCommands(data)
|
||||
setCompletionCandidate(getAvailableCommands(data))
|
||||
setAvailableCommands(Object.keys(data))
|
||||
}).catch(e => console.error(e))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
runCommand("commands.history.get").then((ret) => {
|
||||
setHistory(ret.value);
|
||||
}).catch(e => console.error(e))
|
||||
}, [])
|
||||
|
||||
@ -108,20 +131,20 @@ export default function CommandBar() {
|
||||
const originalParts: string[] = Filt.parse(originalInput)
|
||||
|
||||
setSignatureHelp(allCommands[parts[0]]?.signature_help)
|
||||
setDescription(allCommands[parts[0]]?.description)
|
||||
setDescription(allCommands[parts[0]]?.help || "")
|
||||
|
||||
setCompletionCandidate(getAvailableCommands(allCommands, originalParts[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) {
|
||||
setNextArgs([parts[0], ...nextArgs])
|
||||
setCurrentArg(parts.length-1)
|
||||
setCurrentArg(parts.length - 1)
|
||||
}
|
||||
}
|
||||
|
||||
const onChange = (e) => {
|
||||
const onChange = (e) => {
|
||||
setInput(e.target.value)
|
||||
setOriginalInput(e.target.value)
|
||||
setCurrentCompletion(0)
|
||||
@ -129,25 +152,27 @@ export default function CommandBar() {
|
||||
|
||||
const onKeyDown = (e) => {
|
||||
if (e.keyCode === Key.ENTER) {
|
||||
const body = {"command": input}
|
||||
const [cmd, ...args] = Filt.parse(input);
|
||||
|
||||
fetchApi(`/commands`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
setHistory(data.history)
|
||||
setCurrentPos(currentPos + 1)
|
||||
setHistory([...history, input]);
|
||||
runCommand("commands.history.add", input).catch(() => 0);
|
||||
|
||||
fetchApi.post(`/commands/${cmd}`, {arguments: args})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
setCurrentPos(undefined)
|
||||
setNextArgs([])
|
||||
setResults([...results, {
|
||||
command: input,
|
||||
result: JSON.stringify(data.value || data.error)
|
||||
}])
|
||||
}).catch(e => {
|
||||
setCurrentPos(undefined)
|
||||
setNextArgs([])
|
||||
setResults([...results, {
|
||||
"id": results.length,
|
||||
"command": input,
|
||||
"result": JSON.stringify(data.result)
|
||||
}])
|
||||
command: input,
|
||||
result: e.toString()
|
||||
}]);
|
||||
})
|
||||
|
||||
setSignatureHelp("")
|
||||
@ -160,17 +185,28 @@ export default function CommandBar() {
|
||||
setCompletionCandidate(availableCommands)
|
||||
}
|
||||
if (e.keyCode === Key.UP) {
|
||||
if (currentPos > 0) {
|
||||
setInput(history[currentPos - 1])
|
||||
setOriginalInput(history[currentPos -1])
|
||||
setCurrentPos(currentPos - 1)
|
||||
let nextPos;
|
||||
if (currentPos === undefined) {
|
||||
nextPos = history.length - 1;
|
||||
} else {
|
||||
nextPos = Math.max(0, currentPos - 1);
|
||||
}
|
||||
setInput(history[nextPos])
|
||||
setOriginalInput(history[nextPos])
|
||||
setCurrentPos(nextPos)
|
||||
}
|
||||
if (e.keyCode === Key.DOWN) {
|
||||
setInput(history[currentPos])
|
||||
setOriginalInput(history[currentPos])
|
||||
if (currentPos < history.length -1) {
|
||||
setCurrentPos(currentPos + 1)
|
||||
if (currentPos === undefined) {
|
||||
return
|
||||
} else if (currentPos == history.length - 1) {
|
||||
setInput("");
|
||||
setOriginalInput("");
|
||||
setCurrentPos(undefined);
|
||||
} else {
|
||||
const nextPos = currentPos + 1;
|
||||
setInput(history[nextPos])
|
||||
setOriginalInput(history[nextPos])
|
||||
setCurrentPos(nextPos)
|
||||
}
|
||||
}
|
||||
if (e.keyCode === Key.TAB) {
|
||||
@ -195,8 +231,9 @@ export default function CommandBar() {
|
||||
<div className="command-title">
|
||||
Command Result
|
||||
</div>
|
||||
<Results results={results} />
|
||||
<CommandHelp nextArgs={nextArgs} currentArg={currentArg} help={signatureHelp} description={description} availableCommands={availableCommands} />
|
||||
<Results results={results}/>
|
||||
<CommandHelp nextArgs={nextArgs} currentArg={currentArg} help={signatureHelp} description={description}
|
||||
availableCommands={availableCommands}/>
|
||||
<div className={classnames('command-input input-group')}>
|
||||
<span className="input-group-addon">
|
||||
<i className={'fa fa-fw fa-terminal'}/>
|
||||
@ -205,7 +242,7 @@ export default function CommandBar() {
|
||||
type="text"
|
||||
placeholder="Enter command"
|
||||
className="form-control"
|
||||
value={input}
|
||||
value={input || ""}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
onKeyUp={onKeyUp}
|
||||
|
@ -6,7 +6,7 @@ interface CommandBarState {
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
const defaultState: CommandBarState = {
|
||||
export const defaultState: CommandBarState = {
|
||||
visible: false,
|
||||
};
|
||||
|
||||
|
@ -112,16 +112,11 @@ export function makeFilter(filter?: string): FlowFilterFn | undefined {
|
||||
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 | undefined;
|
||||
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 {
|
||||
if (!column) {
|
||||
return
|
||||
return (a,b) => 0;
|
||||
}
|
||||
const sortKeyFun = FlowColumns[column].sortKey
|
||||
if (!sortKeyFun) {
|
||||
return
|
||||
}
|
||||
return (a, b) => {
|
||||
const ka = sortKeyFun(a)
|
||||
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 {
|
||||
return dispatch => {
|
||||
|
@ -14,7 +14,7 @@ type OptionsMetaState = Partial<{
|
||||
[name in keyof OptionsState]: OptionMeta<OptionsState[name]>
|
||||
}>
|
||||
|
||||
const defaultState: OptionsMetaState = {
|
||||
export const defaultState: OptionsMetaState = {
|
||||
}
|
||||
|
||||
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> {
|
||||
let response = await fetchApi(`/commands/${command}`, {
|
||||
method: 'POST', headers: {
|
||||
|
Loading…
Reference in New Issue
Block a user