fix web tests

This commit is contained in:
Maximilian Hils 2021-08-19 18:21:25 +02:00
parent 0585e690c4
commit 3ad4949c0e
116 changed files with 2678 additions and 3914 deletions

View File

@ -1,4 +1,5 @@
import uuid import uuid
from typing import Literal, Union
from mitmproxy import connection from mitmproxy import connection
from mitmproxy import controller from mitmproxy import controller
@ -66,14 +67,8 @@ def twebsocketflow(messages=True, err=None, close_code=None, close_reason='') ->
timestamp_start=946681202, timestamp_start=946681202,
timestamp_end=946681203, timestamp_end=946681203,
) )
flow.websocket = websocket.WebSocketData()
if messages is True: flow.websocket = twebsocket()
flow.websocket.messages = [
websocket.WebSocketMessage(Opcode.BINARY, True, b"hello binary", 946681203),
websocket.WebSocketMessage(Opcode.TEXT, True, b"hello text", 946681204),
websocket.WebSocketMessage(Opcode.TEXT, False, b"it's me", 946681205),
]
flow.websocket.close_reason = close_reason flow.websocket.close_reason = close_reason
@ -91,15 +86,15 @@ def twebsocketflow(messages=True, err=None, close_code=None, close_reason='') ->
return flow return flow
def tflow(client_conn=True, server_conn=True, req=True, resp=None, err=None) -> http.HTTPFlow: def tflow(
""" client_conn: Union[Literal[True], None, connection.Client] = True,
@type client_conn: bool | None | mitmproxy.proxy.connection.ClientConnection server_conn: Union[Literal[True], None, connection.Server] = True,
@type server_conn: bool | None | mitmproxy.proxy.connection.ServerConnection req: Union[Literal[True], None, http.Request] = True,
@type req: bool | None | mitmproxy.proxy.protocol.http.Request resp: Union[Literal[True], None, http.Response] = None,
@type resp: bool | None | mitmproxy.proxy.protocol.http.Response err: Union[Literal[True], None, flow.Error] = None,
@type err: bool | None | mitmproxy.proxy.protocol.primitives.Error ws: Union[Literal[True], None, websocket.WebSocketData] = None,
@return: mitmproxy.proxy.protocol.http.HTTPFlow ) -> http.HTTPFlow:
""" """Create a flow for testing."""
if client_conn is True: if client_conn is True:
client_conn = tclient_conn() client_conn = tclient_conn()
if server_conn is True: if server_conn is True:
@ -110,11 +105,14 @@ def tflow(client_conn=True, server_conn=True, req=True, resp=None, err=None) ->
resp = tresp() resp = tresp()
if err is True: if err is True:
err = terr() err = terr()
if ws is True:
ws = twebsocket()
f = http.HTTPFlow(client_conn, server_conn) f = http.HTTPFlow(client_conn, server_conn)
f.request = req f.request = req
f.response = resp f.response = resp
f.error = err f.error = err
f.websocket = ws
f.reply = controller.DummyReply() f.reply = controller.DummyReply()
return f return f
@ -197,3 +195,20 @@ def tserver_conn() -> connection.Server:
def terr(content: str = "error") -> flow.Error: def terr(content: str = "error") -> flow.Error:
err = flow.Error(content, 946681207) err = flow.Error(content, 946681207)
return err return err
def twebsocket(messages: bool = True) -> websocket.WebSocketData:
ws = websocket.WebSocketData()
if messages:
ws.messages = [
websocket.WebSocketMessage(Opcode.BINARY, True, b"hello binary", 946681203),
websocket.WebSocketMessage(Opcode.TEXT, True, b"hello text", 946681204),
websocket.WebSocketMessage(Opcode.TEXT, False, b"it's me", 946681205),
]
ws.close_reason = "Close Reason"
ws.close_code = 1000
ws.closed_by_client = False
ws.timestamp_end = 946681205
return ws

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -324,7 +324,7 @@ class TestApp(tornado.testing.AsyncHTTPTestCase):
ws_client2.close() ws_client2.close()
def test_generate_tflow_js(self): def test_generate_tflow_js(self):
tf = tflow.tflow(resp=True, err=True) tf = tflow.tflow(resp=True, err=True, ws=True)
tf.request.trailers = Headers(trailer="qvalue") tf.request.trailers = Headers(trailer="qvalue")
tf.response.trailers = Headers(trailer="qvalue") tf.response.trailers = Headers(trailer="qvalue")
@ -342,9 +342,9 @@ class TestApp(tornado.testing.AsyncHTTPTestCase):
).replace(": null", ": undefined") ).replace(": null", ": undefined")
content = ( content = (
"/** Auto-generated by test_app.py:TestApp._test_generate_tflow_js */\n" "/** Auto-generated by test_app.py:TestApp.test_generate_tflow_js */\n"
"import {HTTPFlow} from '../../flow';\n" "import {HTTPFlow} from '../../flow';\n"
"export default function(): HTTPFlow {\n" "export default function(): Required<HTTPFlow> {\n"
f" return {tflow_json}\n" f" return {tflow_json}\n"
"}" "}"
) )

133
web/package-lock.json generated
View File

@ -2651,12 +2651,6 @@
} }
} }
}, },
"core-js": {
"version": "3.15.2",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.15.2.tgz",
"integrity": "sha512-tKs41J7NJVuaya8DxIOCnl8QuPHx5/ZVbFo1oKgVl1qHFBBrDctzQGtuLjPpRdNTWmKPH6oEvgN/MUID+l485Q==",
"dev": true
},
"core-js-pure": { "core-js-pure": {
"version": "3.15.0", "version": "3.15.0",
"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.15.0.tgz",
@ -2669,6 +2663,15 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true "dev": true
}, },
"cross-fetch": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz",
"integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==",
"dev": true,
"requires": {
"node-fetch": "2.6.1"
}
},
"cross-spawn": { "cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -3521,52 +3524,6 @@
"bser": "^2.0.0" "bser": "^2.0.0"
} }
}, },
"fetch-mock": {
"version": "9.11.0",
"resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.11.0.tgz",
"integrity": "sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q==",
"dev": true,
"requires": {
"@babel/core": "^7.0.0",
"@babel/runtime": "^7.0.0",
"core-js": "^3.0.0",
"debug": "^4.1.1",
"glob-to-regexp": "^0.4.0",
"is-subset": "^0.1.1",
"lodash.isequal": "^4.5.0",
"path-to-regexp": "^2.2.1",
"querystring": "^0.2.0",
"whatwg-url": "^6.5.0"
},
"dependencies": {
"tr46": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
"integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
"dev": true,
"requires": {
"punycode": "^2.1.0"
}
},
"webidl-conversions": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
"integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
"dev": true
},
"whatwg-url": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz",
"integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==",
"dev": true,
"requires": {
"lodash.sortby": "^4.7.0",
"tr46": "^1.0.1",
"webidl-conversions": "^4.0.2"
}
}
}
},
"fill-range": { "fill-range": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
@ -3864,12 +3821,6 @@
"unique-stream": "^2.0.2" "unique-stream": "^2.0.2"
} }
}, },
"glob-to-regexp": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"dev": true
},
"glob-watcher": { "glob-watcher": {
"version": "5.0.3", "version": "5.0.3",
"resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz",
@ -5191,12 +5142,6 @@
"integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==",
"dev": true "dev": true
}, },
"is-subset": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz",
"integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=",
"dev": true
},
"is-typedarray": { "is-typedarray": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
@ -5535,6 +5480,16 @@
"jest-util": "^27.0.2" "jest-util": "^27.0.2"
} }
}, },
"jest-fetch-mock": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz",
"integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==",
"dev": true,
"requires": {
"cross-fetch": "^3.0.4",
"promise-polyfill": "^8.1.3"
}
},
"jest-get-type": { "jest-get-type": {
"version": "27.0.1", "version": "27.0.1",
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz",
@ -6313,12 +6268,6 @@
"integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=",
"dev": true "dev": true
}, },
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
"dev": true
},
"lodash.isobject": { "lodash.isobject": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz",
@ -6331,7 +6280,8 @@
"lodash.isplainobject": { "lodash.isplainobject": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=",
"dev": true
}, },
"lodash.keys": { "lodash.keys": {
"version": "2.4.1", "version": "2.4.1",
@ -6362,12 +6312,6 @@
"integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=",
"dev": true "dev": true
}, },
"lodash.sortby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
"integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=",
"dev": true
},
"lodash.template": { "lodash.template": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
@ -6768,11 +6712,6 @@
"minimist": "^1.2.5" "minimist": "^1.2.5"
} }
}, },
"mock-xmlhttprequest": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mock-xmlhttprequest/-/mock-xmlhttprequest-1.2.0.tgz",
"integrity": "sha512-iCP2jcd8WUrswkminVid7gL3PQ1hhD2UnfEV0dkQjdZmvLPS8mVhIooX1sooGF8/8RkVp4wwVI+wj7zo2S7seQ=="
},
"ms": { "ms": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@ -7294,12 +7233,6 @@
"integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=",
"dev": true "dev": true
}, },
"path-to-regexp": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz",
"integrity": "sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==",
"dev": true
},
"path-type": { "path-type": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
@ -7494,6 +7427,12 @@
"asap": "~2.0.3" "asap": "~2.0.3"
} }
}, },
"promise-polyfill": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.0.tgz",
"integrity": "sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g==",
"dev": true
},
"prompts": { "prompts": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz",
@ -7578,12 +7517,6 @@
"side-channel": "^1.0.4" "side-channel": "^1.0.4"
} }
}, },
"querystring": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz",
"integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==",
"dev": true
},
"raw-body": { "raw-body": {
"version": "1.1.7", "version": "1.1.7",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz",
@ -7621,7 +7554,8 @@
"react-is": { "react-is": {
"version": "17.0.2", "version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true
}, },
"react-popper": { "react-popper": {
"version": "2.2.5", "version": "2.2.5",
@ -7682,6 +7616,7 @@
"version": "16.14.1", "version": "16.14.1",
"resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz", "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz",
"integrity": "sha512-rkIMcQi01/+kxiTE9D3fdS959U1g7gs+/rborw++42m1O9FAQiNI/UNRZExVUoAOprn4umcXf+pFRou8i4zuBg==", "integrity": "sha512-rkIMcQi01/+kxiTE9D3fdS959U1g7gs+/rborw++42m1O9FAQiNI/UNRZExVUoAOprn4umcXf+pFRou8i4zuBg==",
"dev": true,
"requires": { "requires": {
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
"react-is": "^16.12.0 || ^17.0.0" "react-is": "^16.12.0 || ^17.0.0"
@ -7691,6 +7626,7 @@
"version": "17.0.2", "version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.2.tgz",
"integrity": "sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==", "integrity": "sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==",
"dev": true,
"requires": { "requires": {
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
"react-is": "^17.0.2", "react-is": "^17.0.2",
@ -7843,6 +7779,7 @@
"version": "1.5.4", "version": "1.5.4",
"resolved": "https://registry.npmjs.org/redux-mock-store/-/redux-mock-store-1.5.4.tgz", "resolved": "https://registry.npmjs.org/redux-mock-store/-/redux-mock-store-1.5.4.tgz",
"integrity": "sha512-xmcA0O/tjCLXhh9Fuiq6pMrJCwFRaouA8436zcikdIpYWWCjU76CRk+i2bHx8EeiSiMGnB85/lZdU3wIJVXHTA==", "integrity": "sha512-xmcA0O/tjCLXhh9Fuiq6pMrJCwFRaouA8436zcikdIpYWWCjU76CRk+i2bHx8EeiSiMGnB85/lZdU3wIJVXHTA==",
"dev": true,
"requires": { "requires": {
"lodash.isplainobject": "^4.0.6" "lodash.isplainobject": "^4.0.6"
} }
@ -9212,6 +9149,12 @@
"is-typedarray": "^1.0.0" "is-typedarray": "^1.0.0"
} }
}, },
"typescript": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
"integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
"dev": true
},
"uglify-js": { "uglify-js": {
"version": "2.8.29", "version": "2.8.29",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",

View File

@ -5,7 +5,7 @@
"verbose": true "verbose": true
}, },
"scripts": { "scripts": {
"test": "jest --coverage", "test": "tsc --noEmit && jest --coverage",
"build": "gulp prod", "build": "gulp prod",
"start": "gulp" "start": "gulp"
}, },
@ -15,15 +15,12 @@
"classnames": "^2.3.1", "classnames": "^2.3.1",
"codemirror": "^5.62.0", "codemirror": "^5.62.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mock-xmlhttprequest": "^1.1.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-popper": "^2.2.5", "react-popper": "^2.2.5",
"react-redux": "^7.2.4", "react-redux": "^7.2.4",
"react-test-renderer": "^17.0.2",
"redux": "^4.1.0", "redux": "^4.1.0",
"redux-mock-store": "^1.5.4",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"shallowequal": "^1.1.0", "shallowequal": "^1.1.0",
"stable": "^0.1.8" "stable": "^0.1.8"
@ -34,7 +31,6 @@
"@types/redux-mock-store": "^1.0.2", "@types/redux-mock-store": "^1.0.2",
"esbuild": "^0.12.9", "esbuild": "^0.12.9",
"esbuild-jest": "^0.5.0", "esbuild-jest": "^0.5.0",
"fetch-mock": "^9.11.0",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"gulp-clean-css": "^4.3.0", "gulp-clean-css": "^4.3.0",
"gulp-esbuild": "^0.8.2", "gulp-esbuild": "^0.8.2",
@ -46,7 +42,10 @@
"gulp-replace": "^1.1.3", "gulp-replace": "^1.1.3",
"gulp-sourcemaps": "^3.0.0", "gulp-sourcemaps": "^3.0.0",
"jest": "^27.0.4", "jest": "^27.0.4",
"node-fetch": "^2.6.1", "jest-fetch-mock": "^3.0.3",
"through2": "^4.0.2" "react-test-renderer": "^17.0.2",
"redux-mock-store": "^1.5.4",
"through2": "^4.0.2",
"typescript": "^4.3.5"
} }
} }

View File

@ -1,11 +1,14 @@
import * as React from "react" import * as React from "react"
import CommandBar from '../../../components/CommandBar' import CommandBar from '../../../components/CommandBar'
import { render } from "../../test-utils" import {render} from "../../test-utils"
import fetchMock from 'fetch-mock'; import fetchMock, {enableFetchMocks} from "jest-fetch-mock";
import { act, waitFor } from '@testing-library/react' import {waitFor} from '@testing-library/react'
enableFetchMocks()
test('CommandBar Component', async () => { test('CommandBar Component', async () => {
fetchMock.get('./commands.json', {status: 200, body: {"commands": "foo"}}) fetchMock.mockResponseOnce(JSON.stringify({"commands": "foo"}));
const {asFragment, store} = render( const {asFragment, store} = render(
<CommandBar/> <CommandBar/>

View File

@ -0,0 +1,26 @@
import * as React from "react"
import {render, waitFor, screen} 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"
}],
"return_type": null,
"signature_help": "flow.decode flows part"
}
}
));
const {asFragment} = render(<CommandBar/>);
expect(asFragment()).toMatchSnapshot();
await waitFor(() => screen.getByText('["flow.decode"]'))
expect(asFragment()).toMatchSnapshot();
});

View File

@ -1,14 +0,0 @@
jest.mock("../../../contrib/CodeMirror")
import * as React from 'react';
import CodeEditor from '../../../components/ContentView/CodeEditor'
import {render} from '@testing-library/react'
test("CodeEditor", async () => {
const changeFn = jest.fn(),
{asFragment} = render(
<CodeEditor content="foo" onChange={changeFn}/>
);
expect(asFragment()).toMatchSnapshot()
});

View File

@ -1,74 +0,0 @@
import * as React from "react"
import renderer from 'react-test-renderer'
import withContentLoader from '../../../components/contentviews/useContent'
import { TFlow } from '../../ducks/tutils'
import TestUtils from 'react-dom/test-utils'
import mockXMLHttpRequest from 'mock-xmlhttprequest'
global.XMLHttpRequest = mockXMLHttpRequest
class tComponent extends React.Component {
constructor(props, context){
super(props, context)
}
render() {
return (<p>foo</p>)
}
}
let tflow = new TFlow(),
ContentLoader = withContentLoader(tComponent)
describe('ContentLoader Component', () => {
it('should render correctly', () => {
let contentLoader = renderer.create(<ContentLoader flow={tflow} message={tflow.response}/>),
tree = contentLoader.toJSON()
expect(tree).toMatchSnapshot()
})
let contentLoader = TestUtils.renderIntoDocument(<ContentLoader flow={tflow} message={tflow.response}/>)
it('should handle updateContent', () => {
tflow.response.content = 'foo'
contentLoader.updateContent({flow: tflow, message: tflow.response})
expect(contentLoader.state.request).toEqual(undefined)
expect(contentLoader.state.content).toEqual('foo')
// when content length is 0 or null
tflow.response.contentLength = 0
tflow.response.content = undefined
contentLoader.updateContent({flow: tflow, message: tflow.response})
expect(contentLoader.state.request).toEqual(undefined)
expect(contentLoader.state.content).toEqual('')
})
it('should handle componentWillReceiveProps', () => {
contentLoader.updateContent = jest.fn()
contentLoader.UNSAFE_componentWillReceiveProps({flow: tflow, message: tflow.request})
expect(contentLoader.updateContent).toBeCalled()
})
it('should handle requestComplete', () => {
expect(contentLoader.requestComplete(tflow.request, {})).toEqual(undefined)
// request == this.state.request
contentLoader.state.request = tflow.request
contentLoader.requestComplete(tflow.request, {})
expect(contentLoader.state.content).toEqual(tflow.request.responseText)
expect(contentLoader.state.request).toEqual(undefined)
})
it('should handle requestFailed', () => {
console.error = jest.fn()
expect(contentLoader.requestFailed(tflow.request, {})).toEqual(undefined)
//request == this.state.request
contentLoader.state.request = tflow.request
contentLoader.requestFailed(tflow.request, 'foo error')
expect(contentLoader.state.content).toEqual('Error getting content.')
expect(contentLoader.state.request).toEqual(undefined)
expect(console.error).toBeCalledWith('foo error')
})
it('should handle componentWillUnmount', () => {
contentLoader.state.request = { abort : jest.fn() }
contentLoader.componentWillUnmount()
expect(contentLoader.state.request.abort).toBeCalled()
})
})

View File

@ -1,20 +0,0 @@
import * as React from "react"
import renderer from 'react-test-renderer'
import ContentViewOptions from '../../../components/ContentView/ContentViewOptions'
import { Provider } from 'react-redux'
import { TFlow, TStore } from '../../ducks/tutils'
import { uploadContent } from '../../../ducks/flows'
let tflow = new TFlow()
describe('ContentViewOptions Component', () => {
let store = TStore()
it('should render correctly', () => {
let provider = renderer.create(
<Provider store={store}>
<ContentViewOptions flow={tflow} message={tflow.response} uploadContent={uploadContent}/>
</Provider>),
tree = provider.toJSON()
expect(tree).toMatchSnapshot()
})
})

View File

@ -1,60 +0,0 @@
import * as React from "react"
import renderer from 'react-test-renderer'
import TestUtils from 'react-dom/test-utils'
import { Provider } from 'react-redux'
import { ViewServer, ViewImage, PureViewServer, Edit } from '../../../components/ContentView/ContentViews'
import { TFlow, TStore } from '../../ducks/tutils'
import mockXMLHttpRequest from 'mock-xmlhttprequest'
window.XMLHttpRequest = mockXMLHttpRequest
let tflow = new TFlow()
describe('ViewImage Component', () => {
let viewImage = renderer.create(<ViewImage flow={tflow} message={tflow.response}/>),
tree = viewImage.toJSON()
it('should render correctly', () => {
expect(tree).toMatchSnapshot()
})
})
describe('ViewServer Component', () => {
let store = TStore()
it('should render correctly and connect to state', () => {
let provider = renderer.create(
<Provider store={store}>
<ViewServer
flow={tflow}
message={tflow.response}
/>
</Provider>),
tree = provider.toJSON()
expect(tree).toMatchSnapshot()
let viewServer = renderer.create(
<Provider store={store}>
<PureViewServer
flow={tflow}
message={tflow.response}
content={JSON.stringify({lines: [['k1', 'v1']]})}
/>
</Provider>
)
tree = viewServer.toJSON()
expect(tree).toMatchSnapshot()
})
})
describe('Edit Component', () => {
it('should render correctly', () => {
let edit = renderer.create(<Edit
content="foo"
onChange={jest.fn}
flow={tflow}
message={tflow.response}
/>),
tree = edit.toJSON()
expect(tree).toMatchSnapshot()
})
})

View File

@ -1,15 +0,0 @@
import * as React from "react"
import renderer from 'react-test-renderer'
import DownloadContentButton from '../../../components/ContentView/DownloadContentButton'
import { TFlow } from '../../ducks/tutils'
let tflow = new TFlow()
describe('DownloadContentButton Component', () => {
it('should render correctly', () => {
let downloadContentButton = renderer.create(
<DownloadContentButton flow={tflow} message={tflow.response}/>
),
tree = downloadContentButton.toJSON()
expect(tree).toMatchSnapshot()
})
})

View File

@ -1,37 +0,0 @@
import * as React from "react"
import renderer from 'react-test-renderer'
import { ContentEmpty, ContentMissing, ContentTooLarge } from '../../../components/ContentView/MetaViews'
import { TFlow } from '../../ducks/tutils'
let tflow = new TFlow()
describe('ContentEmpty Components', () => {
it('should render correctly', () => {
let contentEmpty = renderer.create(<ContentEmpty flow={tflow} message={tflow.response}/>),
tree = contentEmpty.toJSON()
expect(tree).toMatchSnapshot()
})
})
describe('ContentMissing Components', () => {
it('should render correctly', () => {
let contentMissing = renderer.create(<ContentMissing flow={tflow} message={tflow.response}/>),
tree = contentMissing.toJSON()
expect(tree).toMatchSnapshot()
})
})
describe('ContentTooLarge Components', () => {
it('should render correctly', () => {
let clickFn = jest.fn(),
uploadContentFn = jest.fn(),
contentTooLarge = renderer.create(<ContentTooLarge
flow={tflow}
message={tflow.response}
onClick={clickFn}
uploadContent={uploadContentFn}
/>),
tree = contentTooLarge.toJSON()
expect(tree).toMatchSnapshot()
})
})

View File

@ -1,22 +0,0 @@
import * as React from "react"
import renderer from 'react-test-renderer'
import { Provider } from 'react-redux'
import ShowFullContentButton from '../../../components/ContentView/ShowFullContentButton'
import { TStore } from '../../ducks/tutils'
describe('ShowFullContentButton Component', () => {
let store = TStore()
let setShowFullContentFn = jest.fn(),
showFullContentButton = renderer.create(
<Provider store={store}>
<ShowFullContentButton />
</Provider>
),
tree = showFullContentButton.toJSON()
it('should render correctly', () => {
expect(tree).toMatchSnapshot()
})
})

View File

@ -1,12 +0,0 @@
import * as React from "react"
import renderer from 'react-test-renderer'
import UploadContentButton from '../../../components/ContentView/UploadContentButton'
describe('UpdateContentButton Component', () => {
it('should render correctly', () => {
let uploadContentFn = jest.fn(),
uploadContentButton = renderer.create(<UploadContentButton uploadContent={uploadContentFn}/>),
tree = uploadContentButton.toJSON()
expect(tree).toMatchSnapshot()
})
})

View File

@ -1,20 +0,0 @@
import * as React from "react"
import renderer from 'react-test-renderer'
import ViewSelector from '../../../components/ContentView/ViewSelector'
import { Provider } from 'react-redux'
import { TStore } from '../../ducks/tutils'
describe('ViewSelector Component', () => {
let store = TStore(),
viewSelector = renderer.create(
<Provider store={store}>
<ViewSelector/>
</Provider>
),
tree = viewSelector.toJSON()
it('should render correctly', () => {
expect(tree).toMatchSnapshot()
})
})

View File

@ -1,11 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ContentLoader Component should render correctly 1`] = `
<div
className="text-center"
>
<i
className="fa fa-spinner fa-spin"
/>
</div>
`;

View File

@ -1,41 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ContentViewOptions Component should render correctly 1`] = `
<div
className="view-options"
>
<span>
<b>
View:
</b>
edit
</span>
 
<a
className="btn btn-default btn-xs"
href="./flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/response/content.data"
title="Download the content of the flow."
>
<i
className="fa fa-download"
/>
</a>
 
<a
className="btn btn-default btn-xs"
href="#"
onClick={[Function]}
title="Upload a file to replace the content."
>
<i
className="fa fa-fw fa-upload"
/>
<input
className="hidden"
onChange={[Function]}
type="file"
/>
</a>
 
</div>
`;

View File

@ -1,39 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Edit Component should render correctly 1`] = `
<div
className="text-center"
>
<i
className="fa fa-spinner fa-spin"
/>
</div>
`;
exports[`ViewImage Component should render correctly 1`] = `
<div
className="flowview-image"
>
<img
alt="preview"
className="img-thumbnail"
src="./flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/response/content.data"
/>
</div>
`;
exports[`ViewServer Component should render correctly and connect to state 1`] = `
<div
className="text-center"
>
<i
className="fa fa-spinner fa-spin"
/>
</div>
`;
exports[`ViewServer Component should render correctly and connect to state 2`] = `
<div>
<pre />
</div>
`;

View File

@ -1,13 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DownloadContentButton Component should render correctly 1`] = `
<a
className="btn btn-default btn-xs"
href="./flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/response/content.data"
title="Download the content of the flow."
>
<i
className="fa fa-download"
/>
</a>
`;

View File

@ -1,66 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ContentEmpty Components should render correctly 1`] = `
<div
className="alert alert-info"
>
No
response
content.
</div>
`;
exports[`ContentMissing Components should render correctly 1`] = `
<div
className="alert alert-info"
>
Response
content missing.
</div>
`;
exports[`ContentTooLarge Components should render correctly 1`] = `
<div>
<div
className="alert alert-warning"
>
<button
className="btn btn-xs btn-warning pull-right"
onClick={[MockFunction]}
>
Display anyway
</button>
7b
content size.
</div>
<div
className="view-options text-center"
>
<a
className="btn btn-default btn-xs"
href="#"
onClick={[Function]}
title="Upload a file to replace the content."
>
<i
className="fa fa-fw fa-upload"
/>
<input
className="hidden"
onChange={[Function]}
type="file"
/>
</a>
 
<a
className="btn btn-default btn-xs"
href="./flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/response/content.data"
title="Download the content of the flow."
>
<i
className="fa fa-download"
/>
</a>
</div>
</div>
`;

View File

@ -1,3 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ShowFullContentButton Component should render correctly 1`] = `null`;

View File

@ -1,19 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`UpdateContentButton Component should render correctly 1`] = `
<a
className="btn btn-default btn-xs"
href="#"
onClick={[Function]}
title="Upload a file to replace the content."
>
<i
className="fa fa-fw fa-upload"
/>
<input
className="hidden"
onChange={[Function]}
type="file"
/>
</a>
`;

View File

@ -1,21 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ViewSelector Component should render correctly 1`] = `
<a
className="btn btn-default btn-xs pull-left"
href="#"
onClick={[Function]}
>
<span>
<b>
View:
</b>
auto
<span
className="caret"
/>
</span>
</a>
`;

View File

@ -1,62 +0,0 @@
import * as React from "react"
import renderer from 'react-test-renderer'
import ContentView from '../../components/ContentView'
import { TStore, TFlow } from '../ducks/tutils'
import { Provider } from 'react-redux'
import mockXMLHttpRequest from 'mock-xmlhttprequest'
window.XMLHttpRequest = mockXMLHttpRequest
describe('ContentView Component', () => {
let store = TStore()
it('should render correctly', () => {
store.getState().ui.flow.contentView = 'Edit'
let tflow = TFlow(),
provider = renderer.create(
<Provider store={store}>
<ContentView flow={tflow} message={tflow.request}/>
</Provider>),
tree = provider.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render correctly with empty content', () => {
let tflow = TFlow()
tflow.response.contentLength = 0
let provider = renderer.create(
<Provider store={store}>
<ContentView flow={tflow} message={tflow.response} readonly={true}/>
</Provider>),
tree = provider.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render correctly with missing content', () => {
let tflow = TFlow()
tflow.response.contentLength = null
let provider = renderer.create(
<Provider store={store}>
<ContentView flow={tflow} message={tflow.response} readonly={true}/>
</Provider>),
tree = provider.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render correctly with content too large', () => {
let tflow = TFlow()
tflow.response.contentLength = 1024 * 1024 * 100
let provider = renderer.create(
<Provider store={store}>
<ContentView
flow={tflow}
message={tflow.response}
readonly={true}
uploadContent={jest.fn()}
onOpenFile={jest.fn()}
/>
</Provider>),
tree = provider.toJSON()
expect(tree).toMatchSnapshot()
})
})

View File

@ -1,137 +0,0 @@
import * as React from "react"
import renderer from 'react-test-renderer'
import {
icon,
method,
path,
quickactions,
size,
status,
time,
timestamp,
tls
} from '../../../components/FlowTable/FlowColumns'
import {TFlow, TStore} from '../../ducks/tutils'
import {Provider} from 'react-redux'
describe('Flowcolumns Components', () => {
let tflow = TFlow()
it('should render TLSColumn', () => {
let tlsColumn = renderer.create(<tls flow={tflow}/>),
tree = tlsColumn.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render IconColumn', () => {
let tflow = TFlow(),
iconColumn = renderer.create(<icon flow={tflow}/>),
tree = iconColumn.toJSON()
// plain
expect(tree).toMatchSnapshot()
// not modified
tflow.response.status_code = 304
iconColumn = renderer.create(<icon flow={tflow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// redirect
tflow.response.status_code = 302
iconColumn = renderer.create(<icon flow={tflow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// image
let imageFlow = TFlow()
imageFlow.response.headers = [['Content-Type', 'image/jpeg']]
iconColumn = renderer.create(<icon flow={imageFlow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// javascript
let jsFlow = TFlow()
jsFlow.response.headers = [['Content-Type', 'application/x-javascript']]
iconColumn = renderer.create(<icon flow={jsFlow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// css
let cssFlow = TFlow()
cssFlow.response.headers = [['Content-Type', 'text/css']]
iconColumn = renderer.create(<icon flow={cssFlow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// html
let htmlFlow = TFlow()
htmlFlow.response.headers = [['Content-Type', 'text/html']]
iconColumn = renderer.create(<icon flow={htmlFlow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// default
let fooFlow = TFlow()
fooFlow.response.headers = [['Content-Type', 'foo']]
iconColumn = renderer.create(<icon flow={fooFlow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// no response
tflow.response = null
iconColumn = renderer.create(<icon flow={tflow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render pathColumn', () => {
let tflow = TFlow(),
pathColumn = renderer.create(<path flow={tflow}/>),
tree = pathColumn.toJSON()
expect(tree).toMatchSnapshot()
tflow.error.msg = 'Connection killed.'
tflow.intercepted = true
pathColumn = renderer.create(<path flow={tflow}/>)
tree = pathColumn.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render MethodColumn', () => {
let methodColumn = renderer.create(<method flow={tflow}/>),
tree = methodColumn.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render StatusColumn', () => {
let statusColumn = renderer.create(<status flow={tflow}/>),
tree = statusColumn.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render SizeColumn', () => {
let sizeColumn = renderer.create(<size flow={tflow}/>),
tree = sizeColumn.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render TimeColumn', () => {
let tflow = TFlow(),
timeColumn = renderer.create(<time flow={tflow}/>),
tree = timeColumn.toJSON()
expect(tree).toMatchSnapshot()
tflow.response = null
timeColumn = renderer.create(<time flow={tflow}/>)
tree = timeColumn.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render TimeStampColumn', () => {
let timeStampColumn = renderer.create(<timestamp flow={tflow}/>),
tree = timeStampColumn.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render QuickActionsColumn', () => {
let store = TStore(),
provider = renderer.create(
<Provider store={store}>
<quickactions flow={tflow}/>
</Provider>),
tree = provider.toJSON()
expect(tree).toMatchSnapshot()
})
})

View File

@ -0,0 +1,98 @@
import * as React from "react"
import renderer from 'react-test-renderer'
import FlowColumns from '../../../components/FlowTable/FlowColumns'
import {TFlow} from '../../ducks/tutils'
import {render} from "../../test-utils";
test("should render columns", async () => {
const tflow = TFlow();
Object.entries(FlowColumns).forEach(([name, Col]) => {
const {asFragment} = render(<table>
<tbody>
<tr><Col flow={tflow}/></tr>
</tbody>
</table>)
expect(asFragment()).toMatchSnapshot(name);
})
});
describe('Flowcolumns Components', () => {
it('should render IconColumn', () => {
let tflow = {...TFlow(), websocket: undefined},
iconColumn = renderer.create(<FlowColumns.icon flow={tflow}/>),
tree = iconColumn.toJSON()
// plain
expect(tree).toMatchSnapshot()
// not modified
tflow.response.status_code = 304
iconColumn = renderer.create(<FlowColumns.icon flow={tflow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// redirect
tflow.response.status_code = 302
iconColumn = renderer.create(<FlowColumns.icon flow={tflow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// image
let imageFlow = {...TFlow(), websocket: undefined}
imageFlow.response.headers = [['Content-Type', 'image/jpeg']]
iconColumn = renderer.create(<FlowColumns.icon flow={imageFlow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// javascript
let jsFlow = {...TFlow(), websocket: undefined}
jsFlow.response.headers = [['Content-Type', 'application/x-javascript']]
iconColumn = renderer.create(<FlowColumns.icon flow={jsFlow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// css
let cssFlow = {...TFlow(), websocket: undefined}
cssFlow.response.headers = [['Content-Type', 'text/css']]
iconColumn = renderer.create(<FlowColumns.icon flow={cssFlow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// html
let htmlFlow = {...TFlow(), websocket: undefined}
htmlFlow.response.headers = [['Content-Type', 'text/html']]
iconColumn = renderer.create(<FlowColumns.icon flow={htmlFlow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// default
let fooFlow = {...TFlow(), websocket: undefined}
fooFlow.response.headers = [['Content-Type', 'foo']]
iconColumn = renderer.create(<FlowColumns.icon flow={fooFlow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
// no response
let noResponseFlow = {...TFlow(), response: undefined}
iconColumn = renderer.create(<FlowColumns.icon flow={noResponseFlow}/>)
tree = iconColumn.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render pathColumn', () => {
let tflow = TFlow(),
pathColumn = renderer.create(<FlowColumns.path flow={tflow}/>),
tree = pathColumn.toJSON()
expect(tree).toMatchSnapshot()
tflow.error.msg = 'Connection killed.'
tflow.intercepted = true
pathColumn = renderer.create(<FlowColumns.path flow={tflow}/>)
tree = pathColumn.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render TimeColumn', () => {
let tflow = TFlow(),
timeColumn = renderer.create(<FlowColumns.time flow={tflow}/>),
tree = timeColumn.toJSON()
expect(tree).toMatchSnapshot()
let noResponseFlow = {...tflow, response: undefined}
timeColumn = renderer.create(<FlowColumns.time flow={noResponseFlow}/>)
tree = timeColumn.toJSON()
expect(tree).toMatchSnapshot()
})
})

View File

@ -7,15 +7,15 @@ import {createAppStore} from "../../../ducks";
test("FlowRow", async () => { test("FlowRow", async () => {
const store = createAppStore(testState), const store = createAppStore(testState),
tflow2 = store.getState().flows.view[1], tflow2 = store.getState().flows.list[0],
{asFragment} = render(<table> {asFragment} = render(<table>
<tbody> <tbody>
<FlowRow flow={tflow2} selected highlighted/> <FlowRow flow={tflow2} selected highlighted/>
</tbody> </tbody>
</table>, {store}) </table>, {store})
expect(asFragment()).toMatchSnapshot() expect(asFragment()).toMatchSnapshot()
expect(store.getState().flows.selected[0]).toBe(store.getState().flows.view[0].id)
fireEvent.click(screen.getByText("http://address:22/second")) expect(store.getState().flows.selected[0]).not.toBe(store.getState().flows.list[0].id)
expect(store.getState().flows.selected[0]).toBe(store.getState().flows.view[1].id) fireEvent.click(screen.getByText("http://address:22/path"))
expect(store.getState().flows.selected[0]).toBe(store.getState().flows.list[0].id)
}) })

View File

@ -1,203 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Flowcolumns Components should render IconColumn 1`] = `
<td
className="col-icon"
>
<div
className="resource-icon resource-icon-plain"
/>
</td>
`;
exports[`Flowcolumns Components should render IconColumn 2`] = `
<td
className="col-icon"
>
<div
className="resource-icon resource-icon-not-modified"
/>
</td>
`;
exports[`Flowcolumns Components should render IconColumn 3`] = `
<td
className="col-icon"
>
<div
className="resource-icon resource-icon-redirect"
/>
</td>
`;
exports[`Flowcolumns Components should render IconColumn 4`] = `
<td
className="col-icon"
>
<div
className="resource-icon resource-icon-image"
/>
</td>
`;
exports[`Flowcolumns Components should render IconColumn 5`] = `
<td
className="col-icon"
>
<div
className="resource-icon resource-icon-js"
/>
</td>
`;
exports[`Flowcolumns Components should render IconColumn 6`] = `
<td
className="col-icon"
>
<div
className="resource-icon resource-icon-css"
/>
</td>
`;
exports[`Flowcolumns Components should render IconColumn 7`] = `
<td
className="col-icon"
>
<div
className="resource-icon resource-icon-document"
/>
</td>
`;
exports[`Flowcolumns Components should render IconColumn 8`] = `
<td
className="col-icon"
>
<div
className="resource-icon resource-icon-plain"
/>
</td>
`;
exports[`Flowcolumns Components should render IconColumn 9`] = `
<td
className="col-icon"
>
<div
className="resource-icon resource-icon-plain"
/>
</td>
`;
exports[`Flowcolumns Components should render MethodColumn 1`] = `
<td
className="col-method"
>
GET
</td>
`;
exports[`Flowcolumns Components should render QuickActionsColumn 1`] = `
<td
className="col-quickactions"
onClick={[Function]}
>
<div>
<a
className="quickaction"
href="#"
onClick={[Function]}
>
<i
className="fa fa-fw fa-repeat text-primary"
/>
</a>
<a
className="quickaction"
href="#"
onClick={[Function]}
>
<i
className="fa fa-fw fa-ellipsis-h text-muted"
/>
</a>
</div>
</td>
`;
exports[`Flowcolumns Components should render SizeColumn 1`] = `
<td
className="col-size"
>
14b
</td>
`;
exports[`Flowcolumns Components should render StatusColumn 1`] = `
<td
className="col-status"
style={
Object {
"color": "darkgreen",
}
}
>
200
</td>
`;
exports[`Flowcolumns Components should render TLSColumn 1`] = `
<td
className="col-tls col-tls-https"
/>
`;
exports[`Flowcolumns Components should render TimeColumn 1`] = `
<td
className="col-time"
>
3s
</td>
`;
exports[`Flowcolumns Components should render TimeColumn 2`] = `
<td
className="col-time"
>
...
</td>
`;
exports[`Flowcolumns Components should render TimeStampColumn 1`] = `
<td
className="col-start"
>
1999-12-31 23:00:00.000
</td>
`;
exports[`Flowcolumns Components should render pathColumn 1`] = `
<td
className="col-path"
>
<i
className="fa fa-fw fa-exclamation pull-right"
/>
http://address:22/path
</td>
`;
exports[`Flowcolumns Components should render pathColumn 2`] = `
<td
className="col-path"
>
<i
className="fa fa-fw fa-pause pull-right"
/>
<i
className="fa fa-fw fa-times pull-right"
/>
http://address:22/path
</td>
`;

View File

@ -0,0 +1,310 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Flowcolumns Components should render IconColumn 1`] = `
<td
className="col-icon"
>
<div
className="resource-icon resource-icon-plain"
/>
</td>
`;
exports[`Flowcolumns Components should render IconColumn 2`] = `
<td
className="col-icon"
>
<div
className="resource-icon resource-icon-not-modified"
/>
</td>
`;
exports[`Flowcolumns Components should render IconColumn 3`] = `
<td
className="col-icon"
>
<div
className="resource-icon resource-icon-redirect"
/>
</td>
`;
exports[`Flowcolumns Components should render IconColumn 4`] = `
<td
className="col-icon"
>
<div
className="resource-icon resource-icon-image"
/>
</td>
`;
exports[`Flowcolumns Components should render IconColumn 5`] = `
<td
className="col-icon"
>
<div
className="resource-icon resource-icon-js"
/>
</td>
`;
exports[`Flowcolumns Components should render IconColumn 6`] = `
<td
className="col-icon"
>
<div
className="resource-icon resource-icon-css"
/>
</td>
`;
exports[`Flowcolumns Components should render IconColumn 7`] = `
<td
className="col-icon"
>
<div
className="resource-icon resource-icon-document"
/>
</td>
`;
exports[`Flowcolumns Components should render IconColumn 8`] = `
<td
className="col-icon"
>
<div
className="resource-icon resource-icon-plain"
/>
</td>
`;
exports[`Flowcolumns Components should render IconColumn 9`] = `
<td
className="col-icon"
>
<div
className="resource-icon resource-icon-plain"
/>
</td>
`;
exports[`Flowcolumns Components should render TimeColumn 1`] = `
<td
className="col-time"
>
5s
</td>
`;
exports[`Flowcolumns Components should render TimeColumn 2`] = `
<td
className="col-time"
>
5s
</td>
`;
exports[`Flowcolumns Components should render pathColumn 1`] = `
<td
className="col-path"
>
<i
className="fa fa-fw fa-exclamation pull-right"
/>
<span
className="marker pull-right"
>
</span>
http://address:22/path
</td>
`;
exports[`Flowcolumns Components should render pathColumn 2`] = `
<td
className="col-path"
>
<i
className="fa fa-fw fa-pause pull-right"
/>
<i
className="fa fa-fw fa-times pull-right"
/>
<span
className="marker pull-right"
>
</span>
http://address:22/path
</td>
`;
exports[`should render columns: icon 1`] = `
<DocumentFragment>
<table>
<tbody>
<tr>
<td
class="col-icon"
>
<div
class="resource-icon resource-icon-websocket"
/>
</td>
</tr>
</tbody>
</table>
</DocumentFragment>
`;
exports[`should render columns: method 1`] = `
<DocumentFragment>
<table>
<tbody>
<tr>
<td
class="col-method"
>
GET
</td>
</tr>
</tbody>
</table>
</DocumentFragment>
`;
exports[`should render columns: path 1`] = `
<DocumentFragment>
<table>
<tbody>
<tr>
<td
class="col-path"
>
<i
class="fa fa-fw fa-exclamation pull-right"
/>
<span
class="marker pull-right"
/>
http://address:22/path
</td>
</tr>
</tbody>
</table>
</DocumentFragment>
`;
exports[`should render columns: quickactions 1`] = `
<DocumentFragment>
<table>
<tbody>
<tr>
<td
class="col-quickactions"
>
<div>
<a
class="quickaction"
href="#"
>
<i
class="fa fa-fw fa-repeat text-primary"
/>
</a>
<a
class="quickaction"
href="#"
>
<i
class="fa fa-fw fa-ellipsis-h text-muted"
/>
</a>
</div>
</td>
</tr>
</tbody>
</table>
</DocumentFragment>
`;
exports[`should render columns: size 1`] = `
<DocumentFragment>
<table>
<tbody>
<tr>
<td
class="col-size"
>
14b
</td>
</tr>
</tbody>
</table>
</DocumentFragment>
`;
exports[`should render columns: status 1`] = `
<DocumentFragment>
<table>
<tbody>
<tr>
<td
class="col-status"
style="color: darkgreen;"
>
200
</td>
</tr>
</tbody>
</table>
</DocumentFragment>
`;
exports[`should render columns: time 1`] = `
<DocumentFragment>
<table>
<tbody>
<tr>
<td
class="col-time"
>
5s
</td>
</tr>
</tbody>
</table>
</DocumentFragment>
`;
exports[`should render columns: timestamp 1`] = `
<DocumentFragment>
<table>
<tbody>
<tr>
<td
class="col-start"
>
1999-12-31 23:00:00.000
</td>
</tr>
</tbody>
</table>
</DocumentFragment>
`;
exports[`should render columns: tls 1`] = `
<DocumentFragment>
<table>
<tbody>
<tr>
<td
class="col-tls col-tls-https"
/>
</tr>
</tbody>
</table>
</DocumentFragment>
`;

View File

@ -5,7 +5,7 @@ exports[`FlowRow 1`] = `
<table> <table>
<tbody> <tbody>
<tr <tr
class="selected highlighted has-request has-response" class="selected highlighted intercepted has-request has-response"
> >
<td <td
class="col-tls col-tls-https" class="col-tls col-tls-https"
@ -14,16 +14,22 @@ exports[`FlowRow 1`] = `
class="col-icon" class="col-icon"
> >
<div <div
class="resource-icon resource-icon-plain" class="resource-icon resource-icon-websocket"
/> />
</td> </td>
<td <td
class="col-path" class="col-path"
> >
<i
class="fa fa-fw fa-pause pull-right"
/>
<i <i
class="fa fa-fw fa-exclamation pull-right" class="fa fa-fw fa-exclamation pull-right"
/> />
http://address:22/second <span
class="marker pull-right"
/>
http://address:22/path
</td> </td>
<td <td
class="col-method" class="col-method"
@ -44,7 +50,7 @@ exports[`FlowRow 1`] = `
<td <td
class="col-time" class="col-time"
> >
3s 5s
</td> </td>
<td <td
class="col-quickactions" class="col-quickactions"
@ -55,7 +61,7 @@ exports[`FlowRow 1`] = `
href="#" href="#"
> >
<i <i
class="fa fa-fw fa-repeat text-primary" class="fa fa-fw fa-play text-success"
/> />
</a> </a>
<a <a

View File

@ -1,69 +0,0 @@
import * as React from "react"
import renderer from 'react-test-renderer'
import Connection, { TimeStamp, ConnectionInfo, CertificateInfo, Timing } from '../../../components/FlowView/Connection'
import { TFlow } from '../../ducks/tutils'
let tflow = TFlow()
describe('TimeStamp Component', () => {
it('should render correctly', () => {
let timestamp = renderer.create(<TimeStamp t={1483228800} deltaTo={1483228700} title="foo"/>),
tree = timestamp.toJSON()
expect(tree).toMatchSnapshot()
// without timestamp
timestamp = renderer.create(<TimeStamp/>)
tree = timestamp.toJSON()
expect(tree).toMatchSnapshot()
})
})
describe('ConnectionInfo Component', () => {
it('should render correctly', () => {
let connectionInfo = renderer.create(<ConnectionInfo conn={tflow.client_conn}/>),
tree = connectionInfo.toJSON()
expect(tree).toMatchSnapshot()
})
})
describe('CertificateInfo Component', () => {
it('should render correctly', () => {
let certificateInfo = renderer.create(<CertificateInfo flow={tflow}/>),
tree = certificateInfo.toJSON()
expect(tree).toMatchSnapshot()
})
})
describe('Timing Component', () => {
it('should render correctly', () => {
let timing = renderer.create(<Timing flow={tflow}/>),
tree = timing.toJSON()
expect(tree).toMatchSnapshot()
})
})
describe('Details Component', () => {
it('should render correctly', () => {
let details = renderer.create(<Connection flow={tflow}/>),
tree = details.toJSON()
expect(tree).toMatchSnapshot()
})
it('should render correctly when server address is missing', () => {
let tflowServerAddressNull = tflow
tflowServerAddressNull.server_conn.address = null
tflowServerAddressNull.server_conn.ip_address = null
tflowServerAddressNull.server_conn.sni = null
tflowServerAddressNull.server_conn.ssl_established = false
tflowServerAddressNull.server_conn.tls_version = null
tflowServerAddressNull.server_conn.timestamp_tcp_setup = null
tflowServerAddressNull.server_conn.timestamp_ssl_setup = null
tflowServerAddressNull.server_conn.timestamp_start = null
tflowServerAddressNull.server_conn.timestamp_end = null
let details = renderer.create(<Connection flow={tflowServerAddressNull}/>),
tree = details.toJSON()
expect(tree).toMatchSnapshot()
})
})

View File

@ -1,132 +0,0 @@
import * as React from "react"
import ReactDOM from 'react-dom'
import renderer from 'react-test-renderer'
import TestUtils from 'react-dom/test-utils'
import KeyValueListEditor, { HeaderEditor } from '../../../components/editors/KeyValueListEditor'
import { Key } from '../../../utils'
describe('HeaderEditor Component', () => {
it('should render correctly', () => {
let headerEditor = renderer.create(
<HeaderEditor content="foo" onDone={jest.fn()}/>),
tree = headerEditor.toJSON()
expect(tree).toMatchSnapshot()
})
let doneFn = jest.fn(),
removeFn = jest.fn(),
tabFn = jest.fn(),
headerEditor = TestUtils.renderIntoDocument(
<HeaderEditor content="foo" onDone={doneFn} onRemove={removeFn} onTab={tabFn}/>)
it('should handle focus', () => {
let focusFn = jest.fn()
ReactDOM.findDOMNode = jest.fn( node => {
return {focus: focusFn}
})
headerEditor.focus()
expect(ReactDOM.findDOMNode).toBeCalledWith(headerEditor)
expect(focusFn).toBeCalled()
})
it('should handle keyDown', () => {
let mockEvent = { keyCode: Key.BACKSPACE },
getRangeAt = jest.fn( s => {
return { startOffset: 0, endOffset: 0 }
})
window.getSelection = jest.fn(selection => {
return { getRangeAt }
})
// Backspace
headerEditor.onKeyDown(mockEvent)
expect(window.getSelection).toBeCalled()
expect(getRangeAt).toBeCalledWith(0)
expect(headerEditor.props.onRemove).toBeCalledWith(mockEvent)
// Enter & Tab
mockEvent.keyCode = Key.ENTER
headerEditor.onKeyDown(mockEvent)
expect(headerEditor.props.onTab).toBeCalledWith(mockEvent)
})
})
describe('Headers Component', () => {
let changeFn = jest.fn(),
mockMessage = { headers: [['k1', 'v1'], ['k2', '']] }
it('should handle correctly', () => {
let headers = renderer.create(<KeyValueListEditor onChange={changeFn} message={mockMessage}/>),
tree = headers.toJSON()
expect(tree).toMatchSnapshot()
})
let headers = TestUtils.renderIntoDocument(<KeyValueListEditor onChange={changeFn} message={mockMessage}/>),
headerEditors = TestUtils.scryRenderedComponentsWithType(headers, HeaderEditor),
key1Editor = headerEditors[0],
value1Editor = headerEditors[1],
key2Editor = headerEditors[2],
value2Editor = headerEditors[3]
it('should handle change on header name', () => {
key2Editor.props.onDone('')
expect(changeFn).toBeCalled()
expect(headers._nextSel).toEqual('0-value')
changeFn.mockClear()
})
it('should handle change on header value', () => {
value2Editor.props.onDone('')
expect(changeFn).toBeCalled()
expect(headers._nextSel).toEqual('0-value')
changeFn.mockClear()
})
let mockEvent = { preventDefault: jest.fn() }
it('should handle remove on header name', () => {
key2Editor.props.onRemove(mockEvent)
expect(mockEvent.preventDefault).toBeCalled()
mockEvent.preventDefault.mockClear()
})
it('should handle remove on header value', () => {
value2Editor.props.onRemove(mockEvent)
expect(mockEvent.preventDefault).toBeCalled()
mockEvent.preventDefault.mockClear()
})
it('should handle tab on header name', () => {
key1Editor.props.onTab(mockEvent)
expect(headers._nextSel).toEqual('0-value')
})
it('should handle tab on header value', () => {
value1Editor.props.onTab(mockEvent)
expect(headers._nextSel).toEqual('1-key')
value2Editor.props.onTab(mockEvent)
expect(mockEvent.preventDefault).toBeCalled()
expect(headers._nextSel).toEqual('2-key')
})
it('should handle componentDidUpdate', () => {
headers._nextSel = '1-value'
headers.refs['1-value'] = { focus: jest.fn() }
headers.componentDidUpdate()
expect(headers.refs['1-value'].focus).toBeCalled()
expect(headers._nextSel).toEqual(undefined)
})
it('should handle edit', () => {
headers.refs['0-key'] = { focus: jest.fn() }
headers.edit()
expect(headers.refs['0-key'].focus).toBeCalled()
})
it('should not delete last row when handle remove', () => {
mockMessage = { headers: [['', '']] }
headers = TestUtils.renderIntoDocument(<KeyValueListEditor onChange={changeFn} message={mockMessage}/>)
headers.onChange(0, 0, '')
expect(changeFn).toBeCalledWith([['Name', 'Value']])
})
})

View File

@ -1,146 +0,0 @@
jest.mock('../../../components/ContentView', () => () => null)
import * as React from "react"
import renderer from 'react-test-renderer'
import {ErrorView, Request, Response} from '../../../components/FlowView/HttpMessages'
import {Provider} from 'react-redux'
import {TFlow, TStore} from '../../ducks/tutils'
import {updateEdit} from '../../../ducks/ui/flow'
import {parseUrl} from '../../../flow/utils'
import ContentView from '../../../components/ContentView'
import ContentViewOptions from '../../../components/ContentView/ContentViewOptions'
import KeyValueListEditor from '../../../components/editors/KeyValueListEditor'
import ValueEditor from '../../../components/editors/ValueEditor'
global.fetch = jest.fn()
let tflow = new TFlow(),
store = TStore()
store.getState().ui.flow.modifiedFlow = false
describe('Request Component', () => {
afterEach(() => {
store.clearActions()
})
it('should render correctly', () => {
let provider = renderer.create(
<Provider store={store}>
<Request/>
</Provider>
),
tree = provider.toJSON()
expect(tree).toMatchSnapshot()
})
let provider = renderer.create(
<Provider store={store}>
<Request/>
</Provider>),
valueEditors = provider.root.findAllByType(ValueEditor)
it('should handle done on flow request method', () => {
let valueEditor = valueEditors[0]
valueEditor.props.onDone('foo')
expect(store.getActions()).toEqual([updateEdit({request: {method: 'foo'}})])
})
it('should handle done on flow request url', () => {
let valueEditor = valueEditors[1],
url = 'http://foo/bar'
valueEditor.props.onDone(url)
expect(store.getActions()).toEqual([updateEdit({request: {path: '', ...parseUrl(url)}})])
})
it('should handle done on flow request http version', () => {
let valueEditor = valueEditors[2]
valueEditor.props.onDone('HTTP/9.9')
expect(store.getActions()).toEqual([updateEdit({request: {http_version: 'HTTP/9.9'}})])
})
it('should handle change on flow request header', () => {
let headers = provider.root.findAllByType(KeyValueListEditor).filter(headers => headers.props.type === 'headers')[0]
headers.props.onChange('foo')
expect(store.getActions()).toEqual([updateEdit({request: {headers: 'foo'}})])
})
it('should handle change on flow request contentView', () => {
let contentView = provider.root.findByType(ContentView)
contentView.props.onContentChange('foo')
expect(store.getActions()).toEqual([updateEdit({request: {content: 'foo'}})])
})
it('should handle uploadContent on flow request ContentViewOptions', () => {
let contentViewOptions = provider.root.findByType(ContentViewOptions)
contentViewOptions.props.uploadContent('foo')
expect(fetch).toBeCalled()
fetch.mockClear()
})
})
describe('Response Component', () => {
afterEach(() => {
store.clearActions()
})
it('should render correctly', () => {
let provider = renderer.create(
<Provider store={store}>
<Response/>
</Provider>
),
tree = provider.toJSON()
expect(tree).toMatchSnapshot()
})
let provider = renderer.create(
<Provider store={store}>
<Response/>
</Provider>),
valueEditors = provider.root.findAllByType(ValueEditor)
it('should handle done on flow response http version', () => {
let valueEditor = valueEditors[0]
valueEditor.props.onDone('HTTP/9.9')
expect(store.getActions()).toEqual([updateEdit({response: {http_version: 'HTTP/9.9'}})])
})
it('should handle done on flow response status code', () => {
let valueEditor = valueEditors[1]
valueEditor.props.onDone('404')
expect(store.getActions()).toEqual([updateEdit({response: {code: parseInt('404')}})])
})
it('should handle done on flow response reason', () => {
let valueEdiotr = valueEditors[2]
valueEdiotr.props.onDone('foo')
expect(store.getActions()).toEqual([updateEdit({response: {msg: 'foo'}})])
})
it('should handle change on flow response headers', () => {
let headers = provider.root.findAllByType(KeyValueListEditor).filter(headers => headers.props.type === 'headers')[0]
headers.props.onChange('foo')
expect(store.getActions()).toEqual([updateEdit({response: {headers: 'foo'}})])
})
it('should handle change on flow response ContentView', () => {
let contentView = provider.root.findByType(ContentView)
contentView.props.onContentChange('foo')
expect(store.getActions()).toEqual([updateEdit({response: {content: 'foo'}})])
})
it('should handle updateContent on flow response ContentViewOptions', () => {
let contentViewOptions = provider.root.findByType(ContentViewOptions)
contentViewOptions.props.uploadContent('foo')
expect(fetch).toBeCalled()
fetch.mockClear()
})
})
describe('Error Component', () => {
it('should render correctly', () => {
let errorView = renderer.create(<ErrorView flow={tflow}/>),
tree = errorView.toJSON()
expect(tree).toMatchSnapshot()
})
})

View File

@ -1,38 +0,0 @@
import * as React from "react"
import renderer from 'react-test-renderer'
import Nav, { NavAction } from '../../../components/FlowView/Nav'
describe('Nav Component', () => {
let tabs = ['foo', 'bar'],
onSelectTab = jest.fn(),
nav = renderer.create(<Nav active='foo' tabs={tabs} onSelectTab={onSelectTab}/>),
tree = nav.toJSON()
it('should render correctly', () => {
expect(tree).toMatchSnapshot()
})
it('should handle click', () => {
let mockEvent = { preventDefault: jest.fn() }
tree.children[0].props.onClick(mockEvent)
expect(mockEvent.preventDefault).toBeCalled()
expect(onSelectTab).toBeCalledWith('foo')
})
})
describe('NavAction Component', () => {
let clickFn = jest.fn(),
navAction = renderer.create(<NavAction icon="foo" title="bar" onClick={clickFn}/>),
tree = navAction.toJSON()
it('should render correctly', () => {
expect(tree).toMatchSnapshot()
})
it('should handle click', () => {
let mockEvent = { preventDefault: jest.fn() }
tree.props.onClick(mockEvent)
expect(mockEvent.preventDefault).toBeCalled()
expect(clickFn).toBeCalledWith(mockEvent)
})
})

View File

@ -1,21 +0,0 @@
import * as React from "react"
import ToggleEdit from '../../../components/FlowView/ToggleEdit'
import {TFlow} from '../../ducks/tutils'
import {render} from "../../test-utils"
import {fireEvent, screen} from "@testing-library/react";
let tflow = TFlow();
test("ToggleEdit", async () => {
const {asFragment, store} = render(
<ToggleEdit/>,
);
fireEvent.click(screen.getByTitle("Edit Flow"));
expect(asFragment()).toMatchSnapshot();
expect(store.getState().ui.flow.modifiedFlow).toBeTruthy();
fireEvent.click(screen.getByTitle("Finish Edit"));
expect(asFragment()).toMatchSnapshot();
expect(store.getState().ui.flow.modifiedFlow).toBeFalsy();
});

View File

@ -1,591 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CertificateInfo Component should render correctly 1`] = `<div />`;
exports[`ConnectionInfo Component should render correctly 1`] = `
<table
className="connection-table"
>
<tbody>
<tr>
<td>
Address:
</td>
<td>
127.0.0.1:22
</td>
</tr>
<tr>
<td>
<abbr
title="TLS Server Name Indication"
>
TLS SNI:
</abbr>
</td>
<td>
address
</td>
</tr>
<tr>
<td>
TLS version:
</td>
<td>
TLSv1.2
</td>
</tr>
<tr>
<td>
cipher name:
</td>
<td>
cipher
</td>
</tr>
<tr>
<td>
<abbr
title="ALPN protocol negotiated"
>
ALPN:
</abbr>
</td>
<td>
http/1.1
</td>
</tr>
</tbody>
</table>
`;
exports[`Details Component should render correctly 1`] = `
<section
className="detail"
>
<h4>
Client Connection
</h4>
<table
className="connection-table"
>
<tbody>
<tr>
<td>
Address:
</td>
<td>
127.0.0.1:22
</td>
</tr>
<tr>
<td>
<abbr
title="TLS Server Name Indication"
>
TLS SNI:
</abbr>
</td>
<td>
address
</td>
</tr>
<tr>
<td>
TLS version:
</td>
<td>
TLSv1.2
</td>
</tr>
<tr>
<td>
cipher name:
</td>
<td>
cipher
</td>
</tr>
<tr>
<td>
<abbr
title="ALPN protocol negotiated"
>
ALPN:
</abbr>
</td>
<td>
http/1.1
</td>
</tr>
</tbody>
</table>
<h4>
Server Connection
</h4>
<table
className="connection-table"
>
<tbody>
<tr>
<td>
Address:
</td>
<td>
address:22
</td>
</tr>
<tr>
<td>
<abbr
title="TLS Server Name Indication"
>
TLS SNI:
</abbr>
</td>
<td>
address
</td>
</tr>
<tr>
<td>
TLS version:
</td>
<td>
TLSv1.2
</td>
</tr>
<tr>
<td>
Resolved address:
</td>
<td>
192.168.0.1:22
</td>
</tr>
<tr>
<td>
Source address:
</td>
<td>
address:22
</td>
</tr>
</tbody>
</table>
<div />
<div>
<h4>
Timing
</h4>
<table
className="timing-table"
>
<tbody>
<tr>
<td>
Server conn. initiated
:
</td>
<td>
1999-12-31 23:00:02.000
<span
className="text-muted"
>
(
2s
)
</span>
</td>
</tr>
<tr>
<td>
Server conn. TCP handshake
:
</td>
<td>
1999-12-31 23:00:03.000
<span
className="text-muted"
>
(
3s
)
</span>
</td>
</tr>
<tr />
<tr>
<td>
Client conn. established
:
</td>
<td>
1999-12-31 23:00:00.000
<span
className="text-muted"
>
(
0ms
)
</span>
</td>
</tr>
<tr />
<tr>
<td>
First request byte
:
</td>
<td>
1999-12-31 23:00:00.000
</td>
</tr>
<tr>
<td>
Request complete
:
</td>
<td>
1999-12-31 23:00:01.000
<span
className="text-muted"
>
(
1s
)
</span>
</td>
</tr>
<tr>
<td>
First response byte
:
</td>
<td>
1999-12-31 23:00:02.000
<span
className="text-muted"
>
(
2s
)
</span>
</td>
</tr>
<tr>
<td>
Response complete
:
</td>
<td>
1999-12-31 23:00:03.000
<span
className="text-muted"
>
(
3s
)
</span>
</td>
</tr>
</tbody>
</table>
</div>
</section>
`;
exports[`Details Component should render correctly when server address is missing 1`] = `
<section
className="detail"
>
<h4>
Client Connection
</h4>
<table
className="connection-table"
>
<tbody>
<tr>
<td>
Address:
</td>
<td>
127.0.0.1:22
</td>
</tr>
<tr>
<td>
<abbr
title="TLS Server Name Indication"
>
TLS SNI:
</abbr>
</td>
<td>
address
</td>
</tr>
<tr>
<td>
TLS version:
</td>
<td>
TLSv1.2
</td>
</tr>
<tr>
<td>
cipher name:
</td>
<td>
cipher
</td>
</tr>
<tr>
<td>
<abbr
title="ALPN protocol negotiated"
>
ALPN:
</abbr>
</td>
<td>
http/1.1
</td>
</tr>
</tbody>
</table>
<div />
<div>
<h4>
Timing
</h4>
<table
className="timing-table"
>
<tbody>
<tr />
<tr />
<tr />
<tr>
<td>
Client conn. established
:
</td>
<td>
1999-12-31 23:00:00.000
<span
className="text-muted"
>
(
0ms
)
</span>
</td>
</tr>
<tr />
<tr>
<td>
First request byte
:
</td>
<td>
1999-12-31 23:00:00.000
</td>
</tr>
<tr>
<td>
Request complete
:
</td>
<td>
1999-12-31 23:00:01.000
<span
className="text-muted"
>
(
1s
)
</span>
</td>
</tr>
<tr>
<td>
First response byte
:
</td>
<td>
1999-12-31 23:00:02.000
<span
className="text-muted"
>
(
2s
)
</span>
</td>
</tr>
<tr>
<td>
Response complete
:
</td>
<td>
1999-12-31 23:00:03.000
<span
className="text-muted"
>
(
3s
)
</span>
</td>
</tr>
</tbody>
</table>
</div>
</section>
`;
exports[`TimeStamp Component should render correctly 1`] = `
<tr>
<td>
foo
:
</td>
<td>
2017-01-01 00:00:00.000
<span
className="text-muted"
>
(
2min
)
</span>
</td>
</tr>
`;
exports[`TimeStamp Component should render correctly 2`] = `<tr />`;
exports[`Timing Component should render correctly 1`] = `
<div>
<h4>
Timing
</h4>
<table
className="timing-table"
>
<tbody>
<tr>
<td>
Server conn. initiated
:
</td>
<td>
1999-12-31 23:00:02.000
<span
className="text-muted"
>
(
2s
)
</span>
</td>
</tr>
<tr>
<td>
Server conn. TCP handshake
:
</td>
<td>
1999-12-31 23:00:03.000
<span
className="text-muted"
>
(
3s
)
</span>
</td>
</tr>
<tr />
<tr>
<td>
Client conn. established
:
</td>
<td>
1999-12-31 23:00:00.000
<span
className="text-muted"
>
(
0ms
)
</span>
</td>
</tr>
<tr />
<tr>
<td>
First request byte
:
</td>
<td>
1999-12-31 23:00:00.000
</td>
</tr>
<tr>
<td>
Request complete
:
</td>
<td>
1999-12-31 23:00:01.000
<span
className="text-muted"
>
(
1s
)
</span>
</td>
</tr>
<tr>
<td>
First response byte
:
</td>
<td>
1999-12-31 23:00:02.000
<span
className="text-muted"
>
(
2s
)
</span>
</td>
</tr>
<tr>
<td>
Response complete
:
</td>
<td>
1999-12-31 23:00:03.000
<span
className="text-muted"
>
(
3s
)
</span>
</td>
</tr>
</tbody>
</table>
</div>
`;

View File

@ -1,123 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`HeaderEditor Component should render correctly 1`] = `
<div
className="inline-input editable"
dangerouslySetInnerHTML={
Object {
"__html": "foo",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
tabIndex={0}
/>
`;
exports[`KeyValueListEditor Component should handle correctly 1`] = `
<table
className="header-table"
>
<tbody>
<tr>
<td
className="header-name"
>
<div
className="inline-input editable"
dangerouslySetInnerHTML={
Object {
"__html": "k1",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
tabIndex={0}
/>
<span
className="header-colon"
>
:
</span>
</td>
<td
className="header-value"
>
<div
className="inline-input editable"
dangerouslySetInnerHTML={
Object {
"__html": "v1",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
tabIndex={0}
/>
</td>
</tr>
<tr>
<td
className="header-name"
>
<div
className="inline-input editable"
dangerouslySetInnerHTML={
Object {
"__html": "k2",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
tabIndex={0}
/>
<span
className="header-colon"
>
:
</span>
</td>
<td
className="header-value"
>
<div
className="inline-input editable"
dangerouslySetInnerHTML={
Object {
"__html": "",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
tabIndex={0}
/>
</td>
</tr>
</tbody>
</table>
`;

View File

@ -1,496 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Error Component should render correctly 1`] = `
<section
className="error"
>
<div
className="alert alert-warning"
>
error
<div>
<small>
1999-12-31 23:00:07.000
</small>
</div>
</div>
</section>
`;
exports[`Request Component should render correctly 1`] = `
<section
className="request"
>
<article>
<div
className="edit-flow-container"
>
<a
className="edit-flow"
onClick={[Function]}
title="Edit Flow"
>
<i
className="fa fa-pencil"
/>
</a>
</div>
<div
className="first-line request-line"
>
<div>
<div
className="inline-input readonly"
dangerouslySetInnerHTML={
Object {
"__html": "GET",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
/>
 
<div
className="inline-input readonly has-success"
dangerouslySetInnerHTML={
Object {
"__html": "http://address:22/path",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
/>
 
<div
className="inline-input readonly has-success"
dangerouslySetInnerHTML={
Object {
"__html": "HTTP/1.1",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
/>
</div>
</div>
<table
className="header-table"
>
<tbody>
<tr>
<td
className="header-name"
>
<div
className="inline-input readonly"
dangerouslySetInnerHTML={
Object {
"__html": "header",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
/>
<span
className="header-colon"
>
:
</span>
</td>
<td
className="header-value"
>
<div
className="inline-input readonly"
dangerouslySetInnerHTML={
Object {
"__html": "qvalue",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
/>
</td>
</tr>
<tr>
<td
className="header-name"
>
<div
className="inline-input readonly"
dangerouslySetInnerHTML={
Object {
"__html": "content-length",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
/>
<span
className="header-colon"
>
:
</span>
</td>
<td
className="header-value"
>
<div
className="inline-input readonly"
dangerouslySetInnerHTML={
Object {
"__html": "7",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
/>
</td>
</tr>
</tbody>
</table>
<hr />
<hr />
<table
className="header-table"
>
<tbody />
</table>
</article>
<footer>
<div
className="view-options"
>
<span>
<b>
View:
</b>
edit
</span>
 
<a
className="btn btn-default btn-xs"
href="./flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/request/content.data"
title="Download the content of the flow."
>
<i
className="fa fa-download"
/>
</a>
 
<a
className="btn btn-default btn-xs"
href="#"
onClick={[Function]}
title="Upload a file to replace the content."
>
<i
className="fa fa-fw fa-upload"
/>
<input
className="hidden"
onChange={[Function]}
type="file"
/>
</a>
 
</div>
</footer>
</section>
`;
exports[`Response Component should render correctly 1`] = `
<section
className="response"
>
<article>
<div
className="edit-flow-container"
>
<a
className="edit-flow"
onClick={[Function]}
title="Edit Flow"
>
<i
className="fa fa-pencil"
/>
</a>
</div>
<div
className="first-line response-line"
>
<div
className="inline-input readonly has-success"
dangerouslySetInnerHTML={
Object {
"__html": "HTTP/1.1",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
/>
 
<div
className="inline-input readonly has-success"
dangerouslySetInnerHTML={
Object {
"__html": "200",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
/>
 
<div
className="inline-input readonly"
dangerouslySetInnerHTML={
Object {
"__html": "OK",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
/>
</div>
<table
className="header-table"
>
<tbody>
<tr>
<td
className="header-name"
>
<div
className="inline-input readonly"
dangerouslySetInnerHTML={
Object {
"__html": "header-response",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
/>
<span
className="header-colon"
>
:
</span>
</td>
<td
className="header-value"
>
<div
className="inline-input readonly"
dangerouslySetInnerHTML={
Object {
"__html": "svalue",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
/>
</td>
</tr>
<tr>
<td
className="header-name"
>
<div
className="inline-input readonly"
dangerouslySetInnerHTML={
Object {
"__html": "content-length",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
/>
<span
className="header-colon"
>
:
</span>
</td>
<td
className="header-value"
>
<div
className="inline-input readonly"
dangerouslySetInnerHTML={
Object {
"__html": "7",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
/>
</td>
</tr>
</tbody>
</table>
<hr />
<hr />
<table
className="header-table"
>
<tbody>
<tr>
<td
className="header-name"
>
<div
className="inline-input readonly"
dangerouslySetInnerHTML={
Object {
"__html": "trailer",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
/>
<span
className="header-colon"
>
:
</span>
</td>
<td
className="header-value"
>
<div
className="inline-input readonly"
dangerouslySetInnerHTML={
Object {
"__html": "qvalue",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
/>
</td>
</tr>
</tbody>
</table>
</article>
<footer>
<div
className="view-options"
>
<span>
<b>
View:
</b>
edit
</span>
 
<a
className="btn btn-default btn-xs"
href="./flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/response/content.data"
title="Download the content of the flow."
>
<i
className="fa fa-download"
/>
</a>
 
<a
className="btn btn-default btn-xs"
href="#"
onClick={[Function]}
title="Upload a file to replace the content."
>
<i
className="fa fa-fw fa-upload"
/>
<input
className="hidden"
onChange={[Function]}
type="file"
/>
</a>
 
</div>
</footer>
</section>
`;

View File

@ -1,35 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Nav Component should render correctly 1`] = `
<nav
className="nav-tabs nav-tabs-sm"
>
<a
className="active"
href="#"
onClick={[Function]}
>
Foo
</a>
<a
className=""
href="#"
onClick={[Function]}
>
Bar
</a>
</nav>
`;
exports[`NavAction Component should render correctly 1`] = `
<a
className="nav-action"
href="#"
onClick={[Function]}
title="bar"
>
<i
className="fa fa-fw foo"
/>
</a>
`;

View File

@ -1,35 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ToggleEdit 1`] = `
<DocumentFragment>
<div
class="edit-flow-container"
>
<a
class="edit-flow"
title="Finish Edit"
>
<i
class="fa fa-check"
/>
</a>
</div>
</DocumentFragment>
`;
exports[`ToggleEdit 2`] = `
<DocumentFragment>
<div
class="edit-flow-container"
>
<a
class="edit-flow"
title="Edit Flow"
>
<i
class="fa fa-pencil"
/>
</a>
</div>
</DocumentFragment>
`;

View File

@ -0,0 +1,29 @@
import * as React from "react"
import {render, screen, waitFor} from "../test-utils";
import FlowView from "../../components/FlowView";
import fetchMock, {enableFetchMocks} from "jest-fetch-mock";
import {fireEvent} from "@testing-library/react";
enableFetchMocks();
test("FlowView", async () => {
fetchMock.mockReject(new Error("backend missing"));
const {asFragment} = render(<FlowView/>);
expect(asFragment()).toMatchSnapshot();
fireEvent.click(screen.getByText("Response"));
expect(asFragment()).toMatchSnapshot();
fireEvent.click(screen.getByText("WebSocket"));
expect(asFragment()).toMatchSnapshot();
fireEvent.click(screen.getByText("Connection"));
expect(asFragment()).toMatchSnapshot();
fireEvent.click(screen.getByText("Timing"));
expect(asFragment()).toMatchSnapshot();
fireEvent.click(screen.getByText("Error"));
expect(asFragment()).toMatchSnapshot();
});

View File

@ -1,35 +0,0 @@
jest.mock('../../../flow/utils')
import * as React from "react"
import renderer from 'react-test-renderer'
import FlowMenu from '../../../components/Header/FlowMenu'
import { TFlow, TStore }from '../../ducks/tutils'
import { MessageUtils } from "../../../flow/utils"
import { Provider } from 'react-redux'
describe('FlowMenu Component', () => {
let tflow = new TFlow(),
store = new TStore()
tflow.modified = true
tflow.intercepted = true
global.fetch = jest.fn()
let flowMenu = renderer.create(
<Provider store={store}>
<FlowMenu />
</Provider>
),
tree = flowMenu.toJSON()
it('should render correctly with flow', () => {
expect(tree).toMatchSnapshot()
})
let menu_content_2 = tree.children[1].children[0]
it('should handle download', () => {
let button = menu_content_2.children[0]
button.props.onClick()
expect(MessageUtils.getContentURL).toBeCalledWith(tflow, tflow.response)
})
})

View File

@ -0,0 +1,8 @@
import * as React from "react"
import FlowMenu from '../../../components/Header/FlowMenu'
import {render} from "../../test-utils"
test("FlowMenu", async () => {
const {asFragment} = render(<FlowMenu/>);
expect(asFragment()).toMatchSnapshot();
});

View File

@ -6,6 +6,6 @@ exports[`FileMenu Component should render correctly 1`] = `
href="#" href="#"
onClick={[Function]} onClick={[Function]}
> >
mitmproxy File
</a> </a>
`; `;

View File

@ -1,120 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FlowMenu Component should render correctly with flow 1`] = `
<div
className="flow-menu"
>
<div
className="menu-group"
>
<div
className="menu-content"
>
<button
className="btn btn-default"
onClick={[Function]}
title="[r]eplay flow"
>
<i
className="fa fa-fw fa-repeat text-primary"
/>
Replay
</button>
<button
className="btn btn-default"
onClick={[Function]}
title="[D]uplicate flow"
>
<i
className="fa fa-fw fa-copy text-info"
/>
Duplicate
</button>
<button
className="btn btn-default"
disabled={false}
onClick={[Function]}
title="revert changes to flow [V]"
>
<i
className="fa fa-fw fa-history text-warning"
/>
Revert
</button>
<button
className="btn btn-default"
onClick={[Function]}
title="[d]elete flow"
>
<i
className="fa fa-fw fa-trash text-danger"
/>
Delete
</button>
</div>
<div
className="menu-legend"
>
Flow Modification
</div>
</div>
<div
className="menu-group"
>
<div
className="menu-content"
>
<button
className="btn btn-default"
onClick={[Function]}
title="download"
>
<i
className="fa fa-fw fa-download"
/>
Download
</button>
</div>
<div
className="menu-legend"
>
Export
</div>
</div>
<div
className="menu-group"
>
<div
className="menu-content"
>
<button
className="btn btn-default"
disabled={false}
onClick={[Function]}
title="[a]ccept intercepted flow"
>
<i
className="fa fa-fw fa-play text-success"
/>
Resume
</button>
<button
className="btn btn-default"
disabled={false}
onClick={[Function]}
title="kill intercepted flow [x]"
>
<i
className="fa fa-fw fa-times text-danger"
/>
Abort
</button>
</div>
<div
className="menu-legend"
>
Interception
</div>
</div>
</div>
`;

View File

@ -0,0 +1,134 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FlowMenu 1`] = `
<DocumentFragment>
<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>
</DocumentFragment>
`;

View File

@ -26,7 +26,7 @@ exports[`MainMenu 1`] = `
class="form-control" class="form-control"
placeholder="Search" placeholder="Search"
type="text" type="text"
value="~d address" value="~u /second"
/> />
</div> </div>
<div <div
@ -97,101 +97,3 @@ exports[`MainMenu 1`] = `
</div> </div>
</DocumentFragment> </DocumentFragment>
`; `;
exports[`StartMenu 1`] = `
<DocumentFragment>
<div
class="main-menu"
>
<div
class="menu-group"
>
<div
class="menu-content"
>
<div
class="filter-input input-group"
>
<span
class="input-group-addon"
>
<i
class="fa fa-fw fa-search"
style="color: black;"
/>
</span>
<input
class="form-control"
placeholder="Search"
type="text"
value="~d address"
/>
</div>
<div
class="filter-input input-group"
>
<span
class="input-group-addon"
>
<i
class="fa fa-fw fa-tag"
style="color: rgb(0, 0, 0);"
/>
</span>
<input
class="form-control"
placeholder="Highlight"
type="text"
value="~u /path"
/>
</div>
</div>
<div
class="menu-legend"
>
Find
</div>
</div>
<div
class="menu-group"
>
<div
class="menu-content"
>
<div
class="filter-input input-group"
>
<span
class="input-group-addon"
>
<i
class="fa fa-fw fa-pause"
style="color: rgb(68, 68, 68);"
/>
</span>
<input
class="form-control"
placeholder="Intercept"
type="text"
value=""
/>
</div>
<button
class="btn-sm btn btn-default"
title="[a]ccept all"
>
<i
class="fa fa-fw fa-forward text-success"
/>
Resume All
</button>
</div>
<div
class="menu-legend"
>
Intercept
</div>
</div>
</div>
</DocumentFragment>
`;

View File

@ -14,8 +14,9 @@ exports[`OptionMenu Component should render correctly 1`] = `
title="Open Options" title="Open Options"
> >
<i <i
className="fa fa-fw fa-cogs text-primary" className="fa fa-cogs text-primary"
/> />
 
Edit Options Edit Options
<sup> <sup>
alpha alpha
@ -103,6 +104,18 @@ exports[`OptionMenu Component should render correctly 1`] = `
Display Event Log Display Event Log
</label> </label>
</div> </div>
<div
className="menu-entry"
>
<label>
<input
checked={true}
onChange={[Function]}
type="checkbox"
/>
Display Command Bar
</label>
</div>
</div> </div>
<div <div
className="menu-legend" className="menu-legend"

View File

@ -1,47 +0,0 @@
import * as React from "react"
import renderer from 'react-test-renderer'
import TestUtils from 'react-dom/test-utils'
import ValidateEditor from '../../../components/editors/ValidateEditor'
describe('ValidateEditor Component', () => {
let validateFn = jest.fn( content => content.length == 3),
doneFn = jest.fn()
it('should render correctly', () => {
let validateEditor = renderer.create(
<ValidateEditor content="foo" onEditDone={doneFn} isValid={validateFn}/>
),
tree = validateEditor.toJSON()
expect(tree).toMatchSnapshot()
})
let validateEditor = TestUtils.renderIntoDocument(
<ValidateEditor content="foo" onEditDone={doneFn} isValid={validateFn}/>
)
it('should handle componentWillReceiveProps', () => {
let mockProps = {
isValid: s => s.length == 3,
content: "bar"
}
validateEditor.UNSAFE_componentWillReceiveProps(mockProps)
expect(validateEditor.state.valid).toBeTruthy()
validateEditor.UNSAFE_componentWillReceiveProps({...mockProps, content: "bars"})
expect(validateEditor.state.valid).toBeFalsy()
})
it('should handle input', () => {
validateEditor.onInput("foo bar")
expect(validateFn).toBeCalledWith("foo bar")
})
it('should handle done', () => {
// invalid
validateEditor.editor.reset = jest.fn()
validateEditor.onDone("foo bar")
expect(validateEditor.editor.reset).toBeCalled()
// valid
validateEditor.onDone("bar")
expect(doneFn).toBeCalledWith("bar")
})
})

View File

@ -1,155 +0,0 @@
import * as React from "react"
import renderer from 'react-test-renderer'
import TestUtils from 'react-dom/test-utils'
import ValueEditor from '../../../components/editors/ValueEditor'
import { Key } from '../../../utils'
describe('ValueEditor Component', () => {
let mockFn = jest.fn()
it ('should render correctly', () => {
let valueEditor = renderer.create(
<ValueEditor content="foo" onEditDone={mockFn}/>
),
tree = valueEditor.toJSON()
expect(tree).toMatchSnapshot()
})
let valueEditor = TestUtils.renderIntoDocument(
<ValueEditor content="<script>foo</script>" onEditDone={mockFn}/>
)
it('should handle this.blur', () => {
valueEditor.input.blur = jest.fn()
valueEditor.blur()
expect(valueEditor.input.blur).toHaveBeenCalled()
})
it('should handle reset', () => {
valueEditor.reset()
expect(valueEditor.input.innerHTML).toEqual(
"&lt;script&gt;foo&lt;/script&gt;"
)
})
it('should handle paste', () => {
let mockEvent = {
preventDefault: jest.fn(),
clipboardData: { getData: (t) => "foo content"}
}
document.execCommand = jest.fn()
valueEditor.onPaste(mockEvent)
expect(document.execCommand).toBeCalledWith('insertHTML', false, "foo content")
})
it('should handle mouseDown', () => {
window.addEventListener = jest.fn()
valueEditor.onMouseDown({})
expect(valueEditor._mouseDown).toBeTruthy()
expect(window.addEventListener).toBeCalledWith('mouseup', valueEditor.onMouseUp)
})
it('should handle mouseUp', () => {
window.removeEventListener = jest.fn()
valueEditor.onMouseUp()
expect(window.removeEventListener).toBeCalledWith('mouseup', valueEditor.onMouseUp)
})
it('should handle focus', () => {
let mockEvent = { clientX: 1, clientY: 2 },
mockSelection = {
rangeCount: 1,
getRangeAt: jest.fn( (index) => {return { selectNodeContents: jest.fn() }}),
removeAllRanges: jest.fn(),
addRange: jest.fn()
},
clearState = (v) => {
v._mouseDown = false
v._ignore_events = false
v.state.editable = false
}
window.getSelection = () => mockSelection
// return undefined when mouse down
valueEditor.onMouseDown()
expect(valueEditor.onFocus(mockEvent)).toEqual(undefined)
valueEditor.onMouseUp()
// sel.rangeCount > 0
valueEditor.onFocus(mockEvent)
expect(mockSelection.getRangeAt).toBeCalledWith(0)
expect(valueEditor.state.editable).toBeTruthy()
expect(mockSelection.removeAllRanges).toBeCalled()
expect(mockSelection.addRange).toBeCalled()
clearState(valueEditor)
// document.caretPositionFromPoint
mockSelection.rangeCount = 0
let mockRange = { setStart: jest.fn(), selectNodeContents: jest.fn() }
document.caretPositionFromPoint = jest.fn((x, y) => {
return { offsetNode: 0, offset: x + y}
})
document.createRange = jest.fn(() => mockRange)
valueEditor.onFocus(mockEvent)
expect(mockRange.setStart).toBeCalledWith(0, 3)
clearState(valueEditor)
document.caretPositionFromPoint = null
//document.caretRangeFromPoint
document.caretRangeFromPoint = jest.fn(() => mockRange)
valueEditor.onFocus(mockEvent)
expect(document.caretRangeFromPoint).toBeCalledWith(1, 2)
clearState(valueEditor)
document.caretRangeFromPoint = null
//else
valueEditor.onFocus(mockEvent)
expect(mockRange.selectNodeContents).toBeCalledWith(valueEditor.input)
clearState(valueEditor)
})
it('should handle click', () => {
valueEditor.onMouseUp = jest.fn()
valueEditor.onFocus = jest.fn()
valueEditor.onClick('foo')
expect(valueEditor.onMouseUp).toBeCalled()
expect(valueEditor.onFocus).toBeCalledWith('foo')
})
it('should handle blur', () => {
// return undefined
valueEditor._ignore_events = true
expect(valueEditor.onBlur({})).toEqual(undefined)
// else
valueEditor._ignore_events = false
valueEditor.onBlur({})
expect(valueEditor.state.editable).toBeFalsy()
expect(valueEditor.props.onDone).toBeCalledWith(valueEditor.input.textContent)
})
it('should handle key down', () => {
let mockKeyEvent = (keyCode, shiftKey=false) => {
return {
keyCode: keyCode,
shiftKey: shiftKey,
stopPropagation: jest.fn(),
preventDefault: jest.fn()
}
}
valueEditor.reset = jest.fn()
valueEditor.blur = jest.fn()
valueEditor.onKeyDown(mockKeyEvent(Key.ESC))
expect(valueEditor.reset).toBeCalled()
expect(valueEditor.blur).toBeCalled()
valueEditor.blur.mockReset()
valueEditor.onKeyDown(mockKeyEvent(Key.ENTER))
expect(valueEditor.blur).toBeCalled()
valueEditor.onKeyDown(mockKeyEvent(Key.SPACE))
})
it('should handle input', () => {
valueEditor.onInput()
})
})

View File

@ -1,20 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ValidateEditor Component should render correctly 1`] = `
<div
className="inline-input editable has-success"
dangerouslySetInnerHTML={
Object {
"__html": "foo",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
tabIndex={0}
/>
`;

View File

@ -1,20 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ValueEditor Component should render correctly 1`] = `
<div
className="inline-input editable"
dangerouslySetInnerHTML={
Object {
"__html": "foo",
}
}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onPaste={[Function]}
tabIndex={0}
/>
`;

View File

@ -0,0 +1,111 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CommandBar 1`] = `
<DocumentFragment>
<div
class="command"
>
<div
class="command-title"
>
Command Result
</div>
<div
class="command-result"
/>
<div
class="argument-suggestion popover top"
>
<div
class="arrow"
/>
<div
class="popover-content"
>
<div>
<strong>
Available Commands:
</strong>
<p
class="available-commands"
>
[]
</p>
</div>
</div>
</div>
<div
class="command-input input-group"
>
<span
class="input-group-addon"
>
<i
class="fa fa-fw fa-terminal"
/>
</span>
<input
class="form-control"
placeholder="Enter command"
type="text"
value=""
/>
</div>
</div>
</DocumentFragment>
`;
exports[`CommandBar 2`] = `
<DocumentFragment>
<div
class="command"
>
<div
class="command-title"
>
Command Result
</div>
<div
class="command-result"
/>
<div
class="argument-suggestion popover top"
>
<div
class="arrow"
/>
<div
class="popover-content"
>
<div>
<strong>
Available Commands:
</strong>
<p
class="available-commands"
>
["flow.decode"]
</p>
</div>
</div>
</div>
<div
class="command-input input-group"
>
<span
class="input-group-addon"
>
<i
class="fa fa-fw fa-terminal"
/>
</span>
<input
class="form-control"
placeholder="Enter command"
type="text"
value=""
/>
</div>
</div>
</DocumentFragment>
`;

View File

@ -1,80 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ContentView Component should render correctly 1`] = `
<div
className="contentview"
>
<div
className="text-center"
>
<i
className="fa fa-spinner fa-spin"
/>
</div>
</div>
`;
exports[`ContentView Component should render correctly with content too large 1`] = `
<div>
<div
className="alert alert-warning"
>
<button
className="btn btn-xs btn-warning pull-right"
onClick={[Function]}
>
Display anyway
</button>
100mb
content size.
</div>
<div
className="view-options text-center"
>
<a
className="btn btn-default btn-xs"
href="#"
onClick={[Function]}
title="Upload a file to replace the content."
>
<i
className="fa fa-fw fa-upload"
/>
<input
className="hidden"
onChange={[Function]}
type="file"
/>
</a>
 
<a
className="btn btn-default btn-xs"
href="./flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/response/content.data"
title="Download the content of the flow."
>
<i
className="fa fa-download"
/>
</a>
</div>
</div>
`;
exports[`ContentView Component should render correctly with empty content 1`] = `
<div
className="alert alert-info"
>
No
response
content.
</div>
`;
exports[`ContentView Component should render correctly with missing content 1`] = `
<div
className="alert alert-info"
>
Response
content missing.
</div>
`;

View File

@ -0,0 +1,854 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FlowView 1`] = `
<DocumentFragment>
<div
class="flow-detail"
>
<nav
class="nav-tabs nav-tabs-sm"
>
<a
class="active"
href="#"
>
Request
</a>
<a
class=""
href="#"
>
Response
</a>
<a
class=""
href="#"
>
WebSocket
</a>
<a
class=""
href="#"
>
Error
</a>
<a
class=""
href="#"
>
Connection
</a>
<a
class=""
href="#"
>
Timing
</a>
</nav>
<section
class="request"
>
<div
class="first-line request-line"
>
<div>
<span
class="inline-input has-success"
tabindex="0"
>
GET
</span>
 
<span
class="inline-input has-success"
tabindex="0"
>
http://address:22/second
</span>
 
<span
class="inline-input has-success"
tabindex="0"
>
HTTP/1.1
</span>
</div>
</div>
<div
class="kv-editor headers"
>
<div
class="kv-row"
>
<span
class="inline-input kv-key"
tabindex="0"
>
header
</span>
: 
<span
class="inline-input kv-value"
placeholder="empty"
tabindex="0"
>
qvalue
</span>
</div>
<div
class="kv-row"
>
<span
class="inline-input kv-key"
tabindex="0"
>
content-length
</span>
: 
<span
class="inline-input kv-value"
placeholder="empty"
tabindex="0"
>
7
</span>
</div>
<div
aria-label="Add"
class="kv-add-row fa fa-plus-square-o"
role="button"
/>
</div>
<hr />
<div
class="contentview"
>
<div
class="controls"
>
<h5>
Loading...
</h5>
<button
class="btn-xs btn btn-default"
>
<i
class="fa fa-edit"
/>
 Edit
</button>
 
<a
class="btn btn-default btn-xs"
href="#"
title="Upload a file to replace the content."
>
<i
class="fa fa-fw fa-upload"
/>
Replace
<input
class="hidden"
type="file"
/>
</a>
 
<a
class="btn btn-default btn-xs"
href="#"
>
<span>
<i
class="fa fa-fw fa-files-o"
/>
 
<b>
View:
</b>
auto
<span
class="caret"
/>
</span>
</a>
</div>
</div>
</section>
</div>
</DocumentFragment>
`;
exports[`FlowView 2`] = `
<DocumentFragment>
<div
class="flow-detail"
>
<nav
class="nav-tabs nav-tabs-sm"
>
<a
class=""
href="#"
>
Request
</a>
<a
class="active"
href="#"
>
Response
</a>
<a
class=""
href="#"
>
WebSocket
</a>
<a
class=""
href="#"
>
Error
</a>
<a
class=""
href="#"
>
Connection
</a>
<a
class=""
href="#"
>
Timing
</a>
</nav>
<section
class="response"
>
<div
class="first-line response-line"
>
<span
class="inline-input has-success"
tabindex="0"
>
HTTP/1.1
</span>
 
<span
class="inline-input has-success"
tabindex="0"
>
200
</span>
 
<span
class="inline-input"
tabindex="0"
>
OK
</span>
</div>
<div
class="kv-editor headers"
>
<div
class="kv-row"
>
<span
class="inline-input kv-key"
tabindex="0"
>
header-response
</span>
: 
<span
class="inline-input kv-value"
placeholder="empty"
tabindex="0"
>
svalue
</span>
</div>
<div
class="kv-row"
>
<span
class="inline-input kv-key"
tabindex="0"
>
content-length
</span>
: 
<span
class="inline-input kv-value"
placeholder="empty"
tabindex="0"
>
7
</span>
</div>
<div
aria-label="Add"
class="kv-add-row fa fa-plus-square-o"
role="button"
/>
</div>
<hr />
<div
class="contentview"
>
<div
class="controls"
>
<h5>
Loading...
</h5>
<button
class="btn-xs btn btn-default"
>
<i
class="fa fa-edit"
/>
 Edit
</button>
 
<a
class="btn btn-default btn-xs"
href="#"
title="Upload a file to replace the content."
>
<i
class="fa fa-fw fa-upload"
/>
Replace
<input
class="hidden"
type="file"
/>
</a>
 
<a
class="btn btn-default btn-xs"
href="#"
>
<span>
<i
class="fa fa-fw fa-files-o"
/>
 
<b>
View:
</b>
auto
<span
class="caret"
/>
</span>
</a>
</div>
</div>
</section>
</div>
</DocumentFragment>
`;
exports[`FlowView 3`] = `
<DocumentFragment>
<div
class="flow-detail"
>
<nav
class="nav-tabs nav-tabs-sm"
>
<a
class=""
href="#"
>
Request
</a>
<a
class=""
href="#"
>
Response
</a>
<a
class="active"
href="#"
>
WebSocket
</a>
<a
class=""
href="#"
>
Error
</a>
<a
class=""
href="#"
>
Connection
</a>
<a
class=""
href="#"
>
Timing
</a>
</nav>
<section
class="websocket"
>
<h4>
WebSocket
</h4>
<div
class="contentview"
>
<div
class="controls"
>
<h5>
3 Messages
</h5>
<a
class="btn btn-default btn-xs"
href="#"
>
<span>
<i
class="fa fa-fw fa-files-o"
/>
 
<b>
View:
</b>
auto
<span
class="caret"
/>
</span>
</a>
</div>
</div>
<div>
<i
class="fa fa-fw fa-window-close text-muted"
/>
  Closed by server with code 1000 (Close Reason).
<small
class="pull-right"
>
1999-12-31 23:00:05.000
</small>
</div>
</section>
</div>
</DocumentFragment>
`;
exports[`FlowView 4`] = `
<DocumentFragment>
<div
class="flow-detail"
>
<nav
class="nav-tabs nav-tabs-sm"
>
<a
class=""
href="#"
>
Request
</a>
<a
class=""
href="#"
>
Response
</a>
<a
class=""
href="#"
>
WebSocket
</a>
<a
class=""
href="#"
>
Error
</a>
<a
class="active"
href="#"
>
Connection
</a>
<a
class=""
href="#"
>
Timing
</a>
</nav>
<section
class="detail"
>
<h4>
Client Connection
</h4>
<table
class="connection-table"
>
<tbody>
<tr>
<td>
Address:
</td>
<td>
127.0.0.1:22
</td>
</tr>
<tr>
<td>
<abbr
title="TLS Server Name Indication"
>
SNI
</abbr>
:
</td>
<td>
address
</td>
</tr>
<tr>
<td>
<abbr
title="ALPN protocol negotiated"
>
ALPN
</abbr>
:
</td>
<td>
http/1.1
</td>
</tr>
<tr>
<td>
TLS Version:
</td>
<td>
TLSv1.2
</td>
</tr>
<tr>
<td>
TLS Cipher:
</td>
<td>
cipher
</td>
</tr>
</tbody>
</table>
<h4>
Server Connection
</h4>
<table
class="connection-table"
>
<tbody>
<tr>
<td>
Address:
</td>
<td>
address:22
</td>
</tr>
<tr>
<td>
Resolved address:
</td>
<td>
192.168.0.1:22
</td>
</tr>
<tr>
<td>
Source address:
</td>
<td>
address:22
</td>
</tr>
<tr>
<td>
<abbr
title="TLS Server Name Indication"
>
SNI
</abbr>
:
</td>
<td>
address
</td>
</tr>
<tr>
<td>
TLS Version:
</td>
<td>
TLSv1.2
</td>
</tr>
</tbody>
</table>
</section>
</div>
</DocumentFragment>
`;
exports[`FlowView 5`] = `
<DocumentFragment>
<div
class="flow-detail"
>
<nav
class="nav-tabs nav-tabs-sm"
>
<a
class=""
href="#"
>
Request
</a>
<a
class=""
href="#"
>
Response
</a>
<a
class=""
href="#"
>
WebSocket
</a>
<a
class=""
href="#"
>
Error
</a>
<a
class=""
href="#"
>
Connection
</a>
<a
class="active"
href="#"
>
Timing
</a>
</nav>
<section
class="timing"
>
<h4>
Timing
</h4>
<table
class="timing-table"
>
<tbody>
<tr>
<td>
Client conn. established:
</td>
<td>
1999-12-31 23:00:00.000
<span
class="text-muted"
>
(0ms)
</span>
</td>
</tr>
<tr>
<td>
First request byte:
</td>
<td>
1999-12-31 23:00:00.000
</td>
</tr>
<tr>
<td>
Client conn. TLS handshake:
</td>
<td>
1999-12-31 23:00:01.000
<span
class="text-muted"
>
(1s)
</span>
</td>
</tr>
<tr>
<td>
Request complete:
</td>
<td>
1999-12-31 23:00:01.000
<span
class="text-muted"
>
(1s)
</span>
</td>
</tr>
<tr>
<td>
Server conn. initiated:
</td>
<td>
1999-12-31 23:00:02.000
<span
class="text-muted"
>
(2s)
</span>
</td>
</tr>
<tr>
<td>
First response byte:
</td>
<td>
1999-12-31 23:00:02.000
<span
class="text-muted"
>
(2s)
</span>
</td>
</tr>
<tr>
<td>
Server conn. TCP handshake:
</td>
<td>
1999-12-31 23:00:03.000
<span
class="text-muted"
>
(3s)
</span>
</td>
</tr>
<tr>
<td>
Response complete:
</td>
<td>
1999-12-31 23:00:03.000
<span
class="text-muted"
>
(3s)
</span>
</td>
</tr>
<tr>
<td>
Server conn. TLS handshake:
</td>
<td>
1999-12-31 23:00:04.000
<span
class="text-muted"
>
(4s)
</span>
</td>
</tr>
</tbody>
</table>
</section>
</div>
</DocumentFragment>
`;
exports[`FlowView 6`] = `
<DocumentFragment>
<div
class="flow-detail"
>
<nav
class="nav-tabs nav-tabs-sm"
>
<a
class=""
href="#"
>
Request
</a>
<a
class=""
href="#"
>
Response
</a>
<a
class=""
href="#"
>
WebSocket
</a>
<a
class="active"
href="#"
>
Error
</a>
<a
class=""
href="#"
>
Connection
</a>
<a
class=""
href="#"
>
Timing
</a>
</nav>
<section
class="error"
>
<div
class="alert alert-warning"
>
error
<div>
<small>
1999-12-31 23:00:07.000
</small>
</div>
</div>
</section>
</div>
</DocumentFragment>
`;

View File

@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react";
import Dropdown, {Divider, MenuItem, SubMenu} from '../../../components/common/Dropdown' import Dropdown, {Divider, MenuItem, SubMenu} from '../../../components/common/Dropdown'
import {fireEvent, render, screen, waitFor} from '@testing-library/react' import {fireEvent, render, screen, waitFor} from "../../test-utils";
test('Dropdown', async () => { test('Dropdown', async () => {
@ -18,11 +18,9 @@ test('Dropdown', async () => {
await waitFor(() => expect(onOpen).toBeCalledWith(true)) await waitFor(() => expect(onOpen).toBeCalledWith(true))
expect(asFragment()).toMatchSnapshot() expect(asFragment()).toMatchSnapshot()
/*
onOpen.mockClear() onOpen.mockClear()
fireEvent.click(document.body) fireEvent.click(document.body)
await waitFor(() => expect(onOpen).toBeCalledWith(false)) await waitFor(() => expect(onOpen).toBeCalledWith(false));
*/
}) })
test('SubMenu', async () => { test('SubMenu', async () => {
@ -45,9 +43,9 @@ test('SubMenu', async () => {
test('MenuItem', async () => { test('MenuItem', async () => {
let click = jest.fn(); let click = jest.fn();
const {asFragment} = render( const {asFragment} = render(
<MenuItem onClick={click}>click me</MenuItem> <MenuItem onClick={click}>wtf</MenuItem>
) )
expect(asFragment()).toMatchSnapshot() expect(asFragment()).toMatchSnapshot()
fireEvent.click(screen.getByText("click me")) fireEvent.click(screen.getByText("wtf"))
expect(click).toBeCalled() await waitFor(() => expect(click).toBeCalled());
}) })

View File

@ -18,8 +18,9 @@ exports[`Button Component should render correctly 1`] = `
title="title" title="title"
> >
<i <i
className="fa fa-fw icon" className="fa icon"
/> />
 
<a> <a>
foo foo
</a> </a>

View File

@ -54,7 +54,7 @@ exports[`MenuItem 1`] = `
<a <a
href="#" href="#"
> >
click me wtf
</a> </a>
</li> </li>
</DocumentFragment> </DocumentFragment>

View File

@ -0,0 +1,10 @@
jest.mock("../../../contrib/CodeMirror")
import * as React from 'react';
import CodeEditor from '../../../components/contentviews/CodeEditor'
import {render} from "../../test-utils"
test("CodeEditor", async () => {
const {asFragment} = render(<CodeEditor initialContent="foo"/>);
expect(asFragment()).toMatchSnapshot();
});

View File

@ -0,0 +1,60 @@
import {TFlow} from "../../ducks/tutils";
import * as React from 'react';
import HttpMessage, {ViewImage} from '../../../components/contentviews/HttpMessage'
import {fireEvent, render, screen, waitFor} from "../../test-utils"
import fetchMock, {enableFetchMocks} from "jest-fetch-mock";
import {SHOW_MAX_LINES} from "../../../components/contentviews/useContent";
jest.mock("../../../contrib/CodeMirror")
enableFetchMocks();
test("HttpMessage", async () => {
const lines = Array(SHOW_MAX_LINES).fill([["text", "data"]]).concat(
Array(SHOW_MAX_LINES).fill([["text", "additional"]])
);
fetchMock.mockResponses(
JSON.stringify({
lines: lines.slice(0, SHOW_MAX_LINES + 1),
description: "Auto"
}), JSON.stringify({
lines,
description: "Auto"
}), JSON.stringify({
lines: Array(5).fill([["text", "rawdata"]]),
description: "Raw",
}),
"raw content",
JSON.stringify({
lines: Array(5).fill([["text", "rawdata"]]),
description: "Raw",
})
);
const tflow = TFlow();
const {asFragment} = render(<HttpMessage flow={tflow} message={tflow.request}/>);
await waitFor(() => screen.getAllByText("data"));
expect(screen.queryByText('additional')).toBeNull();
fireEvent.click(screen.getByText("Show more"));
await waitFor(() => screen.getAllByText("additional"));
fireEvent.click(screen.getByText("auto"));
fireEvent.click(screen.getByText("raw"));
await waitFor(() => screen.getAllByText("rawdata"));
expect(asFragment()).toMatchSnapshot();
fireEvent.click(screen.getByText("Edit"));
expect(asFragment()).toMatchSnapshot();
fireEvent.click(screen.getByText("Cancel"));
await waitFor(() => screen.getAllByText("rawdata"));
expect(asFragment()).toMatchSnapshot();
});
test("ViewImage", async () => {
const flow = TFlow();
const {asFragment} = render(<ViewImage flow={flow} message={flow.request}/>)
expect(asFragment()).toMatchSnapshot();
});

View File

@ -0,0 +1,28 @@
import * as React from 'react';
import LineRenderer from '../../../components/contentviews/LineRenderer'
import {fireEvent, render, screen} from "../../test-utils"
test("LineRenderer", async () => {
const lines: [style: string, text: string][][] = [
[
["header", "foo: "],
["text", "42"]
],
[
["header", "bar: "],
["text", "43"]
],
]
const showMore = jest.fn();
const {asFragment} = render(<LineRenderer lines={lines} maxLines={1} showMore={showMore}/>);
expect(asFragment()).toMatchSnapshot();
fireEvent.click(screen.getByText("Show more"));
expect(showMore).toBeCalled();
});
test("No lines", async () => {
const {asFragment} = render(<LineRenderer lines={[]} maxLines={1} showMore={() => 0}/>);
expect(asFragment()).toMatchSnapshot();
})

View File

@ -0,0 +1,15 @@
import * as React from 'react';
import ViewSelector from '../../../components/contentviews/ViewSelector'
import {fireEvent, render, screen} from "../../test-utils"
test("ViewSelector", async () => {
const onChange = jest.fn();
const {asFragment} = render(<ViewSelector value="Auto" onChange={onChange}/>);
expect(asFragment()).toMatchSnapshot();
fireEvent.click(screen.getByText("auto"));
expect(asFragment()).toMatchSnapshot();
fireEvent.click(screen.getByText("raw"));
expect(onChange).toBeCalledWith("Raw");
});

View File

@ -0,0 +1,241 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`HttpMessage 1`] = `
<DocumentFragment>
<div
class="contentview"
>
<div
class="controls"
>
<h5>
Raw
</h5>
<button
class="btn-xs btn btn-default"
>
<i
class="fa fa-edit"
/>
 Edit
</button>
 
<a
class="btn btn-default btn-xs"
href="#"
title="Upload a file to replace the content."
>
<i
class="fa fa-fw fa-upload"
/>
Replace
<input
class="hidden"
type="file"
/>
</a>
 
<a
class="btn btn-default btn-xs"
href="#"
>
<span>
<i
class="fa fa-fw fa-files-o"
/>
 
<b>
View:
</b>
raw
<span
class="caret"
/>
</span>
</a>
</div>
<pre>
<div>
<span
class="text"
>
rawdata
</span>
</div>
<div>
<span
class="text"
>
rawdata
</span>
</div>
<div>
<span
class="text"
>
rawdata
</span>
</div>
<div>
<span
class="text"
>
rawdata
</span>
</div>
<div>
<span
class="text"
>
rawdata
</span>
</div>
</pre>
</div>
</DocumentFragment>
`;
exports[`HttpMessage 2`] = `
<DocumentFragment>
<div
class="contentview"
>
<div
class="controls"
>
<h5>
[Editing]
</h5>
<button
class="btn-xs btn btn-default"
>
<i
class="fa fa-check text-success"
/>
 Done
</button>
 
<button
class="btn-xs btn btn-default"
>
<i
class="fa fa-times text-danger"
/>
 Cancel
</button>
</div>
<div
class="codeeditor"
/>
</div>
</DocumentFragment>
`;
exports[`HttpMessage 3`] = `
<DocumentFragment>
<div
class="contentview"
>
<div
class="controls"
>
<h5>
Raw
</h5>
<button
class="btn-xs btn btn-default"
>
<i
class="fa fa-edit"
/>
 Edit
</button>
 
<a
class="btn btn-default btn-xs"
href="#"
title="Upload a file to replace the content."
>
<i
class="fa fa-fw fa-upload"
/>
Replace
<input
class="hidden"
type="file"
/>
</a>
 
<a
class="btn btn-default btn-xs"
href="#"
>
<span>
<i
class="fa fa-fw fa-files-o"
/>
 
<b>
View:
</b>
raw
<span
class="caret"
/>
</span>
</a>
</div>
<pre>
<div>
<span
class="text"
>
rawdata
</span>
</div>
<div>
<span
class="text"
>
rawdata
</span>
</div>
<div>
<span
class="text"
>
rawdata
</span>
</div>
<div>
<span
class="text"
>
rawdata
</span>
</div>
<div>
<span
class="text"
>
rawdata
</span>
</div>
</pre>
</div>
</DocumentFragment>
`;
exports[`ViewImage 1`] = `
<DocumentFragment>
<div
class="flowview-image"
>
<img
alt="preview"
class="img-thumbnail"
src="./flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/request/content.data"
/>
</div>
</DocumentFragment>
`;

View File

@ -0,0 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LineRenderer 1`] = `
<DocumentFragment>
<pre>
<div>
<span
class="header"
>
foo:
</span>
<span
class="text"
>
42
</span>
</div>
<button
class="btn btn-xs btn-info"
>
<i
aria-hidden="true"
class="fa fa-angle-double-down"
/>
Show more
</button>
</pre>
</DocumentFragment>
`;
exports[`No lines 1`] = `<DocumentFragment />`;

View File

@ -0,0 +1,66 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ViewSelector 1`] = `
<DocumentFragment>
<a
class="btn btn-default btn-xs"
href="#"
>
<span>
<i
class="fa fa-fw fa-files-o"
/>
 
<b>
View:
</b>
auto
<span
class="caret"
/>
</span>
</a>
</DocumentFragment>
`;
exports[`ViewSelector 2`] = `
<DocumentFragment>
<a
class="btn btn-default btn-xs open"
href="#"
>
<span>
<i
class="fa fa-fw fa-files-o"
/>
 
<b>
View:
</b>
auto
<span
class="caret"
/>
</span>
</a>
<ul
class="dropdown-menu show"
style="position: absolute; left: 0px; top: 0px;"
>
<li>
<a
href="#"
>
auto
</a>
</li>
<li>
<a
href="#"
>
raw
</a>
</li>
</ul>
</DocumentFragment>
`;

View File

@ -0,0 +1,31 @@
import * as React from 'react';
import {render, screen, waitFor} from "../../test-utils"
import {useContent} from "../../../components/contentviews/useContent";
import fetchMock, {enableFetchMocks} from 'jest-fetch-mock'
enableFetchMocks()
function TComp({url, hash}: { url: string, hash: string }) {
const content = useContent(url, hash);
return <div>{content}</div>;
}
test("caching", async () => {
fetchMock.mockResponses("hello", "world");
const {rerender} = render(<TComp url="/content" hash="hash"/>);
await waitFor(() => screen.getByText("hello"));
rerender(<TComp url="/content" hash="hash"/>);
expect(fetchMock.mock.calls).toHaveLength(1);
rerender(<TComp url="/content" hash="newhash"/>);
await waitFor(() => screen.getByText("world"));
expect(fetchMock.mock.calls).toHaveLength(2);
});
test("network error", async () => {
fetchMock.mockRejectOnce(new Error("I/O error"));
render(<TComp url="/content" hash="hash"/>);
await waitFor(() => screen.getByText("Error getting content: Error: I/O error."));
});

View File

@ -0,0 +1,27 @@
import * as React from "react"
import ValidateEditor from '../../../components/editors/ValidateEditor'
import {fireEvent, render, screen, waitFor} from "../../test-utils";
test("ValidateEditor", async () => {
const onEditDone = jest.fn();
const {asFragment} = render(
<ValidateEditor content="ok" isValid={x => x.includes("ok")} onEditDone={onEditDone}/>
);
expect(asFragment()).toMatchSnapshot();
fireEvent.mouseDown(screen.getByText("ok"));
fireEvent.mouseUp(screen.getByText("ok"));
screen.getByText("ok").innerHTML = "this is ok";
fireEvent.blur(screen.getByText("this is ok"));
await waitFor(() => expect(onEditDone).toBeCalledWith("this is ok"));
onEditDone.mockClear();
fireEvent.mouseDown(screen.getByText("this is ok"));
fireEvent.mouseUp(screen.getByText("this is ok"));
screen.getByText("this is ok").innerHTML = "wat";
fireEvent.blur(screen.getByText("wat"));
expect(screen.getByText("ok")).toBeDefined();
});

View File

@ -0,0 +1,21 @@
import * as React from "react"
import ValueEditor from '../../../components/editors/ValueEditor'
import {render, waitFor} from "../../test-utils";
test("ValueEditor", async () => {
const onEditDone = jest.fn();
let editor: { current?: ValueEditor | null } = {}
const {asFragment} = render(
<ValueEditor ref={x => editor.current = x} content="hello world" onEditDone={onEditDone}/>
);
expect(asFragment()).toMatchSnapshot();
if (!editor.current)
throw "err";
editor.current.startEditing();
await waitFor(() => expect(editor.current?.isEditing()).toBeTruthy());
editor.current.finishEditing();
await waitFor(() => expect(onEditDone).toBeCalledWith("hello world"));
});

View File

@ -0,0 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ValidateEditor 1`] = `
<DocumentFragment>
<span
class="inline-input has-success"
tabindex="0"
>
ok
</span>
</DocumentFragment>
`;

View File

@ -0,0 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ValueEditor 1`] = `
<DocumentFragment>
<span
class="inline-input"
tabindex="0"
>
hello world
</span>
</DocumentFragment>
`;

View File

@ -1,18 +1,11 @@
/** Auto-generated by test_app.py:TestApp._test_generate_tflow_js */ /** Auto-generated by test_app.py:TestApp.test_generate_tflow_js */
import {HTTPFlow} from '../../flow'; import {HTTPFlow} from '../../flow';
export default function(): HTTPFlow { export default function(): Required<HTTPFlow> {
return { return {
"client_conn": { "client_conn": {
//@ts-ignore
"address": [
"127.0.0.1",
22
],
"alpn": "http/1.1", "alpn": "http/1.1",
//@ts-ignore "cert": undefined,
"alpn_proto_negotiated": "http/1.1",
"cipher": "cipher", "cipher": "cipher",
"cipher_name": "cipher",
"id": "4a18d1a0-50a1-48dd-9aa6-d45d74282939", "id": "4a18d1a0-50a1-48dd-9aa6-d45d74282939",
"peername": [ "peername": [
"127.0.0.1", "127.0.0.1",
@ -97,14 +90,9 @@ export default function(): HTTPFlow {
22 22
], ],
"alpn": undefined, "alpn": undefined,
//@ts-ignore "cert": undefined,
"alpn_proto_negotiated": undefined,
"cipher": undefined, "cipher": undefined,
"id": "f087e7b2-6d0a-41a8-a8f0-e1a4761395f8", "id": "f087e7b2-6d0a-41a8-a8f0-e1a4761395f8",
"ip_address": [
"192.168.0.1",
22
],
"peername": [ "peername": [
"192.168.0.1", "192.168.0.1",
22 22
@ -114,10 +102,6 @@ export default function(): HTTPFlow {
"address", "address",
22 22
], ],
"source_address": [
"address",
22
],
"timestamp_end": 946681205, "timestamp_end": 946681205,
"timestamp_start": 946681202, "timestamp_start": 946681202,
"timestamp_tcp_setup": 946681203, "timestamp_tcp_setup": 946681203,
@ -125,6 +109,16 @@ export default function(): HTTPFlow {
"tls_established": true, "tls_established": true,
"tls_version": "TLSv1.2" "tls_version": "TLSv1.2"
}, },
"type": "http" "type": "http",
"websocket": {
"close_code": 1000,
"close_reason": "Close Reason",
"closed_by_client": false,
"messages_meta": {
"count": 3,
"timestamp_last": 946681205
},
"timestamp_end": 946681205
}
} }
} }

View File

@ -1,5 +1,5 @@
import reduceEventLog, * as eventLogActions from '../../ducks/eventLog' import reduceEventLog, * as eventLogActions from '../../ducks/eventLog'
import reduce from '../../ducks/utils/store' import {reduce} from '../../ducks/utils/store'
describe('event log reducer', () => { describe('event log reducer', () => {
it('should return initial state', () => { it('should return initial state', () => {

View File

@ -1,226 +0,0 @@
jest.mock('../../utils')
import reduceFlows from "../../ducks/flows"
import * as flowActions from "../../ducks/flows"
import reduce from "../../ducks/utils/store"
import { fetchApi } from "../../utils"
import { createStore } from "./tutils"
describe('flow reducer', () => {
let state = undefined
for (let i of [1, 2, 3, 4]) {
state = reduceFlows(state, { type: flowActions.ADD, data: { id: i }, cmd: 'add' })
}
it('should return initial state', () => {
expect(reduceFlows(undefined, {})).toEqual({
highlight: null,
filter: null,
sort: { column: null, desc: false },
selected: [],
...reduce(undefined, {})
})
})
describe('selections', () => {
it('should be possible to select a single flow', () => {
expect(reduceFlows(state, flowActions.select(2))).toEqual(
{
...state,
selected: [2],
}
)
})
it('should be possible to deselect a flow', () => {
expect(reduceFlows({ ...state, selected: [1] }, flowActions.select())).toEqual(
{
...state,
selected: [],
}
)
})
it('should be possible to select relative', () => {
// haven't selected any flow
expect(
flowActions.selectRelative(state, 1)
).toEqual(
flowActions.select(4)
)
// already selected some flows
expect(
flowActions.selectRelative({ ...state, selected: [2] }, 1)
).toEqual(
flowActions.select(3)
)
})
it('should update state.selected on remove', () => {
let next
next = reduceFlows({ ...state, selected: [2] }, {
type: flowActions.REMOVE,
data: 2,
cmd: 'remove'
})
expect(next.selected).toEqual([3])
//last row
next = reduceFlows({ ...state, selected: [4] }, {
type: flowActions.REMOVE,
data: 4,
cmd: 'remove'
})
expect(next.selected).toEqual([3])
//multiple selection
next = reduceFlows({ ...state, selected: [2, 3, 4] }, {
type: flowActions.REMOVE,
data: 3,
cmd: 'remove'
})
expect(next.selected).toEqual([2, 4])
})
})
it('should be possible to set filter', () => {
let filt = "~u 123"
expect(reduceFlows(undefined, flowActions.setFilter(filt)).filter).toEqual(filt)
})
it('should be possible to set highlight', () => {
let key = "foo"
expect(reduceFlows(undefined, flowActions.setHighlight(key)).highlight).toEqual(key)
})
it('should be possible to set sort', () => {
let sort = { column: "TLSColumn", desc: 1 }
expect(reduceFlows(undefined, flowActions.setSort(sort.column, sort.desc)).sort).toEqual(sort)
})
})
describe('flows actions', () => {
let store = createStore({ reduceFlows })
let tflow = { id: 1 }
it('should handle resume action', () => {
store.dispatch(flowActions.resume(tflow))
expect(fetchApi).toBeCalledWith('/flows/1/resume', { method: 'POST' })
})
it('should handle resumeAll action', () => {
store.dispatch(flowActions.resumeAll())
expect(fetchApi).toBeCalledWith('/flows/resume', { method: 'POST' })
})
it('should handle kill action', () => {
store.dispatch(flowActions.kill(tflow))
expect(fetchApi).toBeCalledWith('/flows/1/kill', { method: 'POST' })
})
it('should handle killAll action', () => {
store.dispatch(flowActions.killAll())
expect(fetchApi).toBeCalledWith('/flows/kill', { method: 'POST' })
})
it('should handle remove action', () => {
store.dispatch(flowActions.remove(tflow))
expect(fetchApi).toBeCalledWith('/flows/1', { method: 'DELETE' })
})
it('should handle duplicate action', () => {
store.dispatch(flowActions.duplicate(tflow))
expect(fetchApi).toBeCalledWith('/flows/1/duplicate', { method: 'POST' })
})
it('should handle replay action', () => {
store.dispatch(flowActions.replay(tflow))
expect(fetchApi).toBeCalledWith('/flows/1/replay', { method: 'POST' })
})
it('should handle revert action', () => {
store.dispatch(flowActions.revert(tflow))
expect(fetchApi).toBeCalledWith('/flows/1/revert', { method: 'POST' })
})
it('should handle update action', () => {
store.dispatch(flowActions.update(tflow, 'foo'))
expect(fetchApi.put).toBeCalledWith('/flows/1', 'foo')
})
it('should handle uploadContent action', () => {
let body = new FormData(),
file = new window.Blob(['foo'], { type: 'plain/text' })
body.append('file', file)
store.dispatch(flowActions.uploadContent(tflow, 'foo', 'foo'))
// window.Blob's lastModified is always the current time,
// which causes flaky tests on comparison.
expect(fetchApi).toBeCalledWith('/flows/1/foo/content.data', { method: 'POST', body: expect.anything()})
})
it('should handle clear action', () => {
store.dispatch(flowActions.clear())
expect(fetchApi).toBeCalledWith('/clear', { method: 'POST'} )
})
it('should handle download action', () => {
let state = reduceFlows(undefined, {})
expect(reduceFlows(state, flowActions.download())).toEqual(state)
})
it('should handle upload action', () => {
let body = new FormData()
body.append('file', 'foo')
store.dispatch(flowActions.upload('foo'))
expect(fetchApi).toBeCalledWith('/flows/dump', { method: 'POST', body })
})
})
describe('makeSort', () => {
it('should be possible to sort by TLSColumn', () => {
let sort = flowActions.makeSort({ column: 'TLSColumn', desc: true }),
a = { request: { scheme: 'http' } },
b = { request: { scheme: 'https' } }
expect(sort(a, b)).toEqual(1)
})
it('should be possible to sort by PathColumn', () => {
let sort = flowActions.makeSort({ column: 'PathColumn', desc: true }),
a = { request: {} },
b = { request: {} }
expect(sort(a, b)).toEqual(0)
})
it('should be possible to sort by MethodColumn', () => {
let sort = flowActions.makeSort({ column: 'MethodColumn', desc: true }),
a = { request: { method: 'GET' } },
b = { request: { method: 'POST' } }
expect(sort(b, a)).toEqual(-1)
})
it('should be possible to sort by StatusColumn', () => {
let sort = flowActions.makeSort({ column: 'StatusColumn', desc: false }),
a = { response: { status_code: 200 } },
b = { response: { status_code: 404 } }
expect(sort(a, b)).toEqual(-1)
})
it('should be possible to sort by TimeColumn', () => {
let sort = flowActions.makeSort({ column: 'TimeColumn', desc: false }),
a = { response: { timestamp_end: 9 }, request: { timestamp_start: 8 } },
b = { response: { timestamp_end: 10 }, request: { timestamp_start: 8 } }
expect(sort(b, a)).toEqual(1)
})
it('should be possible to sort by SizeColumn', () => {
let sort = flowActions.makeSort({ column: 'SizeColumn', desc: true }),
a = { request: { contentLength: 1 }, response: { contentLength: 1 } },
b = { request: { contentLength: 1 } }
expect(sort(a, b)).toEqual(-1)
})
})

View File

@ -0,0 +1,197 @@
import reduceFlows, * as flowActions from "../../ducks/flows";
import {reduce} from "../../ducks/utils/store"
import {fetchApi} from "../../utils"
import {TFlow, TStore} from "./tutils"
import FlowColumns from "../../components/FlowTable/FlowColumns"
jest.mock('../../utils')
describe('flow reducer', () => {
let s;
for (let i of ["1", "2", "3", "4"]) {
s = reduceFlows(s, {type: flowActions.ADD, data: {id: i}, cmd: 'add'})
}
let state = s;
it('should return initial state', () => {
expect(reduceFlows(undefined, {})).toEqual({
highlight: undefined,
filter: undefined,
sort: {column: undefined, desc: false},
selected: [],
...reduce(undefined, {})
})
})
describe('selections', () => {
it('should be possible to select a single flow', () => {
expect(reduceFlows(state, flowActions.select("2"))).toEqual(
{
...state,
selected: ["2"],
}
)
})
it('should be possible to deselect a flow', () => {
expect(reduceFlows({...state, selected: ["1"]}, flowActions.select())).toEqual(
{
...state,
selected: [],
}
)
})
it('should be possible to select relative', () => {
// haven't selected any flow
expect(
flowActions.selectRelative(state, 1)
).toEqual(
flowActions.select("4")
)
// already selected some flows
expect(
flowActions.selectRelative({...state, selected: [2]}, 1)
).toEqual(
flowActions.select("3")
)
})
it('should update state.selected on remove', () => {
let next
next = reduceFlows({...state, selected: ["2"]}, {
type: flowActions.REMOVE,
data: "2",
cmd: 'remove'
})
expect(next.selected).toEqual(["3"])
//last row
next = reduceFlows({...state, selected: ["4"]}, {
type: flowActions.REMOVE,
data: "4",
cmd: 'remove'
})
expect(next.selected).toEqual(["3"])
//multiple selection
next = reduceFlows({...state, selected: ["2", "3", "4"]}, {
type: flowActions.REMOVE,
data: "3",
cmd: 'remove'
})
expect(next.selected).toEqual(["2", "4"])
})
})
it('should be possible to set filter', () => {
let filt = "~u 123"
expect(reduceFlows(undefined, flowActions.setFilter(filt)).filter).toEqual(filt)
})
it('should be possible to set highlight', () => {
let key = "foo"
expect(reduceFlows(undefined, flowActions.setHighlight(key)).highlight).toEqual(key)
})
it('should be possible to set sort', () => {
let sort = {column: "tls", desc: true}
expect(reduceFlows(undefined, flowActions.setSort(sort.column, sort.desc)).sort).toEqual(sort)
})
})
describe('flows actions', () => {
let store = TStore();
let tflow = TFlow();
it('should handle resume action', () => {
store.dispatch(flowActions.resume(tflow))
expect(fetchApi).toBeCalledWith('/flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/resume', {method: 'POST'})
})
it('should handle resumeAll action', () => {
store.dispatch(flowActions.resumeAll())
expect(fetchApi).toBeCalledWith('/flows/resume', {method: 'POST'})
})
it('should handle kill action', () => {
store.dispatch(flowActions.kill(tflow))
expect(fetchApi).toBeCalledWith('/flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/kill', {method: 'POST'})
})
it('should handle killAll action', () => {
store.dispatch(flowActions.killAll())
expect(fetchApi).toBeCalledWith('/flows/kill', {method: 'POST'})
})
it('should handle remove action', () => {
store.dispatch(flowActions.remove(tflow))
expect(fetchApi).toBeCalledWith('/flows/d91165be-ca1f-4612-88a9-c0f8696f3e29', {method: 'DELETE'})
})
it('should handle duplicate action', () => {
store.dispatch(flowActions.duplicate(tflow))
expect(fetchApi).toBeCalledWith('/flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/duplicate', {method: 'POST'})
})
it('should handle replay action', () => {
store.dispatch(flowActions.replay(tflow))
expect(fetchApi).toBeCalledWith('/flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/replay', {method: 'POST'})
})
it('should handle revert action', () => {
store.dispatch(flowActions.revert(tflow))
expect(fetchApi).toBeCalledWith('/flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/revert', {method: 'POST'})
})
it('should handle update action', () => {
store.dispatch(flowActions.update(tflow, 'foo'))
expect(fetchApi.put).toBeCalledWith('/flows/d91165be-ca1f-4612-88a9-c0f8696f3e29', 'foo')
})
it('should handle uploadContent action', () => {
let body = new FormData(),
file = new window.Blob(['foo'], {type: 'plain/text'})
body.append('file', file)
store.dispatch(flowActions.uploadContent(tflow, 'foo', 'foo'))
// window.Blob's lastModified is always the current time,
// which causes flaky tests on comparison.
expect(fetchApi).toBeCalledWith('/flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/foo/content.data', {
method: 'POST',
body: expect.anything()
})
})
it('should handle clear action', () => {
store.dispatch(flowActions.clear())
expect(fetchApi).toBeCalledWith('/clear', {method: 'POST'})
})
it('should handle download action', () => {
let state = reduceFlows(undefined, {})
expect(reduceFlows(state, flowActions.download())).toEqual(state)
})
it('should handle upload action', () => {
let body = new FormData()
body.append('file', 'foo')
store.dispatch(flowActions.upload('foo'))
expect(fetchApi).toBeCalledWith('/flows/dump', {method: 'POST', body})
})
})
test("makeSort", () => {
const a = TFlow(), b = TFlow();
Object.keys(FlowColumns).forEach((column) => {
// @ts-ignore
const sort = flowActions.makeSort({column, desc: true});
expect(sort(a, b)).toBeDefined();
})
});

View File

@ -1,69 +0,0 @@
import reduceOptions, * as OptionsActions from '../../ducks/options'
import configureStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as OptionsEditorActions from '../../ducks/ui/optionsEditor'
import {updateError} from "../../ducks/ui/optionsEditor";
const mockStore = configureStore([ thunk ])
describe('option reducer', () => {
it('should return initial state', () => {
expect(reduceOptions(undefined, {})).toEqual(OptionsActions.defaultState)
})
it('should handle receive action', () => {
let action = { type: OptionsActions.RECEIVE, data: {id: {value: 'foo'} } }
expect(reduceOptions(undefined, action)).toEqual({id: 'foo'})
})
it('should handle update action', () => {
let action = {type: OptionsActions.UPDATE, data: {id: {value: 1} } }
expect(reduceOptions(undefined, action)).toEqual({...OptionsActions.defaultState, id: 1})
})
})
describe('option actions', () => {
it('should be possible to update option', () => {
let store = mockStore()
let mockResponse = { status: 200 },
promise = Promise.resolve(mockResponse)
global.fetch = r => { return promise }
store.dispatch(OptionsActions.update('foo', 'bar'))
expect(store.getActions()).toEqual([
{ type: OptionsEditorActions.OPTION_UPDATE_START, option: 'foo', value: 'bar'}
])
store.clearActions()
})
})
describe('sendUpdate', () => {
it('should handle error', async () => {
let store = mockStore()
let mockResponse = { status: 404, text: () => "fooerror" },
promise = Promise.resolve(mockResponse)
global.fetch = r => { return promise }
await store.dispatch(OptionsActions.pureSendUpdate("bar", "error"))
expect(store.getActions()).toEqual([
OptionsEditorActions.updateError("bar", "fooerror")
])
})
})
describe('save', () => {
it('should dump options', () => {
let store = mockStore()
global.fetch = jest.fn()
store.dispatch(OptionsActions.save())
expect(fetch).toBeCalledWith(
'./options/save?_xsrf=undefined',
{
credentials: "same-origin",
method: "POST"
}
)
})
})

View File

@ -0,0 +1,40 @@
import reduceOptions, * as OptionsActions from '../../ducks/options'
import * as OptionsEditorActions from '../../ducks/ui/optionsEditor'
import {enableFetchMocks} from "jest-fetch-mock";
import {TStore} from "./tutils";
describe('option reducer', () => {
it('should return initial state', () => {
expect(reduceOptions(undefined, {type: "other"})).toEqual(OptionsActions.defaultState)
})
it('should handle receive action', () => {
let action = {type: OptionsActions.RECEIVE, data: {id: {value: 'foo'}}}
expect(reduceOptions(undefined, action)).toEqual({id: 'foo'})
})
it('should handle update action', () => {
let action = {type: OptionsActions.UPDATE, data: {id: {value: 1}}}
expect(reduceOptions(undefined, action)).toEqual({...OptionsActions.defaultState, id: 1})
})
})
test("sendUpdate", async () => {
enableFetchMocks();
let store = TStore();
fetchMock.mockResponseOnce("fooerror", {status: 404});
await store.dispatch(dispatch => OptionsActions.pureSendUpdate("intercept", "~~~", dispatch))
expect(store.getActions()).toEqual([
OptionsEditorActions.updateError("intercept", "fooerror")
])
store.clearActions();
fetchMock.mockResponseOnce("", {status: 200});
await store.dispatch(dispatch => OptionsActions.pureSendUpdate("intercept", "valid", dispatch))
expect(store.getActions()).toEqual([
OptionsEditorActions.updateSuccess("intercept")
])
});

View File

@ -1,21 +1,13 @@
import {applyMiddleware, combineReducers, createStore as createReduxStore} from 'redux'
import thunk from 'redux-thunk' import thunk from 'redux-thunk'
import configureStore, {MockStoreCreator, MockStoreEnhanced} from 'redux-mock-store' import configureStore, {MockStoreCreator, MockStoreEnhanced} from 'redux-mock-store'
import {ConnectionState} from '../../ducks/connection' import {ConnectionState} from '../../ducks/connection'
import TFlow from './_tflow' import TFlow from './_tflow'
import {RootState} from "../../ducks"; import {AppDispatch, RootState} from "../../ducks";
import {HTTPFlow} from "../../flow"; import {HTTPFlow} from "../../flow";
import {defaultState as defaultConf} from "../../ducks/conf" import {defaultState as defaultConf} from "../../ducks/conf"
import {defaultState as defaultOptions} from "../../ducks/options" import {defaultState as defaultOptions} from "../../ducks/options"
const mockStoreCreator: MockStoreCreator<RootState> = configureStore([thunk]) const mockStoreCreator: MockStoreCreator<RootState, AppDispatch> = configureStore([thunk])
export function createStore(parts) {
return createReduxStore(
combineReducers(parts),
applyMiddleware(...[thunk])
)
}
export {TFlow} export {TFlow}
@ -63,18 +55,9 @@ export const testState: RootState = {
}, },
ui: { ui: {
flow: { flow: {
contentView: 'Auto', contentViewFor: {},
displayLarge: false,
showFullContent: true,
maxContentLines: 10,
content: [[['foo', 'bar']]],
viewDescription: 'foo',
modifiedFlow: undefined,
tab: 'request' tab: 'request'
}, },
header: {
tab: 'Start'
},
modal: { modal: {
activeModal: undefined activeModal: undefined
}, },
@ -87,15 +70,18 @@ export const testState: RootState = {
}, },
options: defaultOptions, options: defaultOptions,
flows: { flows: {
selected: [tflow1.id], selected: [tflow2.id],
byId: {[tflow1.id]: tflow1, [tflow2.id]: tflow2}, byId: {[tflow1.id]: tflow1, [tflow2.id]: tflow2},
filter: '~d address', filter: '~u /second',
highlight: '~u /path', highlight: '~u /path',
sort: { sort: {
desc: true, desc: true,
column: 'PathColumn' column: "path"
}, },
view: [tflow1, tflow2] view: [tflow2],
list: [tflow1, tflow2],
listIndex: {[tflow1.id]: 0, [tflow2.id]: 1},
viewIndex: {[tflow2.id]: 0},
}, },
connection: { connection: {
state: ConnectionState.ESTABLISHED state: ConnectionState.ESTABLISHED
@ -110,13 +96,20 @@ export const testState: RootState = {
error: true error: true
}, },
view: [ view: [
{id: 1, level: 'info', message: 'foo'}, {id: "1", level: 'info', message: 'foo'},
{id: 2, level: 'error', message: 'bar'} {id: "2", level: 'error', message: 'bar'}
] ],
byId: {}, // TODO: incomplete
list: [], // TODO: incomplete
listIndex: {}, // TODO: incomplete
viewIndex: {}, // TODO: incomplete
},
commandBar: {
visible: true,
} }
} }
export function TStore(): MockStoreEnhanced<RootState> { export function TStore(): MockStoreEnhanced<RootState, AppDispatch> {
return mockStoreCreator(testState) return mockStoreCreator(testState)
} }

View File

@ -1,110 +0,0 @@
import _ from 'lodash'
import reducer, {
startEdit,
setContentViewDescription,
setShowFullContent,
setContent,
updateEdit,
stopEdit,
setContentView,
selectTab,
displayLarge
} from '../../../ducks/ui/flow'
import * as flowActions from '../../../ducks/flows'
describe('flow reducer', () => {
it('should return initial state', () => {
expect(reducer(undefined, {})).toEqual({
displayLarge: false,
viewDescription: '',
showFullContent: false,
modifiedFlow: undefined,
contentView: 'Auto',
tab: 'request',
content: [],
maxContentLines: 80,
})
})
it('should change to edit mode', () => {
let testFlow = {flow : 'foo'}
const newState = reducer(undefined, startEdit({ flow: 'foo' }))
expect(newState.contentView).toEqual('Edit')
expect(newState.modifiedFlow).toEqual(testFlow)
expect(newState.showFullContent).toEqual(true)
})
it('should set the view description', () => {
expect(reducer(undefined, setContentViewDescription('description')).viewDescription)
.toEqual('description')
})
it('should set show full content', () => {
expect(reducer({showFullContent: false}, setShowFullContent()).showFullContent)
.toBeTruthy()
})
it('should set showFullContent to true', () => {
let maxLines = 10
let content = _.range(maxLines)
const newState = reducer({maxContentLines: maxLines}, setContent(content) )
expect(newState.showFullContent).toBeTruthy()
expect(newState.content).toEqual(content)
})
it('should set showFullContent to false', () => {
let maxLines = 5
let content = _.range(maxLines+1);
const newState = reducer({maxContentLines: maxLines}, setContent(_.range(maxLines+1)))
expect(newState.showFullContent).toBeFalsy()
expect(newState.content).toEqual(content)
})
it('should not change the contentview mode', () => {
expect(reducer({contentView: 'foo'}, flowActions.select(1)).contentView).toEqual('foo')
})
it('should change the contentview mode to auto after editing when a new flow will be selected', () => {
expect(reducer({contentView: 'foo', modifiedFlow : 'test_flow'}, flowActions.select(1)).contentView).toEqual('Auto')
})
it('should set update and merge the modifiedflow with the update values', () => {
let modifiedFlow = {headers: []}
let updateValues = {content: 'bar'}
let result = {headers: [], content: 'bar'}
expect(reducer({modifiedFlow}, updateEdit(updateValues)).modifiedFlow).toEqual(result)
})
it('should not change the state when a flow is updated which is not selected', () => {
let modifiedFlow = {id: 1}
let updatedFlow = {id: 0}
expect(reducer({modifiedFlow}, stopEdit(updatedFlow, modifiedFlow)).modifiedFlow).toEqual(modifiedFlow)
})
it('should stop editing when the selected flow is updated', () => {
let modifiedFlow = {id: 1}
let updatedFlow = {id: 1}
expect(reducer(
{ modifiedFlow },
{type: flowActions.UPDATE, data: modifiedFlow}
).modifiedFlow
).toBeFalsy()
})
it('should set content view', () => {
let state = reducer(undefined, setContentView('Edit'))
expect(state.contentView).toEqual('Edit')
expect(state.showFullContent).toBeTruthy()
})
it('should select different tabs', () => {
let state = reducer(undefined, selectTab('response'))
expect(state.tab).toEqual('response')
expect(state.displayLarge).toBeFalsy()
expect(state.showFullContent).toBeFalsy()
})
it('should display large', () => {
expect(reducer(undefined, displayLarge()).displayLarge).toBeTruthy()
})
})

View File

@ -0,0 +1,20 @@
import reduceFlow, * as FlowActions from '../../../ducks/ui/flow'
describe('option reducer', () => {
it('should return initial state', () => {
expect(reduceFlow(undefined, {type: "other"})).toEqual(FlowActions.defaultState)
})
it('should handle set tab', () => {
expect(
reduceFlow(undefined, FlowActions.selectTab("response")).tab
).toEqual("response")
})
it('should handle set content view', () => {
expect(
reduceFlow(undefined, FlowActions.setContentViewFor("foo", "Raw")).contentViewFor["foo"]
).toEqual("Raw")
})
})

View File

@ -1,33 +0,0 @@
import reducer, { setActiveMenu } from '../../../ducks/ui/header'
import * as flowActions from '../../../ducks/flows'
describe('header reducer', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {}).activeMenu).toEqual('Start')
})
it('should return the state for view', () => {
expect(reducer(undefined, setActiveMenu('View')).activeMenu).toEqual('View')
})
it('should change the state to Start when deselecting a flow and we a currently at the flow tab', () => {
expect(reducer(
{ activeMenu: 'Flow', isFlowSelected: true },
flowActions.select(undefined)).activeMenu
).toEqual('Start')
})
it('should change the state to Flow when we selected a flow and no flow was selected before', () => {
expect(reducer(
{ activeMenu: 'Start', isFlowSelected: false },
flowActions.select(1)).activeMenu
).toEqual('Flow')
})
it('should not change the state to Flow when OPTIONS tab is selected and we selected a flow and a flow as selected before', () => {
expect(reducer(
{ activeMenu: 'Options', isFlowSelected: true },
flowActions.select(1)
).activeMenu).toEqual('Options')
})
})

View File

@ -4,6 +4,5 @@ describe('reduceUI in js/ducks/ui/index.js', () => {
it('should combine flow and header', () => { it('should combine flow and header', () => {
let state = reduceUI(undefined, {}) let state = reduceUI(undefined, {})
expect(state.hasOwnProperty('flow')).toBeTruthy() expect(state.hasOwnProperty('flow')).toBeTruthy()
expect(state.hasOwnProperty('header')).toBeTruthy()
}) })
}) })

View File

@ -82,7 +82,7 @@ describe('onKeyDown', () => {
it('should handle switch to left tab', () => { it('should handle switch to left tab', () => {
store.dispatch(createKeyEvent(Key.LEFT)) store.dispatch(createKeyEvent(Key.LEFT))
expect(store.getActions()).toEqual([{ tab: 'details', type: UIActions.SET_TAB }]) expect(store.getActions()).toEqual([{ tab: 'timing', type: UIActions.SET_TAB }])
}) })
it('should handle switch to right tab', () => { it('should handle switch to right tab', () => {

View File

@ -1,4 +1,5 @@
import reduce, * as storeActions from '../../../ducks/utils/store' import {reduce} from '../../../ducks/utils/store'
import * as storeActions from '../../../ducks/utils/store'
describe('store reducer', () => { describe('store reducer', () => {
it('should return initial state', () => { it('should return initial state', () => {

View File

@ -6,7 +6,9 @@ import {createAppStore} from '../ducks'
import {testState} from "./ducks/tutils"; import {testState} from "./ducks/tutils";
// re-export everything // re-export everything
export * from '@testing-library/react' export {
waitFor, fireEvent, act, screen
} from '@testing-library/react'
export function render( export function render(
ui, ui,

View File

@ -4,6 +4,7 @@ import { updateStoreFromUrl, updateUrlFromStore } from '../urlState'
import reduceFlows from '../ducks/flows' import reduceFlows from '../ducks/flows'
import reduceUI from '../ducks/ui/index' import reduceUI from '../ducks/ui/index'
import reduceEventLog from '../ducks/eventLog' import reduceEventLog from '../ducks/eventLog'
import reduceCommandBar from '../ducks/commandBar'
import * as flowsActions from '../ducks/flows' import * as flowsActions from '../ducks/flows'
import configureStore from 'redux-mock-store' import configureStore from 'redux-mock-store'
@ -64,7 +65,8 @@ describe('updateUrlFromStore', () => {
let initialState = { let initialState = {
flows: reduceFlows(undefined, {}), flows: reduceFlows(undefined, {}),
ui: reduceUI(undefined, {}), ui: reduceUI(undefined, {}),
eventLog: reduceEventLog(undefined, {}) eventLog: reduceEventLog(undefined, {}),
commandBar: reduceCommandBar(undefined, {}),
} }
it('should update initial url', () => { it('should update initial url', () => {
@ -89,7 +91,8 @@ describe('initialize', () => {
let initialState = { let initialState = {
flows: reduceFlows(undefined, {}), flows: reduceFlows(undefined, {}),
ui: reduceUI(undefined, {}), ui: reduceUI(undefined, {}),
eventLog: reduceEventLog(undefined, {}) eventLog: reduceEventLog(undefined, {}),
commandBar: reduceCommandBar(undefined, {}),
} }
it('should handle initial state', () => { it('should handle initial state', () => {

View File

@ -27,7 +27,7 @@ describe('formatTimeSTamp', () => {
describe('reverseString', () => { describe('reverseString', () => {
it('should return reversed string', () => { it('should return reversed string', () => {
let str1 = "abc", str2="xyz" let str1 = "abc", str2 = "xyz"
expect(utils.reverseString(str1) > utils.reverseString(str2)).toBeTruthy() expect(utils.reverseString(str1) > utils.reverseString(str2)).toBeTruthy()
}) })
}) })
@ -36,13 +36,13 @@ describe('fetchApi', () => {
it('should handle fetch operation', () => { it('should handle fetch operation', () => {
utils.fetchApi('http://foo/bar', {method: "POST"}) utils.fetchApi('http://foo/bar', {method: "POST"})
expect(fetch.mock.calls[0][0]).toEqual( expect(fetch.mock.calls[0][0]).toEqual(
"http://foo/bar?_xsrf=undefined" "http://foo/bar"
) )
fetch.mockClear() fetch.mockClear()
utils.fetchApi('http://foo?bar=1', {method: "POST"}) utils.fetchApi('http://foo?bar=1', {method: "POST"})
expect(fetch.mock.calls[0][0]).toEqual( expect(fetch.mock.calls[0][0]).toEqual(
"http://foo?bar=1&_xsrf=undefined" "http://foo?bar=1"
) )
}) })
@ -52,11 +52,14 @@ describe('fetchApi', () => {
utils.fetchApi.put("http://foo", [1, 2, 3], {}) utils.fetchApi.put("http://foo", [1, 2, 3], {})
expect(fetch.mock.calls[0]).toEqual( expect(fetch.mock.calls[0]).toEqual(
[ [
"http://foo?_xsrf=undefined", "http://foo",
{ {
body: "[1,2,3]", body: "[1,2,3]",
credentials: "same-origin", credentials: "same-origin",
headers: { "Content-Type": "application/json" }, headers: {
"Content-Type": "application/json",
"X-XSRFToken": undefined,
},
method: "PUT" method: "PUT"
}, },
] ]
@ -66,8 +69,8 @@ describe('fetchApi', () => {
describe('getDiff', () => { describe('getDiff', () => {
it('should return json object including only the changed keys value pairs', () => { it('should return json object including only the changed keys value pairs', () => {
let obj1 = {a: 1, b:{ foo: 1} , c: [3]}, let obj1 = {a: 1, b: {foo: 1}, c: [3]},
obj2 = {a: 1, b:{ foo: 2} , c: [4]} obj2 = {a: 1, b: {foo: 2}, c: [4]}
expect(utils.getDiff(obj1, obj2)).toEqual({ b: {foo: 2}, c:[4]}) expect(utils.getDiff(obj1, obj2)).toEqual({b: {foo: 2}, c: [4]})
}) })
}) })

View File

@ -100,7 +100,7 @@ export default function CommandBar() {
setAllCommands(data["commands"]) setAllCommands(data["commands"])
setCompletionCandidate(getAvailableCommands(data["commands"])) setCompletionCandidate(getAvailableCommands(data["commands"]))
setAvailableCommands(Object.keys(data)) setAvailableCommands(Object.keys(data))
}) }).catch(e => console.error(e))
}, []) }, [])
const parseCommand = (originalInput: string, input: string) => { const parseCommand = (originalInput: string, input: string) => {

View File

@ -204,7 +204,7 @@ export const quickactions: FlowColumn = ({flow}) => {
const ct = flow.response && ResponseUtils.getContentType(flow.response); const ct = flow.response && ResponseUtils.getContentType(flow.response);
return ( return (
<td className={classnames("col-quickactions", {hover: open})} onClick={(e) => 0/*e.stopPropagation()*/}> <td className={classnames("col-quickactions", {hover: open})} onClick={() => 0}>
<div> <div>
{resume_or_replay} {resume_or_replay}
<Dropdown text={<i className="fa fa-fw fa-ellipsis-h text-muted"/>} className="quickaction" <Dropdown text={<i className="fa fa-fw fa-ellipsis-h text-muted"/>} className="quickaction"
@ -262,3 +262,15 @@ export const quickactions: FlowColumn = ({flow}) => {
quickactions.headerName = '' quickactions.headerName = ''
quickactions.sortKey = flow => 0; quickactions.sortKey = flow => 0;
export default {
icon,
method,
path,
quickactions,
size,
status,
time,
timestamp,
tls
};

View File

@ -15,7 +15,7 @@ type TabProps = {
flow: Flow flow: Flow
} }
export const allTabs: { [name: string]: FunctionComponent<TabProps> } = { export const allTabs: { [name: string]: FunctionComponent<TabProps> & {displayName: string} } = {
request: Request, request: Request,
response: Response, response: Response,
error: Error, error: Error,
@ -57,7 +57,7 @@ export default function FlowView() {
event.preventDefault() event.preventDefault()
dispatch(selectTab(tabId)) dispatch(selectTab(tabId))
}}> }}>
{allTabs[tabId].name} {allTabs[tabId].displayName}
</a> </a>
))} ))}
</nav> </nav>

View File

@ -149,3 +149,4 @@ export default function Connection({flow}: { flow: Flow }) {
</section> </section>
) )
} }
Connection.displayName = "Connection"

View File

@ -18,3 +18,4 @@ export default function Error({flow}: ErrorProps) {
</section> </section>
) )
} }
Error.displayName = "Error";

View File

@ -139,8 +139,10 @@ export function Request() {
const flow = useAppSelector(state => state.flows.byId[state.flows.selected[0]]) as HTTPFlow; const flow = useAppSelector(state => state.flows.byId[state.flows.selected[0]]) as HTTPFlow;
return <Message flow={flow} message={flow.request}/>; return <Message flow={flow} message={flow.request}/>;
} }
Request.displayName = "Request"
export function Response() { export function Response() {
const flow = useAppSelector(state => state.flows.byId[state.flows.selected[0]]) as HTTPFlow & { response: HTTPResponse } const flow = useAppSelector(state => state.flows.byId[state.flows.selected[0]]) as HTTPFlow & { response: HTTPResponse }
return <Message flow={flow} message={flow.response}/>; return <Message flow={flow} message={flow.response}/>;
} }
Response.displayName = "Response"

Some files were not shown because too many files have changed in this diff Show More