aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--web/src/js/__tests__/components/EventLog/EventListSpec.js22
-rw-r--r--web/src/js/__tests__/components/EventLog/__snapshots__/EventListSpec.js.snap30
-rw-r--r--web/src/js/__tests__/components/FlowView/DetailsSpec.js50
-rw-r--r--web/src/js/__tests__/components/FlowView/HeadersSpec.js132
-rw-r--r--web/src/js/__tests__/components/FlowView/MessagesSpec.js143
-rw-r--r--web/src/js/__tests__/components/FlowView/NavSpec.js38
-rw-r--r--web/src/js/__tests__/components/FlowView/ToggleEditSpec.js40
-rw-r--r--web/src/js/__tests__/components/FlowView/__snapshots__/DetailsSpec.js.snap274
-rw-r--r--web/src/js/__tests__/components/FlowView/__snapshots__/HeadersSpec.js.snap128
-rw-r--r--web/src/js/__tests__/components/FlowView/__snapshots__/MessagesSpec.js.snap546
-rw-r--r--web/src/js/__tests__/components/FlowView/__snapshots__/NavSpec.js.snap35
-rw-r--r--web/src/js/__tests__/components/FlowView/__snapshots__/ToggleEditSpec.js.snap17
-rw-r--r--web/src/js/components/FlowView/Headers.jsx2
-rw-r--r--web/src/js/components/FlowView/Nav.jsx3
14 files changed, 1458 insertions, 2 deletions
diff --git a/web/src/js/__tests__/components/EventLog/EventListSpec.js b/web/src/js/__tests__/components/EventLog/EventListSpec.js
new file mode 100644
index 00000000..4f14dfba
--- /dev/null
+++ b/web/src/js/__tests__/components/EventLog/EventListSpec.js
@@ -0,0 +1,22 @@
+import React from 'react'
+import EventLogList from '../../../components/EventLog/EventList'
+import TestUtils from 'react-dom/test-utils'
+
+describe('EventList Component', () => {
+ let mockEventList = [
+ { id: 1, level: 'info', message: 'foo' },
+ { id: 2, level: 'error', message: 'bar' }
+ ],
+ eventLogList = TestUtils.renderIntoDocument(<EventLogList events={mockEventList}/>)
+
+ it('should render correctly', () => {
+ expect(eventLogList.state).toMatchSnapshot()
+ expect(eventLogList.props).toMatchSnapshot()
+ })
+
+ it('should handle componentWillUnmount', () => {
+ window.removeEventListener = jest.fn()
+ eventLogList.componentWillUnmount()
+ expect(window.removeEventListener).toBeCalledWith('resize', eventLogList.onViewportUpdate)
+ })
+})
diff --git a/web/src/js/__tests__/components/EventLog/__snapshots__/EventListSpec.js.snap b/web/src/js/__tests__/components/EventLog/__snapshots__/EventListSpec.js.snap
new file mode 100644
index 00000000..10bcb598
--- /dev/null
+++ b/web/src/js/__tests__/components/EventLog/__snapshots__/EventListSpec.js.snap
@@ -0,0 +1,30 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EventList Component should render correctly 1`] = `
+Object {
+ "vScroll": Object {
+ "end": 1,
+ "paddingBottom": 18,
+ "paddingTop": 0,
+ "start": 0,
+ },
+}
+`;
+
+exports[`EventList Component should render correctly 2`] = `
+Object {
+ "events": Array [
+ Object {
+ "id": 1,
+ "level": "info",
+ "message": "foo",
+ },
+ Object {
+ "id": 2,
+ "level": "error",
+ "message": "bar",
+ },
+ ],
+ "rowHeight": 18,
+}
+`;
diff --git a/web/src/js/__tests__/components/FlowView/DetailsSpec.js b/web/src/js/__tests__/components/FlowView/DetailsSpec.js
new file mode 100644
index 00000000..1b0192cf
--- /dev/null
+++ b/web/src/js/__tests__/components/FlowView/DetailsSpec.js
@@ -0,0 +1,50 @@
+import React from 'react'
+import renderer from 'react-test-renderer'
+import Details, { TimeStamp, ConnectionInfo, CertificateInfo, Timing } from '../../../components/FlowView/Details'
+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(<Details flow={tflow}/>),
+ tree = details.toJSON()
+ expect(tree).toMatchSnapshot()
+ })
+})
diff --git a/web/src/js/__tests__/components/FlowView/HeadersSpec.js b/web/src/js/__tests__/components/FlowView/HeadersSpec.js
new file mode 100644
index 00000000..4ed93082
--- /dev/null
+++ b/web/src/js/__tests__/components/FlowView/HeadersSpec.js
@@ -0,0 +1,132 @@
+import React from 'react'
+import ReactDOM from 'react-dom'
+import renderer from 'react-test-renderer'
+import TestUtils from 'react-dom/test-utils'
+import Headers, { HeaderEditor } from '../../../components/FlowView/Headers'
+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(<Headers onChange={changeFn} message={mockMessage}/>),
+ tree = headers.toJSON()
+ expect(tree).toMatchSnapshot()
+ })
+
+ let headers = TestUtils.renderIntoDocument(<Headers 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(<Headers onChange={changeFn} message={mockMessage}/>)
+ headers.onChange(0, 0, '')
+ expect(changeFn).toBeCalledWith([['Name', 'Value']])
+
+ })
+
+})
diff --git a/web/src/js/__tests__/components/FlowView/MessagesSpec.js b/web/src/js/__tests__/components/FlowView/MessagesSpec.js
new file mode 100644
index 00000000..02db77e8
--- /dev/null
+++ b/web/src/js/__tests__/components/FlowView/MessagesSpec.js
@@ -0,0 +1,143 @@
+jest.mock('../../../components/ContentView')
+import React from 'react'
+import renderer from 'react-test-renderer'
+import TestUtils from 'react-dom/test-utils'
+import { Request, Response, ErrorView } from '../../../components/FlowView/Messages'
+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 Headers from '../../../components/FlowView/Headers'
+import ValueEditor from '../../../components/ValueEditor/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 = TestUtils.renderIntoDocument(
+ <Provider store={store}>
+ <Request/>
+ </Provider>),
+ valueEditors = TestUtils.scryRenderedComponentsWithType(provider, 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 = TestUtils.findRenderedComponentWithType(provider, Headers)
+ headers.props.onChange('foo')
+ expect(store.getActions()).toEqual([updateEdit({ request: { headers: 'foo' }})])
+ })
+
+ it('should handle change on flow request contentView', () => {
+ let contentView = TestUtils.findRenderedComponentWithType(provider, ContentView)
+ contentView.props.onContentChange('foo')
+ expect(store.getActions()).toEqual([updateEdit({ request: { content: 'foo' }})])
+ })
+
+ it('should handle uploadContent on flow request ContentViewOptions', () => {
+ let contentViewOptions = TestUtils.findRenderedComponentWithType(provider, 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 = TestUtils.renderIntoDocument(
+ <Provider store={store}>
+ <Response/>
+ </Provider>),
+ valueEditors = TestUtils.scryRenderedComponentsWithType(provider, 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 = TestUtils.findRenderedComponentWithType(provider, Headers)
+ headers.props.onChange('foo')
+ expect(store.getActions()).toEqual([updateEdit( { response: { headers: 'foo' }})])
+ })
+
+ it('should handle change on flow response ContentView', () => {
+ let contentView = TestUtils.findRenderedComponentWithType(provider, ContentView)
+ contentView.props.onContentChange('foo')
+ expect(store.getActions()).toEqual([updateEdit( { response: { content: 'foo' }})])
+ })
+
+ it('should handle updateContent on flow response ContentViewOptions', () => {
+ let contentViewOptions = TestUtils.findRenderedComponentWithType(provider, 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()
+ })
+})
diff --git a/web/src/js/__tests__/components/FlowView/NavSpec.js b/web/src/js/__tests__/components/FlowView/NavSpec.js
new file mode 100644
index 00000000..867e6f2c
--- /dev/null
+++ b/web/src/js/__tests__/components/FlowView/NavSpec.js
@@ -0,0 +1,38 @@
+import 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)
+ })
+})
diff --git a/web/src/js/__tests__/components/FlowView/ToggleEditSpec.js b/web/src/js/__tests__/components/FlowView/ToggleEditSpec.js
new file mode 100644
index 00000000..4578fdc8
--- /dev/null
+++ b/web/src/js/__tests__/components/FlowView/ToggleEditSpec.js
@@ -0,0 +1,40 @@
+// jest.mock('../../../ducks/ui/flow')
+import React from 'react'
+import renderer from 'react-test-renderer'
+import ToggleEdit from '../../../components/FlowView/ToggleEdit'
+import { Provider } from 'react-redux'
+import { startEdit, stopEdit } from '../../../ducks/ui/flow'
+import { TFlow, TStore } from '../../ducks/tutils'
+
+let tflow = new TFlow()
+
+describe('ToggleEdit Component', () => {
+ let store = TStore(),
+ provider = renderer.create(
+ <Provider store={store}>
+ <ToggleEdit/>
+ </Provider>),
+ tree = provider.toJSON()
+
+ afterEach(() => { store.clearActions() })
+
+ it('should render correctly', () => {
+ expect(tree).toMatchSnapshot()
+ })
+
+ it('should handle click on stopEdit', () => {
+ tree.children[0].props.onClick()
+ expect(store.getActions()).toEqual([stopEdit(tflow, true)])
+ })
+
+ it('should handle click on startEdit', () => {
+ store.getState().ui.flow.modifiedFlow = false
+ let provider = renderer.create(
+ <Provider store={store}>
+ <ToggleEdit/>
+ </Provider>),
+ tree = provider.toJSON()
+ tree.children[0].props.onClick()
+ expect(store.getActions()).toEqual([startEdit(tflow)])
+ })
+})
diff --git a/web/src/js/__tests__/components/FlowView/__snapshots__/DetailsSpec.js.snap b/web/src/js/__tests__/components/FlowView/__snapshots__/DetailsSpec.js.snap
new file mode 100644
index 00000000..dcee1895
--- /dev/null
+++ b/web/src/js/__tests__/components/FlowView/__snapshots__/DetailsSpec.js.snap
@@ -0,0 +1,274 @@
+// 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>
+ address:22
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <abbr
+ title="TLS Server Name Indication"
+ >
+ TLS SNI:
+ </abbr>
+ </td>
+ <td>
+ address
+ </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>
+ address:22
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <abbr
+ title="TLS Server Name Indication"
+ >
+ TLS SNI:
+ </abbr>
+ </td>
+ <td>
+ address
+ </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>
+ </tbody>
+ </table>
+ <div />
+ <div>
+ <h4>
+ Timing
+ </h4>
+ <table
+ className="timing-table"
+ >
+ <tbody>
+ <tr />
+ <tr />
+ <tr>
+ <td>
+ Server conn. initiated
+ :
+ </td>
+ <td>
+ 1970-01-01 00:00:01.000
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Client conn. established
+ :
+ </td>
+ <td>
+ 1970-01-01 00:00:01.000
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Server conn. TCP handshake
+ :
+ </td>
+ <td>
+ 1970-01-01 00:00:02.000
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Client conn. SSL handshake
+ :
+ </td>
+ <td>
+ 1970-01-01 00:00:02.000
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Server conn. SSL handshake
+ :
+ </td>
+ <td>
+ 1970-01-01 00:00:03.000
+ </td>
+ </tr>
+ <tr>
+ <td>
+ First response byte
+ :
+ </td>
+ <td>
+ 2017-05-21 12:38:32.481
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Response complete
+ :
+ </td>
+ <td>
+ 2017-05-21 12:38:32.481
+ </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 />
+ <tr />
+ <tr>
+ <td>
+ Server conn. initiated
+ :
+ </td>
+ <td>
+ 1970-01-01 00:00:01.000
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Client conn. established
+ :
+ </td>
+ <td>
+ 1970-01-01 00:00:01.000
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Server conn. TCP handshake
+ :
+ </td>
+ <td>
+ 1970-01-01 00:00:02.000
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Client conn. SSL handshake
+ :
+ </td>
+ <td>
+ 1970-01-01 00:00:02.000
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Server conn. SSL handshake
+ :
+ </td>
+ <td>
+ 1970-01-01 00:00:03.000
+ </td>
+ </tr>
+ <tr>
+ <td>
+ First response byte
+ :
+ </td>
+ <td>
+ 2017-05-21 12:38:32.481
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Response complete
+ :
+ </td>
+ <td>
+ 2017-05-21 12:38:32.481
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+`;
diff --git a/web/src/js/__tests__/components/FlowView/__snapshots__/HeadersSpec.js.snap b/web/src/js/__tests__/components/FlowView/__snapshots__/HeadersSpec.js.snap
new file mode 100644
index 00000000..5b5374dd
--- /dev/null
+++ b/web/src/js/__tests__/components/FlowView/__snapshots__/HeadersSpec.js.snap
@@ -0,0 +1,128 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`HeaderEditor Component should render correctly 1`] = `
+<div
+ className="inline-input editable"
+ contentEditable={undefined}
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "foo",
+ }
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onInput={[Function]}
+ onKeyDown={[Function]}
+ onMouseDown={[Function]}
+ onPaste={[Function]}
+ tabIndex={0}
+/>
+`;
+
+exports[`Headers Component should handle correctly 1`] = `
+<table
+ className="header-table"
+>
+ <tbody>
+ <tr>
+ <td
+ className="header-name"
+ >
+ <div
+ className="inline-input editable"
+ contentEditable={undefined}
+ 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"
+ contentEditable={undefined}
+ 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"
+ contentEditable={undefined}
+ 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"
+ contentEditable={undefined}
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "",
+ }
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onInput={[Function]}
+ onKeyDown={[Function]}
+ onMouseDown={[Function]}
+ onPaste={[Function]}
+ tabIndex={0}
+ />
+ </td>
+ </tr>
+ </tbody>
+</table>
+`;
diff --git a/web/src/js/__tests__/components/FlowView/__snapshots__/MessagesSpec.js.snap b/web/src/js/__tests__/components/FlowView/__snapshots__/MessagesSpec.js.snap
new file mode 100644
index 00000000..c8290e45
--- /dev/null
+++ b/web/src/js/__tests__/components/FlowView/__snapshots__/MessagesSpec.js.snap
@@ -0,0 +1,546 @@
+// 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>
+ 2017-05-21 12:38:32.481
+ </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"
+ contentEditable={undefined}
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "GET",
+ }
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onInput={[Function]}
+ onKeyDown={[Function]}
+ onMouseDown={[Function]}
+ onPaste={[Function]}
+ tabIndex={undefined}
+ />
+  
+ <div
+ className="inline-input readonly has-success"
+ contentEditable={undefined}
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "http://address:22/path",
+ }
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onInput={[Function]}
+ onKeyDown={[Function]}
+ onMouseDown={[Function]}
+ onPaste={[Function]}
+ tabIndex={undefined}
+ />
+  
+ <div
+ className="inline-input readonly has-success"
+ contentEditable={undefined}
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "HTTP/1.1",
+ }
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onInput={[Function]}
+ onKeyDown={[Function]}
+ onMouseDown={[Function]}
+ onPaste={[Function]}
+ tabIndex={undefined}
+ />
+ </div>
+ </div>
+ <table
+ className="header-table"
+ >
+ <tbody>
+ <tr>
+ <td
+ className="header-name"
+ >
+ <div
+ className="inline-input readonly"
+ contentEditable={undefined}
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "header",
+ }
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onInput={[Function]}
+ onKeyDown={[Function]}
+ onMouseDown={[Function]}
+ onPaste={[Function]}
+ tabIndex={undefined}
+ />
+ <span
+ className="header-colon"
+ >
+ :
+ </span>
+ </td>
+ <td
+ className="header-value"
+ >
+ <div
+ className="inline-input readonly"
+ contentEditable={undefined}
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "qvalue",
+ }
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onInput={[Function]}
+ onKeyDown={[Function]}
+ onMouseDown={[Function]}
+ onPaste={[Function]}
+ tabIndex={undefined}
+ />
+ </td>
+ </tr>
+ <tr>
+ <td
+ className="header-name"
+ >
+ <div
+ className="inline-input readonly"
+ contentEditable={undefined}
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "content-length",
+ }
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onInput={[Function]}
+ onKeyDown={[Function]}
+ onMouseDown={[Function]}
+ onPaste={[Function]}
+ tabIndex={undefined}
+ />
+ <span
+ className="header-colon"
+ >
+ :
+ </span>
+ </td>
+ <td
+ className="header-value"
+ >
+ <div
+ className="inline-input readonly"
+ contentEditable={undefined}
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "7",
+ }
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onInput={[Function]}
+ onKeyDown={[Function]}
+ onMouseDown={[Function]}
+ onPaste={[Function]}
+ tabIndex={undefined}
+ />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <hr />
+ </article>
+ <footer>
+ <div
+ className="view-options"
+ >
+ <div
+ className="dropup pull-left"
+ >
+ <a
+ className="btn btn-default btn-xs"
+ href="#"
+ onClick={[Function]}
+ >
+ <span>
+
+ <b>
+ View:
+ </b>
+
+ auto
+
+ <span
+ className="caret"
+ />
+
+ </span>
+ </a>
+ <ul
+ className="dropdown-menu"
+ role="menu"
+ >
+ <li>
+
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ auto
+ </a>
+
+ </li>
+ <li>
+
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ raw
+ </a>
+
+ </li>
+ <li>
+
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ text
+ </a>
+
+ </li>
+ </ul>
+ </div>
+  
+ <a
+ className="btn btn-default btn-xs"
+ href="/flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/request/content"
+ title="Download the content of the flow."
+ >
+ <i
+ className="fa fa-download"
+ />
+ </a>
+  
+  
+ <span>
+ foo
+ </span>
+ </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"
+ contentEditable={undefined}
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "HTTP/1.1",
+ }
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onInput={[Function]}
+ onKeyDown={[Function]}
+ onMouseDown={[Function]}
+ onPaste={[Function]}
+ tabIndex={undefined}
+ />
+  
+ <div
+ className="inline-input readonly has-success"
+ contentEditable={undefined}
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "200",
+ }
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onInput={[Function]}
+ onKeyDown={[Function]}
+ onMouseDown={[Function]}
+ onPaste={[Function]}
+ tabIndex={undefined}
+ />
+  
+ <div
+ className="inline-input readonly"
+ contentEditable={undefined}
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "OK",
+ }
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onInput={[Function]}
+ onKeyDown={[Function]}
+ onMouseDown={[Function]}
+ onPaste={[Function]}
+ tabIndex={undefined}
+ />
+ </div>
+ <table
+ className="header-table"
+ >
+ <tbody>
+ <tr>
+ <td
+ className="header-name"
+ >
+ <div
+ className="inline-input readonly"
+ contentEditable={undefined}
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "header-response",
+ }
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onInput={[Function]}
+ onKeyDown={[Function]}
+ onMouseDown={[Function]}
+ onPaste={[Function]}
+ tabIndex={undefined}
+ />
+ <span
+ className="header-colon"
+ >
+ :
+ </span>
+ </td>
+ <td
+ className="header-value"
+ >
+ <div
+ className="inline-input readonly"
+ contentEditable={undefined}
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "svalue",
+ }
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onInput={[Function]}
+ onKeyDown={[Function]}
+ onMouseDown={[Function]}
+ onPaste={[Function]}
+ tabIndex={undefined}
+ />
+ </td>
+ </tr>
+ <tr>
+ <td
+ className="header-name"
+ >
+ <div
+ className="inline-input readonly"
+ contentEditable={undefined}
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "content-length",
+ }
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onInput={[Function]}
+ onKeyDown={[Function]}
+ onMouseDown={[Function]}
+ onPaste={[Function]}
+ tabIndex={undefined}
+ />
+ <span
+ className="header-colon"
+ >
+ :
+ </span>
+ </td>
+ <td
+ className="header-value"
+ >
+ <div
+ className="inline-input readonly"
+ contentEditable={undefined}
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "7",
+ }
+ }
+ onBlur={[Function]}
+ onClick={[Function]}
+ onFocus={[Function]}
+ onInput={[Function]}
+ onKeyDown={[Function]}
+ onMouseDown={[Function]}
+ onPaste={[Function]}
+ tabIndex={undefined}
+ />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <hr />
+ </article>
+ <footer>
+ <div
+ className="view-options"
+ >
+ <div
+ className="dropup pull-left"
+ >
+ <a
+ className="btn btn-default btn-xs"
+ href="#"
+ onClick={[Function]}
+ >
+ <span>
+
+ <b>
+ View:
+ </b>
+
+ auto
+
+ <span
+ className="caret"
+ />
+
+ </span>
+ </a>
+ <ul
+ className="dropdown-menu"
+ role="menu"
+ >
+ <li>
+
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ auto
+ </a>
+
+ </li>
+ <li>
+
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ raw
+ </a>
+
+ </li>
+ <li>
+
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ text
+ </a>
+
+ </li>
+ </ul>
+ </div>
+  
+ <a
+ className="btn btn-default btn-xs"
+ href="/flows/d91165be-ca1f-4612-88a9-c0f8696f3e29/response/content"
+ title="Download the content of the flow."
+ >
+ <i
+ className="fa fa-download"
+ />
+ </a>
+  
+  
+ <span>
+ foo
+ </span>
+ </div>
+ </footer>
+</section>
+`;
diff --git a/web/src/js/__tests__/components/FlowView/__snapshots__/NavSpec.js.snap b/web/src/js/__tests__/components/FlowView/__snapshots__/NavSpec.js.snap
new file mode 100644
index 00000000..b1b1d3fd
--- /dev/null
+++ b/web/src/js/__tests__/components/FlowView/__snapshots__/NavSpec.js.snap
@@ -0,0 +1,35 @@
+// 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>
+`;
diff --git a/web/src/js/__tests__/components/FlowView/__snapshots__/ToggleEditSpec.js.snap b/web/src/js/__tests__/components/FlowView/__snapshots__/ToggleEditSpec.js.snap
new file mode 100644
index 00000000..5a4243a2
--- /dev/null
+++ b/web/src/js/__tests__/components/FlowView/__snapshots__/ToggleEditSpec.js.snap
@@ -0,0 +1,17 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ToggleEdit Component should render correctly 1`] = `
+<div
+ className="edit-flow-container"
+>
+ <a
+ className="edit-flow"
+ onClick={[Function]}
+ title="Finish Edit"
+ >
+ <i
+ className="fa fa-check"
+ />
+ </a>
+</div>
+`;
diff --git a/web/src/js/components/FlowView/Headers.jsx b/web/src/js/components/FlowView/Headers.jsx
index 92e11465..76ca65b6 100644
--- a/web/src/js/components/FlowView/Headers.jsx
+++ b/web/src/js/components/FlowView/Headers.jsx
@@ -4,7 +4,7 @@ import ReactDOM from 'react-dom'
import ValueEditor from '../ValueEditor/ValueEditor'
import { Key } from '../../utils'
-class HeaderEditor extends Component {
+export class HeaderEditor extends Component {
constructor(props) {
super(props)
diff --git a/web/src/js/components/FlowView/Nav.jsx b/web/src/js/components/FlowView/Nav.jsx
index af5a879e..022f2f2b 100644
--- a/web/src/js/components/FlowView/Nav.jsx
+++ b/web/src/js/components/FlowView/Nav.jsx
@@ -2,6 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import classnames from 'classnames'
+import _ from 'lodash'
NavAction.propTypes = {
icon: PropTypes.string.isRequired,
@@ -9,7 +10,7 @@ NavAction.propTypes = {
onClick: PropTypes.func.isRequired,
}
-function NavAction({ icon, title, onClick }) {
+export function NavAction({ icon, title, onClick }) {
return (
<a title={title}
href="#"